select、poll、epoll三者的优缺点

1.select函数

函数定义

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

优点:

用户可以在一个线程内同时处理多个 socket 的 IO 请求。用户可以注册多个 socket,然后调用 select 函数读取被激活的 socket,从而实现在同一个线程内同时处理多个 IO 请求,在这点上select 函数与同步阻塞模型不同,因为在同步阻塞模型中需要通过多线程才能达到这个目的。

缺点:

  1. 每次调用 select 都需要将进程加入到所有监视 fd 的等待队列,每次唤醒都需要从每个队列中移除。 这里涉及了两次遍历,而且每次都要将整个 fd_set 列表传递给内核,有一定的开销。

  2. 当函数返回时,系统会将就绪描述符写入 fd_set 中,并将其拷贝到用户空间。进程被唤醒后,用户线程并不知道哪些 fd 收到数据,还需要遍历一次。

  3. 受 fd_set 的大小限制,32 位系统最多能监听 1024 个 fd,64 位最多监听 2048 个。

2.poll函数

函数定义

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
struct pollfd{
    int fd;        // 感兴趣fd
    short events;  // 监听事件
    short revents; // 就绪事件
};
*/
// return:表示此时有多少个监控的描述符就绪,若超时则为0,出错为-1。

poll 函数与 select 原理相似,都需要来回拷贝全部监听的文件描述符,不同的是

  1. 1)poll 函数采用链表的方式

### 性能功能差异分析 #### 文件描述符数量限制 `select` 机制存在文件描述符数量的硬性限制,通常为 1024,这在处理大量连接时成为瓶颈。相比之下,`poll` `epoll` 没有明确的文件描述符数量限制,能够更好地适应大规模并发连接的需求[^1]。 #### 性能表现 当文件描述符的数量增加时,`poll` 的性能随着描述符集合的增大而显著下降,每秒钟内的系统调用数目迅速减少。相反,`epoll` 的性能基本保持稳定,展现出良好的扩展性。这是因为 `epoll` 在内核中维护了一个事件表,无需像 `select` `poll` 那样每次调用时都重新传输文件描述符集事件集[^1]。 #### 内存拷贝效率 `select` `poll` 每次调用都需要将文件描述符集合从用户空间复制到内核空间,这在大量连接的情况下会造成较大的开销。而 `epoll` 只在适当的时候调用 `EPOLL_CTL_ADD` 将文件描述符结构拷贝到内核中,这一操作并不频繁,从而减少了内存拷贝带来的性能损耗[^3]。 #### 事件处理机制 `epoll` 使用回调函数的方式,将就绪的文件描述符结构加入到就绪队列中,`epoll_wait` 返回时可以直接访问就绪队列来获取哪些文件描述符已经就绪,其时间复杂度为 O(1)。这种机制避免了 `select` `poll` 中必须遍历整个文件描述符集合来查找就绪描述符的缺点[^3]。 #### API 使用复杂度 `epoll` 提供了三个主要的 API:`epoll_create` 用于创建一个 `epoll` 文件描述符;`epoll_ctl` 用于注册、修改或删除文件描述符的监听事件;`epoll_wait` 用于等待文件描述符上的事件发生。这些 API 的设计使得 `epoll` 更加灵活高效,但也增加了使用的复杂度[^2]。 ### 示例代码 以下是使用 `epoll` 的简单示例: ```c #include <sys/epoll.h> #include <unistd.h> #include <stdio.h> #define MAX_EVENTS 10 #define PORT 8080 int main() { int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1"); return 1; } struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = INADDR_ANY; int server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == -1) { perror("socket"); return 1; } if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { perror("bind"); return 1; } if (listen(server_fd, SOMAXCONN) == -1) { perror("listen"); return 1; } struct epoll_event event; event.events = EPOLLIN; event.data.fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) { perror("epoll_ctl: server_fd"); return 1; } struct epoll_event events[MAX_EVENTS]; while (1) { int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (num_events == -1) { perror("epoll_wait"); return 1; } for (int i = 0; i < num_events; ++i) { if (events[i].data.fd == server_fd) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len); if (client_fd == -1) { perror("accept"); continue; } event.data.fd = client_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) { perror("epoll_ctl: client_fd"); close(client_fd); } } else { // Handle client data char buffer[1024]; ssize_t bytes_read = read(events[i].data.fd, buffer, sizeof(buffer)); if (bytes_read <= 0) { close(events[i].data.fd); } else { write(events[i].data.fd, buffer, bytes_read); close(events[i].data.fd); } } } } close(server_fd); return 0; } ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值