文章目录
一、IO多路复用简介
允许单个线程或者进程管理多个网络连接或者文件描述符
可以通过以下几种机制实现:
- select(表,本质就是一个结构体,成员只有一个数组)
① select监听的最大文件描述符个数是1024
② select会有清空表的过程,需要反复构造表,反复拷贝表,效率比较低
③ select对应的进程从休眠态被唤醒后,需要再次遍历文件描述符表找到就绪的文件描述符,效率比较低 - poll(链表)
① poll监听的文件描述符没有个数限制
② poll没有清空表的过程,不需要反复构造表,反复拷贝表,效率比较高
③ poll对应的进程从休眠态被唤醒后,需要再次遍历文件描述符表,效率比较低 - epoll(红黑树+双向链表:红黑树保存要监听的文件描述符,双向链表保存就绪的文件描述符)
① epoll监听的文件描述符没有个数限制
② epoll没有清空表的过程,不需要反复构造表,反复拷贝表,效率比较高
③ epoll对应的进程从休眠态被唤醒后,将就绪的文件描述符直接放到双向链表中,不需要再次遍历文件描述符表,能直接获得就绪的文件描述符,效率比较高
二、应用层相关API
(一)select
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
功能:
允许一个进程或线程管理多个文件描述符
参数:
nfds:监听的文件描述符数量,即最大的文件描述符+1
readfds:监听读表; 没有可以传NULL
writefds:监听写表; 没有可以传NULL
exceptfds:监听其他表; 没有可以传NULL
timeout:超时时间; NULL表示会一直阻塞,直到有文件描述符就绪
返回值:
成功返回三个集合一共就绪的文件描述符数量
失败返回-1,重置错误码
//判断fd是否存在于表中
int FD_ISSET(int fd, fd_set *set);
//向表中添加成员
void FD_SET(int fd, fd_set *set);
//删除表中的成员
void FD_CLR(int fd, fd_set *set);
//清空表
void FD_ZERO(fd_set *set);
补充:fd_set
该表本质就是一个结构体,结构体中只有一个unsigned long类型(4个字节,即32位)的数组成员,该数组有32个元素,即4*8*32=1024
- 注:进程会记录自己打开的文件描述符个数,每个进程维护自己的fd,进程与进程之间的fd互不影响
(二)poll
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:
允许程序同时监视多个文件描述符,查看其是否发生读、写或者错误等事件
参数:
fds:指向pollfd结构数组的指针。每个pollfd结构代表一个要监视的文件描述符及其感兴趣的事件。
nfds:监视的文件描述符数量,即最大的文件描述符加1
timeout:超时时间
返回值:
成功返回revents为非0的结构体数量
超时没有就绪文件描述符,返回0
失败返回-1,重置错误码
struct pollfd {
int fd; // 文件描述符
short events; // 感兴趣的事件
short revents; // 返回的事件
};
成员:
fd:要监视的文件描述符。
events:通过位掩码指定的感兴趣的事件类型,如POLLIN(有数据可读)、POLLOUT(可以写入数据)等。
revents:在调用poll后,由系统填充,表示实际发生的事件。
nfds:fds数组中的元素数量,即要监视的文件描述符的数量。这个值通常是fds数组中最后一个元素的索引加1。
timeout:指定等待事件的超时时间(毫秒)。
如果timeout为-1,则poll会无限期地等待,直到某个事件发生。
如果timeout为0,则poll会立即返回,不阻塞。
如果timeout为正数,则poll会等待指定的毫秒数,如果在这段时间内没有事件发生,则返回
(三)epoll
更适用于高并发场景,epoll使用红黑树作为底层数据结构来维护注册的文件描述符集合,实现高效的检索和管理
#include <sys/epoll.h>
int epoll_create(int size);
功能:
创建一个epoll实例
参数:
@size:这是一个提示性的参数,表示epoll实例可以监视的文件描述符的数量上限。Linux 2.6.8及以后的版本中,这个参数被忽略,但仍然需要传递一个大于0的值。
返回值:
成功返回epfd;
失败返回-1,重置错误码
备注:
这个函数本质就是创建一个红黑树,用户通过epfd拿到红黑树,这个epfd也会占用一个文件描述符fd
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:
epoll的控制操作
参数:
@epfd:由epoll_create返回的文件描述符,表示epoll实例
@op:操作方式
EPOLL_CTL_ADD 添加
EPOLL_CTL_MOD 修改
EPOLL_CTL_DEL 删除
@fd:被操作的文件描述符
@event:事件结构体,包含事件类型
struct epoll_event{
uin32_t events; //events字段表示注册的事件类型(如EPOLLIN、EPOLLOUT等)
epoll_data_t data; //data字段用于存储用户数据
}
返回值:
成功返回0;
失败返回-1,置位错误码
int epoll_wait(int epfd, struct epoll_event *revents, int maxevents, int timeout);
功能:
阻塞等待文件描述符就绪
参数:
@epfd epoll实例
@revents 用于存放事件信息的数组,每个数组元素都是epoll_event类型的结构体。这个数组用于接收就绪的文件描述符及其对应的事件信息。
@maxevents events数组的大小,即最多可以等待多少个事件
@timeout 超时时间
= 0 :立即返回
> 0 :毫秒超时
=-1 :忽略超时,永久阻塞,直到有fd就绪
返回值:
成功,返回就绪的文件描述符的个数;
超时,返回0,代表超时,没有文件描述符就绪
失败,返回-1,重置错误码
三、内核中IO多路复用的实现机制
US:
---
select(fd2 + 1, &rfds, NULL, NULL, NULL)
-----------------------------------------------------------
KS: VFS:
---
进入到内核层之后会调用到syscall函数,进行宏定义替换后会得到sys_select函数
long sys_select(int n, fd_set __user * inp, fd_set __user * outp