17. IO多路复用.md 27 KB

IO多路复用

1. 为什么需要IO多路复用

1.1 传统IO模型的问题

  • 阻塞IO:进程在读写操作时会阻塞,无法处理其他fd
  • 多进程/多线程:每个连接一个进程/线程,资源消耗大
  • 非阻塞IO:需要轮询所有fd,CPU占用高

1.2 IO多路复用的优势

  • 单线程处理多个fd:减少线程/进程创建开销
  • 事件驱动:只处理有事件的fd,效率高
  • 资源节省:避免频繁的上下文切换

1.3 典型应用场景

  • Web服务器:处理大量并发连接
  • 数据库服务器:管理多个客户端连接
  • 聊天服务器:实时消息转发
  • 嵌入式系统:多传感器数据采集

2. select详解

2.1 函数原型

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

2.2 参数说明

nfds

  • 需要监视的文件描述符数量
  • 值为nfds + 1(因为fd从0开始)
  • 例如:最大fd为5,则nfds=6

fd_set类型

  • 文件描述符集合,本质上是位图
  • 最大支持FD_SETSIZE个fd(通常为1024)

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是否在集合中

timeout参数

struct timeval {
    long tv_sec;   // 秒
    long tv_usec;  // 微秒
};
  • NULL:永久阻塞,直到有事件发生
  • 0, 0:非阻塞,立即返回
  • >0:等待指定时间后返回

2.3 返回值

  • >0:有事件发生的fd数量
  • 0:超时
  • -1:出错(errno设置)

2.4 select使用示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUF_SIZE 1024
#define MAX_CLIENTS 10

int main() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len;
    fd_set read_fds, temp_fds;
    int max_fd, i, activity;
    char buffer[BUF_SIZE];
    int client_fds[MAX_CLIENTS];
    int client_count = 0;

    // 初始化客户端数组
    for (i = 0; i < MAX_CLIENTS; i++) {
        client_fds[i] = 0;
    }

    // 1. 创建服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. 绑定地址和端口
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 监听
    if (listen(server_fd, 5) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    // 4. 初始化fd_set
    FD_ZERO(&read_fds);
    FD_SET(server_fd, &read_fds);
    max_fd = server_fd;

    // 5. 主循环
    while (1) {
        temp_fds = read_fds;

        // select等待事件
        activity = select(max_fd + 1, &temp_fds, NULL, NULL, NULL);
        if (activity < 0) {
            perror("select error");
            continue;
        }

        // 检查服务器套接字是否有新连接
        if (FD_ISSET(server_fd, &temp_fds)) {
            client_len = sizeof(client_addr);
            client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
            if (client_fd < 0) {
                perror("accept failed");
                continue;
            }

            printf("New connection from %s:%d\n",
                   inet_ntoa(client_addr.sin_addr),
                   ntohs(client_addr.sin_port));

            // 添加到客户端数组
            for (i = 0; i < MAX_CLIENTS; i++) {
                if (client_fds[i] == 0) {
                    client_fds[i] = client_fd;
                    FD_SET(client_fd, &read_fds);
                    if (client_fd > max_fd) {
                        max_fd = client_fd;
                    }
                    break;
                }
            }
        }

        // 检查客户端数据
        for (i = 0; i < MAX_CLIENTS; i++) {
            if (client_fds[i] > 0 && FD_ISSET(client_fds[i], &temp_fds)) {
                int valread = read(client_fds[i], buffer, BUF_SIZE);
                if (valread == 0) {
                    // 客户端断开连接
                    printf("Client disconnected\n");
                    close(client_fds[i]);
                    FD_CLR(client_fds[i], &read_fds);
                    client_fds[i] = 0;
                } else {
                    // 回显数据
                    buffer[valread] = '\0';
                    printf("Received: %s", buffer);
                    send(client_fds[i], buffer, valread, 0);
                }
            }
        }
    }

    close(server_fd);
    return 0;
}

