內核監視多個文件描述符,一旦某個描述符就緒 (可讀/可寫/異常),通知進程處理。適用場景:
優勢:單進程/線程處理多連接,無進程切換開銷,內存占用極低。
#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:最大描述符值 + 1readset/writeset/exceptset:關注的描述符集合,傳入傳出參數timeout:NULL 永久等待;tv_sec=tv_usec=0 輪詢;否則定時等待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); // 判斷是否就緒
#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); // 回射
}
}
}
#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;
}
啟動服務端,開啟多個客戶端,輸入任意文本均可收到回射回復,驗證多路復用正確性。
FD_SETSIZE (通常 1024)O(n) 開銷fd_set,用戶/內核態數據拷貝高併發場景建議使用 poll、epoll (Linux)、kqueue (BSD) 或 IOCP (Windows)。