1.select :基于位图 实现,监听文件描述符有最大数量限制(默认 1024),由内核参数FD_SETSIZE控制,无法突破。
2.在IO多路复用中,位图(Bitmap) 是一种用二进制位(bit)表示状态的数据结构,每个位对应一个元素,0表示"否"(不监听或未就绪),1表示"是"(监听或就绪)。其核心优势在于空间效率高 (每个文件描述符仅需1位存储)和操作高效(支持快速的位运算)。
位图在select中的实现原理
-
数据结构定义
select使用fd_set类型表示位图,其本质是一个整型数组 。在Linux内核中,fd_set的大小由__FD_SETSIZE(默认1024)定义,通过long数组模拟位图:cpp#define __FD_SETSIZE 1024 #define __NFDBITS (8 * sizeof(long)) typedef struct { long fds_bits[__FD_SETSIZE / __NFDBITS]; } fd_set;- 每个
long占8字节(64位),因此fd_set需1024 / 64 = 16个long存储,共1024位。 - 每个位对应一个文件描述符(fd),例如第3位为1表示监听fd=3。
- 每个
-
位图操作宏
Linux提供以下宏操作位图:FD_ZERO(&fdset):清空位图(所有位设为0)。FD_SET(fd, &fdset):将fd对应的位置1(开始监听)。FD_CLR(fd, &fdset):将fd对应的位置0(停止监听)。FD_ISSET(fd, &fdset):检查fd对应的位是否为1(是否就绪)。
-
select的位图监听流程
select通过三个位图(readfds、writefds、exceptfds)分别监听文件描述符的读、写、异常事件,流程如下:- 用户态初始化 :
用户程序将需要监听的fd通过FD_SET注册到三个位图中,并调用select()系统调用。 - 内核态处理 :
- 内核将用户空间的位图拷贝到内核空间(
copy_from_user)。 - 遍历位图中所有设置为1的fd,调用其
poll()方法检查状态:- 读就绪:接收缓冲区有数据或连接关闭(FIN包)。
- 写就绪:发送缓冲区有足够空间。
- 异常:连接重置(RST包)或带外数据(urgent data)。
- 将就绪的fd保留在结果位图中,未就绪的fd置0。
- 内核将用户空间的位图拷贝到内核空间(
- 返回用户态 :
- 内核将结果位图拷贝回用户空间(
copy_to_user)。 - 用户程序通过
FD_ISSET检查哪些fd就绪,并处理事件。
- 内核将结果位图拷贝回用户空间(
- 用户态初始化 :
select的位图限制与性能瓶颈
- 文件描述符数量限制
- 默认最多监听1024个fd(受
__FD_SETSIZE限制),可通过修改内核参数调整,但需重新编译内核。 - 每个
fd_set占用128字节(16×8字节),三个位图共384字节。
- 默认最多监听1024个fd(受
- 性能问题
- 内存拷贝开销 :每次调用
select需两次用户态与内核态之间的位图拷贝(copy_from_user和copy_to_user)。 - 线性扫描低效:内核需遍历所有fd(时间复杂度O(n)),即使只有少数fd就绪。
- 重复初始化:用户程序需在每次调用前重新初始化或备份位图(因内核会修改传入位图)。
- 在
select()的使用中,用户程序必须在每次调用前重新初始化或备份fd_set位图,因为内核会直接修改传入的位图,仅保留就绪的文件描述符(fd)对应的位,而清除其他位。这种设计虽然节省了内存拷贝开销,但要求开发者手动维护位图状态。
- 内存拷贝开销 :每次调用
示例代码解析
以下是一个简单的select服务器示例,展示位图的使用:
cpp
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
int main() {
fd_set readfds; // 读位图
FD_ZERO(&readfds); // 清空位图
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
FD_SET(server_fd, &readfds); // 监听server_fd
while (1) {
fd_set tmp_fds = readfds; // 备份位图(避免内核修改影响后续监听)
int ready_fds = select(server_fd + 1, &tmp_fds, NULL, NULL, NULL);
if (FD_ISSET(server_fd, &tmp_fds)) {
// 处理新连接
int client_fd = accept(server_fd, NULL, NULL);
FD_SET(client_fd, &readfds); // 将新fd加入监听
}
// 遍历所有客户端fd
for (int i = server_fd + 1; i <= server_fd + 1024; i++) {
if (FD_ISSET(i, &tmp_fds)) {
// 处理客户端数据读写
}
}
}
return 0;
}
- 关键点 :
- 使用
tmp_fds备份位图,避免内核修改影响后续监听。 - 通过
FD_ISSET检查就绪的fd,并处理事件。
- 使用
总结
select通过位图高效管理文件描述符的监听状态,但其设计存在性能瓶颈(如内存拷贝、线性扫描、fd数量限制) ,适合连接数较少的场景。对于高并发需求,更推荐使用epoll(Linux)或kqueue(BSD),它们通过事件驱动机制避免了轮询开销,性能更优。
Windowns下实现的select
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <time.h>
#include <sys/timeb.h>
#pragma comment(lib, "ws2_32.lib")
#define MAX_SOCKETS 1024
#define BUFFER_SIZE 4096
#define TIMEOUT_MS 1000
// Socket事件类型枚举
typedef enum {
EVENT_NONE = 0,
EVENT_READ = 1 << 0,
EVENT_WRITE = 1 << 1,
EVENT_ERROR = 1 << 2,
EVENT_ALL = EVENT_READ | EVENT_WRITE | EVENT_ERROR
} SocketEvent;
// Socket信息结构
typedef struct {
SOCKET fd;
struct sockaddr_in addr;
char read_buffer[BUFFER_SIZE];
char write_buffer[BUFFER_SIZE];
int read_len;
int write_len;
int events; // 感兴趣的事件
int revents; // 返回的事件
void* user_data; // 用户数据
time_t last_active; // 最后活动时间
} SocketInfo;
// Select多路复用器
typedef struct {
// select的三个位图
fd_set read_fds; // 读集合
fd_set write_fds; // 写集合
fd_set except_fds; // 异常集合
// 主位图(所有监控的socket)
fd_set master_read_fds;
fd_set master_write_fds;
fd_set master_except_fds;
// socket信息管理
SocketInfo* sockets[MAX_SOCKETS];
int max_fd; // 最大文件描述符
int socket_count; // 监控的socket数量
// 统计信息
long total_polls;
long total_events;
struct timeb start_time;
} SelectMultiplexer;
// ============ 工具函数 ============
// 获取当前时间戳(毫秒)
long get_current_time() {
struct timeb tb;
ftime(&tb);
return tb.time * 1000 + tb.millitm;
}
// 设置socket为非阻塞模式
int set_nonblocking(SOCKET sock) {
unsigned long mode = 1;
return ioctlsocket(sock, FIONBIO, &mode);
}
// ============ 多路复用器管理 ============
// 创建多路复用器
SelectMultiplexer* select_create() {
SelectMultiplexer* mux = (SelectMultiplexer*)calloc(1, sizeof(SelectMultiplexer));
if (!mux) return NULL;
// 初始化所有fd_set
FD_ZERO(&mux->read_fds);
FD_ZERO(&mux->write_fds);
FD_ZERO(&mux->except_fds);
FD_ZERO(&mux->master_read_fds);
FD_ZERO(&mux->master_write_fds);
FD_ZERO(&mux->master_except_fds);
mux->max_fd = 0;
mux->socket_count = 0;
mux->total_polls = 0;
mux->total_events = 0;
ftime(&mux->start_time);
// 初始化socket数组
for (int i = 0; i < MAX_SOCKETS; i++) {
mux->sockets[i] = NULL;
}
return mux;
}
// 销毁多路复用器
void select_destroy(SelectMultiplexer* mux) {
if (!mux) return;
// 关闭所有socket并释放资源
for (int i = 0; i < MAX_SOCKETS; i++) {
if (mux->sockets[i]) {
closesocket(mux->sockets[i]->fd);
free(mux->sockets[i]);
mux->sockets[i] = NULL;
}
}
free(mux);
}
// 创建socket信息
SocketInfo* create_socket_info(SOCKET fd, struct sockaddr_in* addr, int events) {
SocketInfo* info = (SocketInfo*)calloc(1, sizeof(SocketInfo));
if (!info) return NULL;
info->fd = fd;
if (addr) {
info->addr = *addr;
}
info->events = events;
info->revents = 0;
info->read_len = 0;
info->write_len = 0;
info->last_active = time(NULL);
return info;
}
// ============ socket管理 ============
// 添加socket到多路复用器
int select_add(SelectMultiplexer* mux, SOCKET fd, struct sockaddr_in* addr, int events, void* user_data) {
if (!mux || fd == INVALID_SOCKET) return -1;
// 检查是否已存在
for (int i = 0; i < MAX_SOCKETS; i++) {
if (mux->sockets[i] && mux->sockets[i]->fd == fd) {
return -2; // 已存在
}
}
// 创建socket信息
SocketInfo* info = create_socket_info(fd, addr, events);
if (!info) return -3;
info->user_data = user_data;
// 找到空闲位置
int index = -1;
for (int i = 0; i < MAX_SOCKETS; i++) {
if (mux->sockets[i] == NULL) {
mux->sockets[i] = info;
index = i;
break;
}
}
if (index == -1) {
free(info);
return -4; // 超出最大限制
}
// 根据事件类型添加到对应的主集合
if (events & EVENT_READ) {
FD_SET(fd, &mux->master_read_fds);
}
if (events & EVENT_WRITE) {
FD_SET(fd, &mux->master_write_fds);
}
if (events & EVENT_ERROR) {
FD_SET(fd, &mux->master_except_fds);
}
// 更新最大fd
if ((int)fd > mux->max_fd) {
mux->max_fd = fd;
}
mux->socket_count++;
return 0;
}
// 修改socket监控事件
int select_modify(SelectMultiplexer* mux, SOCKET fd, int events) {
if (!mux) return -1;
SocketInfo* info = NULL;
for (int i = 0; i < MAX_SOCKETS; i++) {
if (mux->sockets[i] && mux->sockets[i]->fd == fd) {
info = mux->sockets[i];
break;
}
}
if (!info) return -2;
int old_events = info->events;
// 从主集合中移除
if (old_events & EVENT_READ) {
FD_CLR(fd, &mux->master_read_fds);
}
if (old_events & EVENT_WRITE) {
FD_CLR(fd, &mux->master_write_fds);
}
if (old_events & EVENT_ERROR) {
FD_CLR(fd, &mux->master_except_fds);
}
// 添加到新的主集合
if (events & EVENT_READ) {
FD_SET(fd, &mux->master_read_fds);
}
if (events & EVENT_WRITE) {
FD_SET(fd, &mux->master_write_fds);
}
if (events & EVENT_ERROR) {
FD_SET(fd, &mux->master_except_fds);
}
info->events = events;
return 0;
}
// 从多路复用器移除socket
int select_remove(SelectMultiplexer* mux, SOCKET fd) {
if (!mux) return -1;
for (int i = 0; i < MAX_SOCKETS; i++) {
if (mux->sockets[i] && mux->sockets[i]->fd == fd) {
SocketInfo* info = mux->sockets[i];
// 从所有主集合中移除
FD_CLR(fd, &mux->master_read_fds);
FD_CLR(fd, &mux->master_write_fds);
FD_CLR(fd, &mux->master_except_fds);
// 清理资源
closesocket(fd);
free(info);
mux->sockets[i] = NULL;
mux->socket_count--;
// 重新计算最大fd
if (fd == mux->max_fd) {
mux->max_fd = 0;
for (int j = 0; j < MAX_SOCKETS; j++) {
if (mux->sockets[j] && (int)mux->sockets[j]->fd > mux->max_fd) {
mux->max_fd = mux->sockets[j]->fd;
}
}
}
return 0;
}
}
return -2; // 未找到
}
// ============ 核心Select操作 ============
// 执行select轮询
int select_poll(SelectMultiplexer* mux, int timeout_ms) {
if (!mux || mux->socket_count == 0) return -1;
// 复制主集合到工作集合
mux->read_fds = mux->master_read_fds;
mux->write_fds = mux->master_write_fds;
mux->except_fds = mux->master_except_fds;
// 设置超时
struct timeval timeout;
timeout.tv_sec = timeout_ms / 1000;
timeout.tv_usec = (timeout_ms % 1000) * 1000;
// 执行select
int activity = select(mux->max_fd + 1,
&mux->read_fds,
&mux->write_fds,
&mux->except_fds,
&timeout);
mux->total_polls++;
if (activity == SOCKET_ERROR) {
return -WSAGetLastError();
}
if (activity == 0) {
return 0; // 超时,没有事件
}
// 更新所有socket的返回事件
int event_count = 0;
for (int i = 0; i < MAX_SOCKETS; i++) {
SocketInfo* info = mux->sockets[i];
if (!info) continue;
info->revents = 0;
// 检查读事件
if ((info->events & EVENT_READ) && FD_ISSET(info->fd, &mux->read_fds)) {
info->revents |= EVENT_READ;
event_count++;
}
// 检查写事件
if ((info->events & EVENT_WRITE) && FD_ISSET(info->fd, &mux->write_fds)) {
info->revents |= EVENT_WRITE;
event_count++;
}
// 检查异常事件
if ((info->events & EVENT_ERROR) && FD_ISSET(info->fd, &mux->except_fds)) {
info->revents |= EVENT_ERROR;
event_count++;
}
// 更新活动时间
if (info->revents != 0) {
info->last_active = time(NULL);
}
}
mux->total_events += event_count;
return event_count;
}
// 检查socket是否有特定事件
int select_has_event(SelectMultiplexer* mux, SOCKET fd, int event_type) {
if (!mux) return 0;
for (int i = 0; i < MAX_SOCKETS; i++) {
if (mux->sockets[i] && mux->sockets[i]->fd == fd) {
return (mux->sockets[i]->revents & event_type) != 0;
}
}
return 0;
}
// 获取socket的返回事件
int select_get_revents(SelectMultiplexer* mux, SOCKET fd) {
if (!mux) return 0;
for (int i = 0; i < MAX_SOCKETS; i++) {
if (mux->sockets[i] && mux->sockets[i]->fd == fd) {
return mux->sockets[i]->revents;
}
}
return 0;
}
// 获取socket信息
SocketInfo* select_get_info(SelectMultiplexer* mux, SOCKET fd) {
if (!mux) return NULL;
for (int i = 0; i < MAX_SOCKETS; i++) {
if (mux->sockets[i] && mux->sockets[i]->fd == fd) {
return mux->sockets[i];
}
}
return NULL;
}
// ============ 统计和调试 ============
// 打印多路复用器状态
void select_dump_stats(SelectMultiplexer* mux) {
if (!mux) return;
long uptime = get_current_time() - (mux->start_time.time * 1000 + mux->start_time.millitm);
printf("\n=== Select Multiplexer Statistics ===\n");
printf("Uptime: %.2f seconds\n", uptime / 1000.0);
printf("Total polls: %ld\n", mux->total_polls);
printf("Total events: %ld\n", mux->total_events);
printf("Active sockets: %d\n", mux->socket_count);
printf("Max fd: %d\n", mux->max_fd);
printf("Events per poll: %.2f\n",
mux->total_polls > 0 ? (float)mux->total_events / mux->total_polls : 0);
printf("\n=== Socket Details ===\n");
printf("%-8s %-15s %-8s %-8s %-8s\n",
"Socket", "IP:Port", "Events", "Revents", "Inactive(s)");
for (int i = 0; i < MAX_SOCKETS; i++) {
SocketInfo* info = mux->sockets[i];
if (!info) continue;
char ip_port[32];
sprintf(ip_port, "%s:%d",
inet_ntoa(info->addr.sin_addr),
ntohs(info->addr.sin_port));
time_t inactive = time(NULL) - info->last_active;
printf("%-8d %-15s %-8x %-8x %-8ld\n",
info->fd,
ip_port,
info->events,
info->revents,
inactive);
}
printf("====================================\n\n");
}
// ============ 示例使用 ============
// 创建TCP服务器
SOCKET create_server(int port) {
WSADATA wsa;
if (WSAStartup(MAKEWORD(2,2), &wsa) != 0) {
return INVALID_SOCKET;
}
SOCKET server = socket(AF_INET, SOCK_STREAM, 0);
if (server == INVALID_SOCKET) {
return INVALID_SOCKET;
}
int opt = 1;
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(server, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
closesocket(server);
return INVALID_SOCKET;
}
if (listen(server, SOMAXCONN) == SOCKET_ERROR) {
closesocket(server);
return INVALID_SOCKET;
}
set_nonblocking(server);
return server;
}
// Echo服务器示例
void run_echo_server_with_select() {
printf("Starting echo server with select multiplexer...\n");
// 创建服务器
SOCKET server = create_server(8888);
if (server == INVALID_SOCKET) {
printf("Failed to create server\n");
return;
}
printf("Server listening on port 8888\n");
// 创建多路复用器
SelectMultiplexer* mux = select_create();
if (!mux) {
closesocket(server);
WSACleanup();
return;
}
// 添加服务器socket,监控读事件
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8888);
select_add(mux, server, &server_addr, EVENT_READ, NULL);
printf("Multiplexer initialized. Waiting for connections...\n");
printf("Press Ctrl+C to exit\n\n");
int running = 1;
long last_stat_time = get_current_time();
while (running) {
// 轮询事件,100ms超时
int events = select_poll(mux, 100);
if (events < 0) {
printf("select error: %d\n", -events);
break;
}
// 处理事件
for (int i = 0; i < MAX_SOCKETS; i++) {
SocketInfo* info = mux->sockets[i];
if (!info) continue;
SOCKET fd = info->fd;
int revents = info->revents;
if (revents == 0) continue;
// 服务器socket有读事件(新连接)
if (fd == server && (revents & EVENT_READ)) {
struct sockaddr_in client_addr;
int addr_len = sizeof(client_addr);
SOCKET client = accept(server, (struct sockaddr*)&client_addr, &addr_len);
if (client != INVALID_SOCKET) {
set_nonblocking(client);
printf("New client: %s:%d (socket: %d)\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
client);
// 添加客户端socket,监控读和错误事件
select_add(mux, client, &client_addr, EVENT_READ | EVENT_ERROR, NULL);
}
}
// 客户端socket有读事件
else if (revents & EVENT_READ) {
char buffer[BUFFER_SIZE];
int bytes = recv(fd, buffer, BUFFER_SIZE, 0);
if (bytes > 0) {
buffer[bytes] = '\0';
printf("Client %d: %s\n", fd, buffer);
// 修改为监控写事件(准备回写)
select_modify(mux, fd, EVENT_WRITE);
// 保存要发送的数据
SocketInfo* client_info = select_get_info(mux, fd);
if (client_info) {
memcpy(client_info->write_buffer, buffer, bytes);
client_info->write_len = bytes;
}
}
else if (bytes == 0) {
printf("Client %d disconnected\n", fd);
select_remove(mux, fd);
}
else {
if (WSAGetLastError() != WSAEWOULDBLOCK) {
printf("Client %d error: %d\n", fd, WSAGetLastError());
select_remove(mux, fd);
}
}
}
// 客户端socket有写事件
else if (revents & EVENT_WRITE) {
SocketInfo* client_info = select_get_info(mux, fd);
if (client_info && client_info->write_len > 0) {
int sent = send(fd, client_info->write_buffer, client_info->write_len, 0);
if (sent > 0) {
printf("Echoed %d bytes to client %d\n", sent, fd);
// 修改为监控读事件
select_modify(mux, fd, EVENT_READ);
client_info->write_len = 0;
}
}
}
// 客户端socket有错误事件
else if (revents & EVENT_ERROR) {
printf("Client %d has error, closing\n", fd);
select_remove(mux, fd);
}
// 清除已处理的事件
info->revents = 0;
}
// 定期打印统计信息(每10秒)
long now = get_current_time();
if (now - last_stat_time > 10000) {
select_dump_stats(mux);
last_stat_time = now;
}
}
// 清理
select_destroy(mux);
closesocket(server);
WSACleanup();
printf("Server stopped\n");
}
int main() {
run_echo_server_with_select();
return 0;
}