【C++基本语法】Linux IO多路复用select, poll, epoll特性对比-程序员宅基地

技术标签: c++  Linux  linux  

本篇介绍Linux三种IO复用接口,主要对比其特性,非接口使用和原理解析。

1. 对比

接口 优点和使用场景 缺点 底层实现/原因
select 1. 可移植性比epoll好
2.适用于并发场景较少或初学者使用
1. fd限制。监测的文件描述符有上限(1024),可以用FD_SETSIZE改变,但会增加开销
2. 需要频繁在用户态和内核态之间复制fd集合,fd多时效率低
3.每次调用都要遍历整个文件描述符合集
底层用fd数组,每次调用都需要将数组从用户态拷贝到内核态
poll 1. 无fd数量限制
1. 内核检查时仍需遍历,并发高时效率低
2. 频繁增删fd时,效率可能不如select
原理与select类似,用链表代替数组
epoll 1. 无fd数量限制
2. 仅在fd状态变化时通知应用,减少不必要的检查(O(1))
3. epoll_ctl注册fd后,不需要在每次调用时复制fd集合,内存拷贝开销小
4. 支持水平触发(LT)和边缘触发(ET)

适用于:需要同时处理大量并发连接的场景,如负载均衡器、代理服务器等
缺点说的人很少,缺陷有一篇文章参考Epoll的缺陷 底层使用红黑树+双向链表

2. select/poll 运行流程

这篇文章IO多路复用里的动图做的很好(现在它是我的了,hh)
select原理

3. Epoll优越性的原理(参考xiaolincoding)

select/poll 的问题主要是用线性结构去保存fd合集,因此内核检查时都需要遍历,复杂度为O(n),n较大时,效率下降明显。

epoll通过下面两点解决这个问题

  1. 使用红黑树保存进程所有待检测的fd。红黑树增删查的复杂度都是O(logn),所以每次操作时只需要传入待检测的socket,减少数据拷贝。
  2. 使用事件驱动机制,内核里使用一个链表来记录就绪事件。当事件发生时,通过回调将其加入该链表,当用户调用 epoll_wait() 函数时,只会返回有事件发生的fd的个数,无需轮询整个fd合集,极大提高效率。
    epoll原理

4. 边缘触发(edge-triggered,ET)和水平触发(level-triggered,LT)

水平触发LT- 只要fd就绪(可读或可写),如果可读,epoll_wait会持续醒来,直到所有数据被读完;(如果socket可写,应用程序写完后应移除EPOLLOUT事件,否则epoll_wait仍会触发)
边缘触发ET- 当发生可读事件时,epoll_wait只苏醒一次,即使应用程序没用从内核读出或者读完数据,后续也不会苏醒,所以要尽量一次读完缓冲区数据。

select, poll 只有LT模式,epoll默认也是水平触发LT,可以切换成ET。

注意点:

  • 使用LT模式,没必要一次读完,后续还会收到通知
  • 使用ET模式,应尽量配合非阻塞IO。因为ET模式下,事件发生后应用会尽量读写数据,如果fd是阻塞的,当读完时,进程会阻塞住。如果fd是非阻塞的,读写完后read和write返回错误EAGIN/EWOULDBLOCK.

5. 示例:用IO多路复用实现监测多个异步连接

#include <vector>
#include <sys/poll.h>

// ...

std::vector<int> clientfds; // 存储多个客户端套接字

// 循环创建多个非阻塞套接字并尝试连接
for (int i = 0; i < num_connections; ++i) {
    
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1) {
    
        // 错误处理...
        continue;
    }

    // 设置套接字为非阻塞模式
    // ...

    // 尝试连接服务器
    struct sockaddr_in serveraddr;
    // 初始化 serveraddr
    // ...

    if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) {
    
        if (errno == EINPROGRESS) {
    
            // 连接正在进行,添加到监测列表
            clientfds.push_back(clientfd);
        } else {
    
            // 真正的错误,关闭套接字
            close(clientfd);
            // 错误处理...
        }
    }
}

