首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

I/O 多路復用之 select 詳細總結:原理、API 與 TCP 回射服務器實戰

     編輯:關於C語言

什麼是 I/O 多路復用

內核監視多個文件描述符,一旦某個描述符就緒 (可讀/可寫/異常),通知進程處理。適用場景:

  • 同時處理多個客戶連接 (如聊天服務器)
  • 同時監聽 TCP 和 UDP 端口
  • 需處理標準輸入與網絡套接字

優勢:單進程/線程處理多連接,無進程切換開銷,內存占用極低。

select 系統調用詳解

#include <sys/select.h>
#include <sys/time.h>

int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);
// 返回:就緒描述符數,超時返回 0,出錯 -1

關鍵參數

  • maxfdp1:最大描述符值 + 1
  • readset/writeset/exceptset:關注的描述符集合,傳入傳出參數
  • timeout:NULL 永久等待;tv_sec=tv_usec=0 輪詢;否則定時等待

fd_set 操作宏

FD_ZERO(fd_set *set);       // 清空
FD_SET(int fd, fd_set *set); // 添加
FD_CLR(int fd, fd_set *set); // 移除
FD_ISSET(int fd, fd_set *set); // 判斷是否就緒

TCP 回射服務器完整實現

服務端 (select_server.c)

#include <stdio.h> <stdlib.h> <string.h> <errno.h>
#include <netinet/in.h> <sys/socket.h> <sys/select.h> <unistd.h> <sys/types.h>

#define IP "127.0.0.1"  PORT 8787  MAXLINE 1024  LISTENQ 5

int socket_bind(const char* ip, int port);
void do_select(int listenfd);
void handle_connection(int *connfds, int num, fd_set *prset, fd_set *pallset);

int main() {
    int listenfd = socket_bind(IP, PORT);
    listen(listenfd, LISTENQ);
    do_select(listenfd);
    return 0;
}

int socket_bind(const char* ip, int port) {
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr = {0};
    servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port);
    inet_pton(AF_INET, ip, &servaddr.sin_addr);
    bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    return listenfd;
}

void do_select(int listenfd) {
    fd_set rset, allset; FD_ZERO(&allset); FD_SET(listenfd, &allset);
    int maxfd = listenfd, maxi = -1, clientfds[FD_SETSIZE];
    for (int i=0;imaxfd)?connfd:maxfd; maxi = (i>maxi)?i:maxi;
            if (--nready <= 0) continue;
        }
        handle_connection(clientfds, maxi, &rset, &allset);
    }
}

void handle_connection(int *connfds, int num, fd_set *prset, fd_set *pallset) {
    char buf[MAXLINE];
    for (int i=0;i<=num;i++) {
        if (connfds[i] < 0) continue;
        if (FD_ISSET(connfds[i], prset)) {
            int n = read(connfds[i], buf, MAXLINE);
            if (n == 0) { close(connfds[i]); FD_CLR(connfds[i], pallset); connfds[i]=-1; continue; }
            write(STDOUT_FILENO, buf, n); write(connfds[i], buf, n); // 回射
        }
    }
}

客戶端 (select_client.c)

#include <netinet/in.h> <sys/socket.h> <stdio.h> <string.h> <stdlib.h> <sys/select.h> <unistd.h>

#define MAXLINE 1024  IP "127.0.0.1"  PORT 8787

void handle_connection(int sockfd) {
    char send[MAXLINE], recv[MAXLINE]; fd_set rset; int maxfdp, n;
    for (;;) {
        FD_ZERO(&rset); FD_SET(STDIN_FILENO, &rset); FD_SET(sockfd, &rset);
        maxfdp = (STDIN_FILENO > sockfd) ? STDIN_FILENO : sockfd;
        select(maxfdp+1, &rset, NULL, NULL, NULL);
        if (FD_ISSET(sockfd, &rset)) {
            n = read(sockfd, recv, MAXLINE);
            if (n == 0) { fprintf(stderr, "Server closed\n"); close(sockfd); exit(0); }
            write(STDOUT_FILENO, recv, n);
        }
        if (FD_ISSET(STDIN_FILENO, &rset)) {
            n = read(STDIN_FILENO, send, MAXLINE);
            if (n == 0) { FD_CLR(STDIN_FILENO, &rset); continue; }
            write(sockfd, send, n);
        }
    }
}

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serv = {0}; serv.sin_family=AF_INET; serv.sin_port=htons(PORT);
    inet_pton(AF_INET, IP, &serv.sin_addr);
    connect(sockfd, (struct sockaddr*)&serv, sizeof(serv));
    handle_connection(sockfd);
    return 0;
}

運行效果

啟動服務端,開啟多個客戶端,輸入任意文本均可收到回射回復,驗證多路復用正確性。

select 缺點與替代方案

  • 單進程監視描述符上限 FD_SETSIZE (通常 1024)
  • 每次調用需線性掃描所有描述符,O(n) 開銷
  • 需維護用戶態 fd_set,用戶/內核態數據拷貝

高併發場景建議使用 poll、epoll (Linux)、kqueue (BSD) 或 IOCP (Windows)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved