select 允许单线程同时监控多个套接字的可读、可写、异常状态,实现I/O 多路复用。避免为每个连接创建线程,降低资源消耗,适合连接数适中(<1000)的场景。
int select(int nfds, // Windows 忽略,保留兼容性
fd_set *readfds, // 关注可读的套接字集合
fd_set *writefds, // 关注可写的套接字集合
fd_set *exceptfds, // 关注异常的套接字集合
const struct timeval *timeout); // 超时时间,NULL=永久阻塞
返回值:就绪套接字总数;SOCKET_ERROR 表示出错,需 WSAGetLastError()。
| 宏 | 作用 |
|---|---|
FD_ZERO(&set) | 清空集合 |
FD_SET(s, &set) | 将套接字 s 加入集合 |
FD_CLR(s, &set) | 从集合移除 s |
FD_ISSET(s, &set) | 测试 s 是否在集合中(select 返回后调用) |
Windows 默认 FD_SETSIZE=64,可在包含 winsock2.h 前定义更大值(≤1024)。
accept 不阻塞)。注意:可写事件通常持续就绪,若持续监控会导致 select 频繁返回,建议仅在需要发送数据时加入 writefds。
FD_ZERO 初始化关注集合。FD_SET 将监听套接字及所有已连接套接字加入 readfds。select 阻塞等待。readfds(用 FD_ISSET 判断):
accept 新连接,加入集合。recv 读取数据;若返回 0 或错误 → 关闭套接字,从集合移除。writefds,下轮 select 监控可写;发送完成后移出 writefds。#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <map>
#pragma comment(lib, "ws2_32.lib")
int main() {
WSADATA wsa; WSAStartup(MAKEWORD(2,2), &wsa);
SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in addr = {}; addr.sin_family = AF_INET; addr.sin_port = htons(5000); addr.sin_addr.s_addr = INADDR_ANY;
bind(listen_sock, (sockaddr*)&addr, sizeof(addr));
listen(listen_sock, SOMAXCONN);
fd_set read_set; std::map clients; // 简化:不缓存未发完数据
while (true) {
FD_ZERO(&read_set);
FD_SET(listen_sock, &read_set);
for (auto &kv : clients) FD_SET(kv.first, &read_set);
int ret = select(0, &read_set, NULL, NULL, NULL);
if (ret == SOCKET_ERROR) break;
if (FD_ISSET(listen_sock, &read_set)) {
SOCKET client = accept(listen_sock, NULL, NULL);
clients[client] = 0;
}
for (auto it = clients.begin(); it != clients.end(); ) {
SOCKET s = it->first;
if (!FD_ISSET(s, &read_set)) { ++it; continue; }
char buf[1024];
int len = recv(s, buf, sizeof(buf), 0);
if (len <= 0) { closesocket(s); it = clients.erase(it); continue; }
// 立即回显(实际应处理可写、缓存未发完数据)
send(s, buf, len, 0);
++it;
}
}
WSACleanup(); return 0;
}
| 局限 | 影响 | 替代 |
|---|---|---|
| FD_SETSIZE 限制(默认 64/1024) | 无法处理超大连接数 | IOCP (Windows)、epoll (Linux)、kqueue (BSD) |
| 每次调用需全量拷贝 fd_set 到内核 | O(N) 开销,N 大时性能下降 | epoll/kqueue/IOCP 仅注册一次 |
| 无法区分“可读有数据”与“连接关闭” | 需额外 recv 判断 | epoll EPOLLRDHUP、IOCP 完成通知 |