// 为所有非阻塞连接设置pollfd数组
std::vector<pollfd> pollfds;
for (int fd : clientfds) {
    
    pollfd event;
    event.fd = fd;
    event.events = POLLOUT;
    pollfds.push_back(event);
}

// 设置超时时间
int timeout = 3000;

// 使用poll监测所有连接
int ret = poll(pollfds.data(), pollfds.size(), timeout);

if (ret > 0) {
    
    for (size_t i = 0; i < pollfds.size(); ++i) {
    
        if (pollfds[i].revents & POLLOUT) {
    
            // 检查连接状态
            int err;
            socklen_t len = sizeof(err);
            if (getsockopt(pollfds[i].fd, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && !err) {
    
                std::cout << "Connection " << pollfds[i].fd << " established successfully." << std::endl;
            } else {
    
                std::cout << "Connection " << pollfds[i].fd << " failed." << std::endl;
            }
            close(pollfds[i].fd); // 关闭套接字
        } else if (pollfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
    
            // 发生错误,处理错误
            std::cout << "Connection " << pollfds[i].fd << " error occurred." << std::endl;
            close(pollfds[i].fd); // 关闭套接字
        }
    }
} else if (ret == 0) {
    
    // 超时
    std::cout << "Connection attempt timed out." << std::endl;
} else {
    
    // poll出错
    std::cout << "poll failed." << std::endl;
}

// ...

遗留问题

所有人都说poll和select几乎一样。poll的fd合集是链表,它存在于用户空间,调用时究竟是复制了整个fd合集,还是只传递了指针给内核,内核检查的时候不是直接遍历的用户空间的fd合集吗?

参考文档

【1】https://blog.csdn.net/qq_34827674/article/details/115619261
【2】https://blog.csdn.net/v123411739/article/details/124699602?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171405243016800213061387%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=171405243016800213061387&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-124699602-null-null.142v100pc_search_result_base4&utm_term=IO%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8&spm=1018.2226.3001.4187

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_40192753/article/details/138188099

智能推荐

初学HTML5——画布(canvas)_html canvas里加标题-程序员宅基地

文章浏览阅读3.2k次,点赞3次,收藏11次。一.canvas介绍canvas为画布, 使用canvas便可以轻松的在网页上绘制图形,文字,图片等。二.画布的使用1.创建画布:HTML5中提供了<canvas>标签,使用<canvas>标签可以在网页中创建一个矩形区域的画布:<canvas id="cavsElem" width=“400” height="300">你的浏览器不支持canva..._html canvas里加标题

牛客网剑指Offer_66道python(更新中)_牛客网剑指offer 66题 python-程序员宅基地

文章浏览阅读1k次。牛客网剑指Offer_编程题导语1 Fibonacci数列及其应用(考察:递归与循环)2 数组3 查找与排序4功能快捷键5合理的创建标题,有助于目录的生成6如何改变文本的样式7插入链接与图片8如何插入一段漂亮的代码片9生成一个适合你的列表10创建一个表格11设定内容居中、居左、居右12SmartyPants13创建一个自定义列表14如何创建一个注脚15注释也是必不可少的16KaTeX数学公式..._牛客网剑指offer 66题 python

计算机网络--基本命令使用_网络基本命令的使用-程序员宅基地

文章浏览阅读1.8k次,点赞3次,收藏14次。目录实验一:ipconfig实验二:ping实验三:tracert实验一:ipconfig ipconfig 是微软操作系统的计算机上用来控制网络连接的一个命令行工具。它的主要用来显示当前网络连接的配置信息(/all 参数)1、使用 ipconfig/all 查看自己计算机的网络配置IPv6地址:IPv4地址:子网掩码(Subnet Mask):网关(Gateway):2、使用 ipconfig/all 查看旁边计算机的网络配置,看看有什么异同。✎ 问题你的计算机和旁边的计算机是否_网络基本命令的使用

skywalking_skywalking重启-程序员宅基地

文章浏览阅读1.8k次。skywalking和agent_skywalking重启

RC5T620-1017电源管理IC规格书/datasheet_rc5t620 1017-程序员宅基地

文章浏览阅读665次。RC5T620-1017是用于GPS-PND/MID和智能电话的电源管理IC,它集成了五个高效降压DCDC转换器,十二个低压降稳压器,电源控制逻辑,锂离子电池充电器,I2C总线接口, 电压检测,热关断等。RC5T620结构图:特征:1.系统I2C总线接口@ 3.4MHz和400kHz检测器功能(系统/ IO /电池电压检测器,UVLO)热关断功能看门狗定时器打开按键输入以启动系统CPU的上电复位输出通过OTP灵活的开机/关机顺序通过OTP灵活的DCDCx和LDOx默认开/关控制_rc5t620 1017

喜马拉雅 xm文件转m4a_音频转文字这种刚需,我用python写了个软件,免费不限时...-程序员宅基地

文章浏览阅读837次。最新独立版本,bug更少,支持格式更多,欢迎体验。王华:音频转文字工具,完全免费,自己用Python写的!​zhuanlan.zhihu.com一、需求分析:1、音频转文字:目前市面上的音频转文字大多收费。音频转文字的需求是:上传一段音频,直接识别成文字,对于会议记录的比较好使,注意不是实时的语音识别。 2、文字转语音:转的语音不要太生硬。3、截图文字识别(OCR):截屏完成即可弹出截图所含图片..._xm转m4a

随便推点

MySQL 大表优化方案-程序员宅基地

文章浏览阅读92次。点击上方“Java基基”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏原创 | Java 2020超神之路,很肝~中文详细注释的开源项目...

android 9.0 开机动画,Android bootanim开机动画启动流程-程序员宅基地

文章浏览阅读870次。1. system进程在启动过程中会调用SurfaceFlinger类的静态成员函数instantiate来启动SurfaceFlinger服务。启动过程中,首先创建一个SurfaceFlinger实例,此实例会被一个SP 指针引用。当一个对象被一个智能指针第一次引用的时候,该类的onFirstRef方法将被调用:void SurfaceFlinger::onFirstRef(){mEventQu..._android bootnim

[转]quick-cocos2d-x 从2.2.5升级到3.3的体会-程序员宅基地

文章浏览阅读431次。原文地址:https://my.oschina.net/ffs/blog/390275从quick 2.2.5升级到quick 3.3 final版本,我发现改动还是很大的,但是github上的文档只涵盖了很少的一部分,因此我在这里把自己升级中的一些注意事项罗列出来,希望能有所帮助。 首先,我们可以看看github上的文档,主要有如下部分: 下列函

Java-接口和多态_error: constructor pingpangplayer in class pingpan-程序员宅基地

文章浏览阅读149次。接口接口Interface,接口和类的关系是实现,implementsInterface 接口名{}package com.it01;/* * java用于处理继承的单一局限性---接口Interface,接口和类的关系是实现,implements * * 创建接口的格式 * Interface 接口名{ * } */public class Inte..._error: constructor pingpangplayer in class pingpangplayer cannot be applied

Kotlin-简约之美-进阶篇(二):when的使用详解_kotlin duplicate label in when-程序员宅基地

文章浏览阅读1.4k次。提到 when,大家都会联想到 Java 中的 switch,然而在 kotlin 中,when 显然比 Java 中的 switch 要强大得多。首先,我们先来看看 when 的特点:它可以作为表达式使用使用更加安全强大灵活的分支结构可以不带参数接下来,我来带大家逐步领略这些特点。以下面这段 Java 功能代码为例:switch(animal) { case EAGLE:..._kotlin duplicate label in when

NVIDIA vGPU vApps/vWS/vCS适配GPU版本介绍-程序员宅基地

文章浏览阅读2.9k次。NVIDIA vGPU 12.0版本-vGPU版本名称变化 -注: 2021年1月生效最新名称 NVIDIA Virtual PC (vPC)-曾用名称 NVIDIA GRID Virtual PC (GRID vPC) 最新名称 NVIDIA Virtual Applications (vApps)-曾用名称 NVIDIA GRID Virtual Applications (GRID vApps) 最新名称 NVIDIA RTX Virtual Workstation (vWS)-曾用名...

推荐文章

热门文章

相关标签