2.5 select的局限性

  1. fd数量限制FD_SETSIZE通常为1024
  2. 性能问题:每次调用需要拷贝fd_set到内核
  3. 线性扫描:需要遍历所有fd检查事件
  4. fd_set重建:每次调用都需要重新设置fd_set

3. poll详解

3.1 函数原型

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

3.2 pollfd结构体

struct pollfd {
    int   fd;         // 文件描述符
    short events;     // 请求的事件
    short revents;    // 返回的事件
};

3.3 事件类型

events(请求事件)

  • POLLIN:有数据可读
  • POLLOUT:可以写入数据
  • POLLERR:发生错误
  • POLLHUP:挂起
  • POLLPRI:有紧急数据

revents(返回事件)

  • 包含实际发生的事件
  • 可以检查POLLINPOLLOUT

3.4 poll使用示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUF_SIZE 1024
#define MAX_CLIENTS 10

int main() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len;
    struct pollfd fds[MAX_CLIENTS + 1];
    int nfds = 1;
    int timeout = 5000;  // 5秒超时
    char buffer[BUF_SIZE];
    int i, ret;

    // 1. 创建服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. 绑定地址和端口
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 监听
    if (listen(server_fd, 5) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    // 4. 初始化pollfd数组
    fds[0].fd = server_fd;
    fds[0].events = POLLIN;

    // 5. 主循环
    while (1) {
        ret = poll(fds, nfds, timeout);
        if (ret < 0) {
            perror("poll error");
            continue;
        }

        if (ret == 0) {
            printf("Poll timeout\n");
            continue;
        }

        // 检查服务器套接字
        if (fds[0].revents & POLLIN) {
            client_len = sizeof(client_addr);
            client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
            if (client_fd < 0) {
                perror("accept failed");
                continue;
            }

            printf("New connection from %s:%d\n",
                   inet_ntoa(client_addr.sin_addr),
                   ntohs(client_addr.sin_port));

            // 添加到pollfd数组
            if (nfds < MAX_CLIENTS + 1) {
                fds[nfds].fd = client_fd;
                fds[nfds].events = POLLIN;
                nfds++;
            } else {
                printf("Max clients reached\n");
                close(client_fd);
            }
        }

        // 检查客户端数据
        for (i = 1; i < nfds; i++) {
            if (fds[i].revents & POLLIN) {
                int valread = read(fds[i].fd, buffer, BUF_SIZE);
                if (valread == 0) {
                    // 客户端断开连接
                    printf("Client disconnected\n");
                    close(fds[i].fd);
                    // 移除客户端
                    fds[i] = fds[nfds - 1];
                    nfds--;
                    i--;
                } else {
                    // 回显数据
                    buffer[valread] = '\0';
                    printf("Received: %s", buffer);
                    send(fds[i].fd, buffer, valread, 0);
                }
            }
        }
    }

    close(server_fd);
    return 0;
}

3.5 poll的优势

  1. 无fd数量限制:使用动态数组
  2. 结构更清晰:events和revents分离
  3. 不需要每次重建:直接修改events字段

4. epoll详解

4.1 核心API

epoll_create

#include <sys/epoll.h>

int epoll_create(int size);
  • 创建epoll实例
  • size:建议的大小(内核会动态调整)
  • 返回epoll文件描述符

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • epfd:epoll实例文件描述符
  • op:操作类型
    • EPOLL_CTL_ADD:添加fd
    • EPOLL_CTL_MOD:修改fd
    • EPOLL_CTL_DEL:删除fd
  • fd:目标文件描述符
  • event:事件结构体

epoll_wait

int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);
  • events:输出参数,存储发生的事件
  • maxevents:最大事件数
  • timeout:超时时间(毫秒)

4.2 epoll_event结构体

struct epoll_event {
    uint32_t events;   // 事件类型
    epoll_data_t data; // 用户数据
};

