本文主要是介绍C++ I/O多路复用 select / poll / epoll,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
I/O多路复用:在网络I/O中,用 1个或1组线程 管理 多个连接描述符。
如果有至少一个描述符准备就绪,就处理对应的事件
如果没有,就会被阻塞,让出CPU给其他应用程序运行,直到有准备就绪的描述符 或 超时
(当超时时间设置为 -1 时,表示永不超时)
I/O多路复用的优势
I/O多路复用的优势如下所示:
-
高效利用资源: 通过在单一线程中管理多个 I/O 操作,减少了线程创建和上下文切换的开销。
-
减少阻塞: 允许程序在等待 I/O 操作时继续处理其他任务,提高了应用的响应能力和吞吐量。
-
降低系统负担: 减少了对系统调用的频繁使用,尤其在处理大量连接时,性能优于传统的多线程或多进程模型。
-
简化编程模型: 使得程序可以以事件驱动的方式处理多个 I/O 操作,从而简化了复杂的并发编程模型。
一、select模式
int select(int nfds, //最大描述符值fd_set *readfds, //读fd_set *writefds, //写fd_set *exceptfds, //异常struct timeval *timeout); //超时时间
select支持Window和linux系统。他的流程如下:
-
设置文件描述符集: 程序通过
FD_SET
宏将感兴趣的文件描述符添加到读、写或异常事件的集合中。(默认会创建三种内置描述符:
0描述符:输入流
1描述符:输出流
2描述符:错误流 )
-
调用
select
函数: 程序调用select
,传入最大文件描述符值、读、写和异常文件描述符集以及超时时间。 -
阻塞等待:
select
会被阻塞,直到至少有一个文件描述符准备好进行 I/O 操作,或直到超时。 -
检查文件描述符状态:
select
返回后,程序通过FD_ISSET
宏检查哪些文件描述符的状态发生了变化。 -
处理事件: 根据
FD_ISSET
返回的结果,程序可以处理那些已经准备好的文件描述符。
(这里用可读事件举例子:
<1> 如果,接收到的描述符 == 服务器描述符,说明 服务器存在可读事件
也就是说,此时有客户端发出了新的连接,需建立一个新的连接
然后将新的客户端的文件描述符添加到集合中,并更新最大文件描述符值
<2> 如果,接收到的描述符 == 客户端描述符,说明 客户端有新消息
此时读出消息,再根据内容向客户端回传信息。
如果读出错误则关闭这条连接。 )
-
重置文件描述符集: 如果
select
被重新调用,文件描述符集必须在每次调用前重新设置
1.1 优缺点
优点:select方式是兼容性最广的,支持Windows和Linux
缺点:每次调用select函数,会将整个FD_SET(维护所有文件描述符的数据结构)
从 用户态 的堆 复制到 内核态的堆,以供内核态检查。系统开销大。
二、poll模式
//poll函数返回的是 “发生事件的文件描述符数量”
int poll(struct pollfd *fds, //描述符数组的指针nfds_t nfds, //数组中描述符数量int timeout); //超时时间struct pollfd {int fd; // 文件描述符short events; // 需要监视的事件类型short revents; // 实际发生的事件
};
2.1 监视事件类型
对于每个文件描述符,设置所需监视的事件类型events(如读、写或异常事件)。这些事件类型可以是:
POLLIN
:表示可以读取数据。
POLLOUT
:表示可以写入数据。
POLLERR
:表示发生了错误。
POLLHUP
:表示挂起(比如管道关闭)
POLLNVAL
:表示无效的文件描述符
2.2 检查真实发生事件
上述五种类型,本质上是五种宏定义,其实是各自取一位为1,其余为0。(比如,POLLIN为0x1,POLLOUT为0x4)
因此,当我们需要判断一个真实发生事件的类型是否是读取类型,可以通过以下语句:
if(fd[i].revents & POLLIN) {//处理读取事件
}
三、epoll模式
epoll模式又叫做event poll模式,它与select不同的是:
select返回的是一系列文件描述符,至于哪些要做哪种事件,需要我们自己轮询
epoll会通过回调函数告知我们一个有变化的描述符,并且告诉我们需要完成哪种事件
这篇关于C++ I/O多路复用 select / poll / epoll的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!