在C语言网络编程中,处理多个客户端连接是一个常见需求。如果为每个连接都创建一个线程或进程,不仅资源消耗大,而且难以维护。这时,IO多路复用技术就派上用场了。而 select 函数正是实现 IO 多路复用最基础、最经典的系统调用之一。
select 是 Unix/Linux 系统提供的一个系统调用,用于监视多个文件描述符(file descriptor,简称 fd)的状态变化,比如是否有数据可读、是否可写、是否发生异常等。它允许程序在一个线程中同时监听多个 socket,而无需阻塞在某一个连接上。
在 C 语言中,select 的函数声明如下:
#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 使用 select 前,需要对 fd_set 集合进行操作,常用宏包括:
FD_ZERO(fd_set *set):清空集合。FD_SET(int fd, fd_set *set):将 fd 加入集合。FD_CLR(int fd, fd_set *set):从集合中移除 fd。FD_ISSET(int fd, fd_set *set):判断 fd 是否在集合中(通常用于 select 返回后检查)。下面是一个使用 select 实现的简单回显服务器,它可以同时处理多个客户端连接:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <sys/select.h>#define PORT 8080#define MAX_CLIENTS 10#define BUFFER_SIZE 1024int main() { int server_fd, new_socket, client_sockets[MAX_CLIENTS]; struct sockaddr_in address; int addrlen = sizeof(address); char buffer[BUFFER_SIZE] = {0}; fd_set readfds; int activity, i, valread; // 初始化客户端 socket 数组 for (i = 0; i < MAX_CLIENTS; i++) client_sockets[i] = 0; // 创建服务器 socket if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 设置地址重用 int opt = 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } if (listen(server_fd, 3) < 0) { perror("listen"); exit(EXIT_FAILURE); } printf("Server listening on port %d\n", PORT); while (1) { FD_ZERO(&readfds); FD_SET(server_fd, &readfds); int max_sd = server_fd; // 添加客户端 sockets 到 readfds for (i = 0; i < MAX_CLIENTS; i++) { int sd = client_sockets[i]; if (sd > 0) FD_SET(sd, &readfds); if (sd > max_sd) max_sd = sd; } // 等待活动(阻塞) activity = select(max_sd + 1, &readfds, NULL, NULL, NULL); if (activity < 0) { perror("select error"); break; } // 新连接请求 if (FD_ISSET(server_fd, &readfds)) { if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { perror("accept"); continue; } // 将新 socket 加入客户端列表 for (i = 0; i < MAX_CLIENTS; i++) { if (client_sockets[i] == 0) { client_sockets[i] = new_socket; printf("New client connected, socket fd: %d\n", new_socket); break; } } } // 检查已有客户端是否有数据 for (i = 0; i < MAX_CLIENTS; i++) { int sd = client_sockets[i]; if (FD_ISSET(sd, &readfds)) { if ((valread = read(sd, buffer, BUFFER_SIZE)) == 0) { // 客户端断开连接 getpeername(sd, (struct sockaddr*)&address, (socklen_t*)&addrlen); printf("Client disconnected, ip: %s, port: %d\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port)); close(sd); client_sockets[i] = 0; } else { // 回显数据 send(sd, buffer, strlen(buffer), 0); memset(buffer, 0, BUFFER_SIZE); } } } } return 0;} 通过本教程,你应该已经掌握了 C语言select函数的基本用法,并理解了它在 IO多路复用 和 C语言socket编程 中的核心作用。虽然现代高性能服务器更多使用 epoll(Linux)或 kqueue(BSD),但 select 依然是学习网络编程的重要起点。
掌握 select 不仅能帮助你构建简单的并发服务器,还能为你深入理解更高级的 I/O 模型打下坚实基础。
本文由主机测评网于2025-12-05发表在主机测评网_免费VPS_免费云服务器_免费独立服务器,如有疑问,请联系我们。
本文链接:https://www.vpshk.cn/2025123472.html