typedef union epoll_data {
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

4.3 事件类型

  • EPOLLIN:有数据可读
  • EPOLLOUT:可以写入数据
  • EPOLLERR:发生错误
  • EPOLLHUP:挂起
  • EPOLLRDHUP:对端关闭连接
  • EPOLLET:边缘触发模式

4.4 epoll使用示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUF_SIZE 1024
#define MAX_EVENTS 10

int main() {
    int server_fd, client_fd, epoll_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len;
    struct epoll_event event, events[MAX_EVENTS];
    char buffer[BUF_SIZE];
    int i, nfds;

    // 1. 创建服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. 绑定地址和端口
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 监听
    if (listen(server_fd, 5) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    // 4. 创建epoll实例
    epoll_fd = epoll_create1(0);
    if (epoll_fd < 0) {
        perror("epoll_create1 failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 5. 添加服务器套接字到epoll
    event.events = EPOLLIN;
    event.data.fd = server_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) {
        perror("epoll_ctl failed");
        close(epoll_fd);
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 6. 主循环
    while (1) {
        nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds < 0) {
            perror("epoll_wait error");
            continue;
        }

        for (i = 0; i < nfds; i++) {
            if (events[i].data.fd == server_fd) {
                // 新连接
                client_len = sizeof(client_addr);
                client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
                if (client_fd < 0) {
                    perror("accept failed");
                    continue;
                }

                printf("New connection from %s:%d\n",
                       inet_ntoa(client_addr.sin_addr),
                       ntohs(client_addr.sin_port));

                // 添加客户端到epoll
                event.events = EPOLLIN;
                event.data.fd = client_fd;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) < 0) {
                    perror("epoll_ctl failed");
                    close(client_fd);
                }
            } else {
                // 客户端数据
                int valread = read(events[i].data.fd, buffer, BUF_SIZE);
                if (valread == 0) {
                    // 客户端断开连接
                    printf("Client disconnected\n");
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                    close(events[i].data.fd);
                } else {
                    // 回显数据
                    buffer[valread] = '\0';
                    printf("Received: %s", buffer);
                    send(events[i].data.fd, buffer, valread, 0);
                }
            }
        }
    }

    close(epoll_fd);
    close(server_fd);
    return 0;
}

4.5 LT(水平触发)vs ET(边缘触发)

LT模式(默认)

  • 只要fd可读/可写,就会持续通知
  • 编程简单,不容易丢失事件
  • 可能导致重复通知

ET模式

  • 只在状态变化时通知一次
  • 性能更高,但编程复杂
  • 必须一次性读完所有数据

ET模式要求

  1. 必须使用非阻塞fd
  2. 必须一次性读完所有数据
  3. 使用EAGAIN判断是否读完

4.6 ET模式示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUF_SIZE 1024

// 设置非阻塞模式
int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int main() {
    int server_fd, client_fd, epoll_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len;
    struct epoll_event event, events[10];
    char buffer[BUF_SIZE];
    int i, nfds, n;

    // 1. 创建服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置非阻塞
    set_nonblocking(server_fd);

    // 2. 绑定地址和端口
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 监听
    if (listen(server_fd, 5) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    // 4. 创建epoll实例
    epoll_fd = epoll_create1(0);
    if (epoll_fd < 0) {
        perror("epoll_create1 failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 5. 添加服务器套接字到epoll(边缘触发)
    event.events = EPOLLIN | EPOLLET;
    event.data.fd = server_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) {
        perror("epoll_ctl failed");
        close(epoll_fd);
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 6. 主循环
    while (1) {
        nfds = epoll_wait(epoll_fd, events, 10, -1);
        if (nfds < 0) {
            perror("epoll_wait error");
            continue;
        }

        for (i = 0; i < nfds; i++) {
            if (events[i].data.fd == server_fd) {
                // 新连接
                while (1) {
                    client_len = sizeof(client_addr);
                    client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
                    if (client_fd < 0) {
                        if (errno == EAGAIN || errno == EWOULDBLOCK) {
                            break;  // 没有更多连接
                        }
                        perror("accept failed");
                        break;
                    }

                    printf("New connection from %s:%d\n",
                           inet_ntoa(client_addr.sin_addr),
                           ntohs(client_addr.sin_port));

                    // 设置非阻塞
                    set_nonblocking(client_fd);

                    // 添加客户端到epoll(边缘触发)
                    event.events = EPOLLIN | EPOLLET;
                    event.data.fd = client_fd;
                    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) < 0) {
                        perror("epoll_ctl failed");
                        close(client_fd);
                    }
                }
            } else {
                // 客户端数据(边缘触发,必须一次性读完)
                while (1) {
                    n = read(events[i].data.fd, buffer, BUF_SIZE);
                    if (n == 0) {
                        // 客户端断开连接
                        printf("Client disconnected\n");
                        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                        close(events[i].data.fd);
                        break;
                    } else if (n < 0) {
                        if (errno == EAGAIN || errno == EWOULDBLOCK) {
                            break;  // 没有更多数据
                        }
                        perror("read error");
                        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                        close(events[i].data.fd);
                        break;
                    } else {
                        // 回显数据
                        buffer[n] = '\0';
                        printf("Received: %s", buffer);
                        send(events[i].data.fd, buffer, n, 0);
                    }
                }
            }
        }
    }

    close(epoll_fd);
    close(server_fd);
    return 0;
}

5. select vs poll vs epoll对比表

特性 select poll epoll
fd数量限制 1024(FD_SETSIZE) 无限制 无限制
数据结构 fd_set(位图) pollfd数组 红黑树+就绪链表
触发方式 水平触发 水平触发 支持边缘触发
性能 O(n) O(n) O(1)
内核实现 遍历所有fd 遍历所有fd 回调机制
内存拷贝 每次拷贝fd_set 每次拷贝pollfd 仅首次添加
适用场景 少量fd 中等数量fd 大量fd
跨平台 支持 支持 仅Linux

6. 嵌入式场景

6.1 多路传感器数据采集

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <fcntl.h>

#define SENSOR_COUNT 4

int main() {
    int fds[SENSOR_COUNT];
    fd_set read_fds;
    int max_fd = 0;
    int i, activity;
    char buffer[1024];

    // 打开传感器设备
    for (i = 0; i < SENSOR_COUNT; i++) {
        char device[64];
        snprintf(device, sizeof(device), "/dev/sensor%d", i);
        fds[i] = open(device, O_RDONLY | O_NONBLOCK);
        if (fds[i] < 0) {
            perror("open sensor failed");
            continue;
        }
        if (fds[i] > max_fd) {
            max_fd = fds[i];
        }
    }

    // 主循环
    while (1) {
        FD_ZERO(&read_fds);
        for (i = 0; i < SENSOR_COUNT; i++) {
            if (fds[i] > 0) {
                FD_SET(fds[i], &read_fds);
            }
        }

        activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
        if (activity < 0) {
            perror("select error");
            continue;
        }

        for (i = 0; i < SENSOR_COUNT; i++) {
            if (fds[i] > 0 && FD_ISSET(fds[i], &read_fds)) {
                int n = read(fds[i], buffer, sizeof(buffer));
                if (n > 0) {
                    printf("Sensor %d data: %.*s\n", i, n, buffer);
                }
            }
        }
    }

    // 关闭传感器
    for (i = 0; i < SENSOR_COUNT; i++) {
        if (fds[i] > 0) {
            close(fds[i]);
        }
    }

    return 0;
}

6.2 多客户端通信

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define PORT 8888
#define MAX_EVENTS 10
#define BUF_SIZE 1024

int main() {
    int server_fd, client_fd, epoll_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len;
    struct epoll_event event, events[MAX_EVENTS];
    char buffer[BUF_SIZE];
    int i, nfds;

    // 创建服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 绑定地址和端口
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 监听
    if (listen(server_fd, 5) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    // 创建epoll实例
    epoll_fd = epoll_create1(0);
    if (epoll_fd < 0) {
        perror("epoll_create1 failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 添加服务器套接字到epoll
    event.events = EPOLLIN;
    event.data.fd = server_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) {
        perror("epoll_ctl failed");
        close(epoll_fd);
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 主循环
    while (1) {
        nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds < 0) {
            perror("epoll_wait error");
            continue;
        }

        for (i = 0; i < nfds; i++) {
            if (events[i].data.fd == server_fd) {
                // 新连接
                client_len = sizeof(client_addr);
                client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
                if (client_fd < 0) {
                    perror("accept failed");
                    continue;
                }

                printf("New connection from %s:%d\n",
                       inet_ntoa(client_addr.sin_addr),
                       ntohs(client_addr.sin_port));

                // 添加客户端到epoll
                event.events = EPOLLIN;
                event.data.fd = client_fd;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) < 0) {
                    perror("epoll_ctl failed");
                    close(client_fd);
                }
            } else {
                // 客户端数据
                int valread = read(events[i].data.fd, buffer, BUF_SIZE);
                if (valread == 0) {
                    // 客户端断开连接
                    printf("Client disconnected\n");
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                    close(events[i].data.fd);
                } else {
                    // 处理数据
                    buffer[valread] = '\0';
                    printf("Received from client %d: %s", events[i].data.fd, buffer);

                    // 广播给其他客户端
                    for (int j = 0; j < nfds; j++) {
                        if (events[j].data.fd != server_fd &&
                            events[j].data.fd != events[i].data.fd) {
                            send(events[j].data.fd, buffer, valread, 0);
                        }
                    }
                }
            }
        }
    }

    close(epoll_fd);
    close(server_fd);
    return 0;
}

7. 注意事项

7.1 性能考虑

  • epoll性能最好:适合大量fd的场景
  • select/poll适合少量fd:简单场景使用
  • 边缘触发需要非阻塞fd:避免阻塞导致事件丢失
  • 合理设置超时:避免无意义的等待

7.2 编程要点

  • select需要每次重建fd_set:注意FD_ZERO和FD_SET
  • poll需要管理pollfd数组:动态添加/删除fd
  • epoll需要管理epoll_event:正确设置events和data
  • 处理EAGAIN:非阻塞模式下必须处理

7.3 常见错误

  • fd数量超过限制:select的FD_SETSIZE限制
  • 忘记字节序转换:端口和IP地址需要转换
  • 未处理部分读写:需要循环读写直到完成
  • 内存泄漏:忘记关闭fd

8. 面试要点

8.1 常见问题

  1. select、poll、epoll的区别是什么?

    • fd数量限制、性能、实现机制、适用场景
  2. epoll的LT和ET模式有什么区别?

    • 通知机制、性能、编程复杂度、使用要求
  3. 为什么epoll性能更好?

    • 红黑树+就绪链表、回调机制、避免遍历
  4. 如何处理部分读写?

    • 循环读写、处理EAGAIN、使用非阻塞fd
  5. IO多路复用的适用场景有哪些?

    • Web服务器、数据库、聊天服务器、嵌入式系统

8.2 编程要点

  • 记住三种IO多路复用的函数原型和参数
  • 理解fd_set、pollfd、epoll_event的结构
  • 掌握select/poll/epoll的使用流程
  • 理解LT和ET模式的区别和要求
  • 能够处理常见的错误情况

8.3 性能优化

  • 根据场景选择合适的IO多路复用方式
  • 合理设置超时时间,避免无意义等待
  • 使用边缘触发提高性能(需要非阻塞fd)
  • 批量处理事件,减少系统调用次数