select详细分析

1.select :基于位图 实现,监听文件描述符有最大数量限制(默认 1024),由内核参数FD_SETSIZE控制,无法突破。

2.在IO多路复用中,位图(Bitmap) 是一种用二进制位(bit)表示状态的数据结构,每个位对应一个元素,0表示"否"(不监听或未就绪),1表示"是"(监听或就绪)。其核心优势在于空间效率高 (每个文件描述符仅需1位存储)和操作高效(支持快速的位运算)。

位图在select中的实现原理

  1. 数据结构定义
    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_set1024 / 64 = 16long存储,共1024位。
    • 每个位对应一个文件描述符(fd),例如第3位为1表示监听fd=3。
  2. 位图操作宏
    Linux提供以下宏操作位图:

    • FD_ZERO(&fdset):清空位图(所有位设为0)。
    • FD_SET(fd, &fdset):将fd对应的位置1(开始监听)。
    • FD_CLR(fd, &fdset):将fd对应的位置0(停止监听)。
    • FD_ISSET(fd, &fdset):检查fd对应的位是否为1(是否就绪)。
  3. select的位图监听流程
    select通过三个位图(readfdswritefdsexceptfds)分别监听文件描述符的读、写、异常事件,流程如下:

    • 用户态初始化
      用户程序将需要监听的fd通过FD_SET注册到三个位图中,并调用select()系统调用。
    • 内核态处理
      • 内核将用户空间的位图拷贝到内核空间(copy_from_user)。
      • 遍历位图中所有设置为1的fd,调用其poll()方法检查状态:
        • 读就绪:接收缓冲区有数据或连接关闭(FIN包)。
        • 写就绪:发送缓冲区有足够空间。
        • 异常:连接重置(RST包)或带外数据(urgent data)。
      • 将就绪的fd保留在结果位图中,未就绪的fd置0。
    • 返回用户态
      • 内核将结果位图拷贝回用户空间(copy_to_user)。
      • 用户程序通过FD_ISSET检查哪些fd就绪,并处理事件。

select的位图限制与性能瓶颈

  1. 文件描述符数量限制
    • 默认最多监听1024个fd(受__FD_SETSIZE限制),可通过修改内核参数调整,但需重新编译内核。
    • 每个fd_set占用128字节(16×8字节),三个位图共384字节。
  2. 性能问题
    • 内存拷贝开销 :每次调用select需两次用户态与内核态之间的位图拷贝(copy_from_usercopy_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;
}
相关推荐
网创联盟,知识导航2 小时前
沐雨云香港大宽带云服务器 · 配置全览
服务器·阿里云·腾讯云
fantasy5_53 小时前
Linux 动态进度条实战:从零掌握开发工具与核心原理
linux·运维·服务器
不瘦80斤不改名3 小时前
Python 日志(logging)全解析
服务器·python·php
莫逸风3 小时前
【局域网服务方案】:无需找运营商,低成本拥有高性能服务器
运维·服务器
小李独爱秋4 小时前
计算机网络经典问题透视:常规密钥体制与公钥体制最主要的区别是什么?—— 一文带你从“钥匙”看懂现代密码学核心
服务器·网络·tcp/ip·计算机网络·密码学
Amy_au5 小时前
Linux week 01
linux·运维·服务器
淮上安子骞5 小时前
sage10.8源码部署
服务器·密码学·ctf·本地部署·sage
KingRumn5 小时前
DBUS源码剖析之DBusMessage数据结构
linux·服务器·数据结构
OpenMiniServer5 小时前
JsonKV协议技术文档
linux·服务器·网络