IO多路复用
概述
- 文件描述符
- IO多路复用
- select
- poll
- epoll
文件描述符
文件描述符
服务器端有两种文件描述符:负责监听的文件描述符,负责通信的文件描述符:
监听文件描述符:
- Read Buffer:存储客户端的连接请求 -> accept()
- Write Buffer:当服务器接收到客户端的连接请求时,需要对其进行回应
通信文件描述符:
- Read Buffer:存储客户端发送的通信数据 -> read()
- Write Buffer:存储要给客户端发送的数据 -> write()
accept(), read(), write()三个函数都是阻塞函数,以accept()为例:
- 当Read Buffer内有数据时即有连接请求时,解除阻塞,建立连接;
- 当Read Buffer内没有数据时,则一直阻塞;
在单线程下,是不能同时处理这三个函数的,如果有一个函数发生阻塞,另外两个也只能阻塞。解决办法:
- 多线程
- 多进程
- IO多路复用
IO多路复用
使用IO多路复用函数委托内核检测服务器端所有的文件描述符(通信和监听两类),这个检测过程会导致进程/线程阻塞,如果检测到已就绪的文件描述符就解除阻塞,并将这些已就绪的文件描述符传出。相当于本来有三个地方会阻塞,现在把三个集合到一起进行检测,从而只有一个地方会阻塞,这样在阻塞期间可以同时对三者进行检测。
处理流程:
- 用IO多路复用函数委托内核检测服务器端所有的文件描述符
- 根据类型对传出的所有已就绪文件描述符进行判断,并做出不同的处理
- 监听的文件描述符:和客户端建立连接
- 此时调用accept()是不会导致程序阻塞的,因为IO多路复用函数已经检测过了,确定此时监听的文件描述符是就绪的即有新的请求
- 通信的文件描述符:调用通信函数和已建立连接的客户端通信
- 此时调用read()/recv()函数不会阻塞程序,因为通信的文件描述符是就绪的即读缓冲区以有数据
- 此时调用write()/send()函数不会阻塞程序,因为写缓存不满,可以写数据
- 监听的文件描述符:和客户端建立连接
- 对文件描述符进行下一轮的检测,循环往复
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
select
- 可以跨平台;
- 使用select能够检测的最大文件描述符个数有上限,默认是1024;
- fd_set不可以重用;
- 待测集合需要频繁在用户区和内核区之间进行数据的拷贝;
- 内核对select传递进来的待测集合的检测方式是线性的;
1 |
|
函数参数:
- nfds:委托内核检测的这三个集合中最大的文件描述符加1
- 内核需要现行便利这些集合中的文件描述符,这个值是循环结束的条件;
- 在Windows中这个参数是无效的,指定为-1
- readfds:文件描述符的集合,内核只检测这个集合中文件描述符对应的读缓冲区
- 传入传出参数,如果不需要可以指定为NULL
- writefds:文件描述符的集合, 内核只检测这个集合中文件描述符对应的写缓冲区
- 传入传出参数,如果不需要使用这个参数可以指定为NULL
- exceptfds:文件描述符的集合, 内核检测集合中文件描述符是否有异常状态
- 传入传出参数,如果不需要使用这个参数可以指定为NULL
- timeout:超时时长,用来强制解除select()函数的阻塞的
- NULL:函数检测不到就绪的文件描述符会一直阻塞。
- 等待固定时长(秒):函数检测不到就绪的文件描述符,在指定时长之后强制解除阻塞,函数返回0
- 不等待:函数不会阻塞,直接将该参数对应的结构体初始化为0即可。
返回值:
- 大于0:成功,返回集合中已就绪的文件描述符的总个数
- 等于-1:函数调用失败
- 等于0:超时,没有检测到就绪的文件描述符
1 |
|
poll
IO多路复用
http://example.com/2025/02/22/NetworkCommunications/IOMultiplexing/