select,poll,epoll的区别 各自支持的最大fd数上限以及原因

本文介绍了select、poll和epoll的区别,包括它们各自支持的最大文件描述符(fd)数上限。select默认上限为1024,可通过修改FD_SETSIZE并重新编译增加;poll和epoll无明确上限。在性能方面,epoll优于select和poll,因为它使用边缘触发和水平触发模式,并且只返回已准备好的事件,减少了扫描的时间开销。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

各自支持的最大fd数上限以及原因

select支持的fd数是固定的,默认一般为1024,用一个常数FD_SETSIZE定义,如果要monitor更多的fd,需要修改这个常数,并且重新编译(?),一般如果要monitor超过1024的fd,推荐使用poll/epoll

poll和epoll,因为他们monitor的fd list是通过数组或者说链表来保存,所以没有数量限制

区别

select

select是三者中最早被使用的多路复用机制,因此可移植性最好

通过给定三个fd set(bit mask),分别对应关注的是否可读,可写,是否出现异常三种类别,在调用select的时候,这三个集合被传给内核,内核逐一检测这些fd是否ready,然后把三个集合中ready的fd对应的bit置为1,返回

然后用户再逐一检测set中的fd是否处于ready,如果是,就对对应的fd执行相应的操作

注意到,在上面的描述中,用户始终需要扫描整个集合,也就是说扫描的个数和io事件的个数无关,而是始终是FD_SETSIZE,为了提高效率,我们可以使用一个maxfd,这样可以只检测maxfd以下的,从而扫描的个数正比于监听的fd个数,而不是始终是固定的FD_SETSIZE

缺点:

1.每次调用select都需要把三个集合传给内核,然后内核又把修改过的三个集合传回来,耗费时间

2.扫描时耗时与monitor的fd个数成正比,所以如果监听的个数很多,就会很慢

3.监听个数有上限

poll

基本和select相同,优点是监听的fd没有数量限制(因为poll不是用bit mask保存关注的fd,而是用pollfd 结构体数组

epoll

首先他和poll一样,监听的fd数没有限制(或者说只受硬件配置限制

为什么epoll的性能更好:

1.每次有io event让fd编程ready,内核都把该ready fd加入到epoll ready list,执行wait的时候直接从ready list里取而select每次调用都要扫描所有的fd

2.每次调用select/poll,都把三个set传给内核,然后内核把ready的用三个set传回来而epoll,使用epoll_ctl在内核里创建一个数据结构,之后epoll_wait时不需要传什么数据

返回的时候,也只返回ready的(事实上因为返回时我们也必须检查所有的位,判断是否ready,而epoll只返回ready的,所以还是有区别的

3.每次调用select之前,都必须初始化三个集合,还有返回之后必须检查所有的位(不过貌似这些是小头

我们可以说select/poll的耗时和N(fd数)呈线性关系,epoll的耗时和发生的io事件呈线性关系

所以epoll十分实用下面这种情况:monitor大量的fd,但是其中大部分是空闲的(因此io事件相对较少

此外epoll还提供edge trigger和level trigger两种形式的通知,而select/poll 都只支持level trigger

这两种的区别在于:

level trigger:只要fd处于ready(可读可写),就会报告给用户

edge trigger:仅当上一次调用epoll_wait之后有新的io事件,才报告给用户(如果是之前没有使用epoll_wait,那么就看创建fd之后有没有新的io事件)

举例说明edge trigger和level trigger的区别

假设现在一个fd处于ready,可读,我们执行一次epoll wait,此时一定是ready,不管采用level trigger还是edge trigger

再执行一次,如果是level trigger,那么仍然报告ready

但如果是edge trigger,那么就不会报告ready,因为从上次调用epoll wait以来,没有发生io事件

### 三种网络IO模型的对比 #### Select `select` 是一种早期的多路复用机制,用于监控多个文件描述符的状态变化。它通过一个组来管理这些文件描述符,并允许程序一次性查询它们是否有据可读或写入是否可用。然而,它的性能存在瓶颈,因为每次调用 `select` 都会重新复制整个文件描述符集合到内核空间并逐一检查其状态[^1]。 优点: - 支持多种平台。 缺点: - 文件描述符的量受限于系统定义的最大值(通常较小)。 - 效率低下,尤其是当监听大量连接时,因为它需要遍历所有句柄以找到已准备好的那些。 - 不适合大规模并发环境下的高性能需求。 ```c fd_set readfds; FD_ZERO(&readfds); FD_SET(fd, &readfds); if (select(max_fd + 1, &readfds, NULL, NULL, timeout)) { if(FD_ISSET(fd, &readfds)){ // 处理I/O事件... } } ``` #### Poll 相比 `select`, `poll` 解除了最大文件描述符量限制的问题,但它仍然继承了类似的缺陷——即每次调用都需要传递完整的列表给内核去扫描每一个条目是否存在活动情况[^1]。 结构体形式使得它可以容纳更多的项而不受传统位掩码大小的影响: ```c struct pollfd fds[n]; for(i=0;i<n;i++) { fds[i].fd = some_file_descriptor(); fds[i].events = POLLIN | POLLOUT; } ret = poll(fds, n, timeout_ms); foreach i from 0 to ret{ process_event(fds[i]); } ``` 尽管如此,由于缺乏内部优化设计如红黑树索引等技术手段的支持,在面对极高频率访问请求或者超大容量链接池的情况下表现依旧不够理想。 #### Epoll 作为 Linux 特有的改进版本,`epoll` 提供了一套更加灵活高效的解决方案。主要体现在以下几个方面: - **分离操作**: 将注册感兴趣的事件与实际等待发生分开执行(`epoll_ctl()` 和 `epoll_wait()`) ,从而减少了不必要的开销; - **无上限约束**: 关心的文件描述符目理论上仅取决于系统的内存资源而非固定的值界限; - **高效的据结构运用**: 利用了诸如红黑树这样的高级算法加快查找速度的同时还借助双向链表存储就绪队列以便快速定位目标对象[^3]。 下面展示了一个简单的例子说明如何创建 epoll 实例并向其中添加新的 socket 连接以及轮询发生的 I/O 条件变更: ```c int efd = epoll_create1(0); struct epoll_event ev, events[MAX_EVENTS]; ev.events = EPOLLIN | EPOLLET; ev.data.fd = listen_sock; epoll_ctl(efd, EPOLL_CTL_ADD, listen_sock, &ev); while(true){ nfds = epoll_wait(efd, events, MAX_EVENTS, -1); for(n = 0; n < nfds; ++n){ handle_io(events[n].data.fd); } } ``` 综上所述,对于不同规模的应用场景可以选择合适的工具集来满足特定的要求。如果只是少量短时间内的交互,则可能 select 或者 basic blocking io 已经足够胜任;而对于大型服务器架构而言,采用 epoll 才能充分发挥硬件潜力达到最佳吞吐量效果。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值