IO多路复用

IO多路复用技术本质就是: 通过一种机制,使得单个进程可以监视多个文件描述符,一旦某个描述符就绪(读就绪或者写就绪),就能够通知进程进行相应读写操作。

文件描述符:
linux下一切都被看做文件,包含普通文件,目录文件,设备文件,套接字等等。抽象为文件,提供统一接口,方便调用。

为了将文件与进程对应上,对于进程打开的每个文件,内核会返回一个文件描述符。本质上其实就是一个非负整数。

每个进程有一个文件描述符表,每个表的存储一个指向文件的指针,指向系统打开文件的表,然后打开文件表内存储指向inode的指针。

Unix网络编程中的五种IO模型
网络IO涉及两个交互1. 等待数据准备 2. 将数据从内核拷贝到用户内存中
缓存IO: 缓存IO机制中,OS会将IO数据先拷贝到操作系统内核缓冲区中,然后才拷贝到用户进程地址空间

阻塞IO 用户进程会阻塞等待数据,进程被挂起,陷入等待状态,等数据准备好执行系统调用将数据从内核拷贝到用户内存,然后内核返回结构。进程解除block状态,重新运行。
非阻塞IO 用户进程请求数据之后会立即返回结果。如果数据没准备好,每隔一段时间再发送一次,一旦准备好,执行系统调用将数据拷贝到用户内存,然后返回。
IO多路复用 看起来与阻塞IO相似,等待数据、拷贝数据都阻塞,区别在于 等待多个数据就绪,即可以处理多个连接。进程调用select之后就被slect阻塞,会监听多个文件描述符直到有数据就绪。
一般会设置为非阻塞IO,select通过轮训机制判断每个IO数据是否就绪。

信号驱动IO 进程请求之后,内核立即返回。等待数据就绪之后,发送信号给进程,信号处理程序中将数据从内核空间拷贝到用户内存

异步IO 不阻塞进程,即使从内核空间缓冲区将数据拷贝到进程中也不阻塞,而是通过一个回调函数通知进程拷贝完成。

select、poll、 epoll

select

函数原型:

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

参数一:最大的监听的文件描述值+1,从0开始
参数二三四: 指定要监听的文件文件描述符的集合
参数五: 超时时间 指定内核等待就绪文件描述符的时间。两个特殊case: NULL, 0

返回值: 就绪文件描述符数目,如果是超时返回是0, 出错返回-1.

工作流程:

  1. 将FD_SET从用户空间拷贝到内核空间
  2. 内核轮询遍历FD_SET中的文件描述符,调用文件描述符对应的设备驱动的poll函数,返回当前文件的状态,设置就绪回调函数,并将调用进程放入文件描述符对应的等待序列中。
  3. 遍历完成,如果没有文件描述符就绪, 将进程睡眠,等待timeout时间内有文件描述符就绪。这段时间文件就绪后,会触发回调函数,唤醒等待队列上的当前进程。再次轮询一遍,FD_SET统计所有就绪的文件描述符。进入步骤四
  4. 遍历完成之后,如果有文件描述符就绪,退出循环,将进程从全部文件描述符等待序列中删除。并返回就绪数量。

在这里插入图片描述

特点:

  1. select/poll 每次都需要将FD_SET从用户空间传递到内核空间
  2. fd事件回调函数是 pollwake,只是唤醒调用进程,调用进程需要重新遍历fd。
  3. 当有事件发生时,返回的FD_SET保存所有FD,尽管其中只有很少fd就绪
  4. select/poll 返回时,会将该进程从全部监听的FD等待队列中删除,这样就需要每次都需要重新传入全部监听fd,然后重新挂载。
  5. 单个进程能够监听的最大文件描述符的数量为1024(内核定义 FD_SETSIZE)。

POLL
函数原型:

poll机制与select类似,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 重点是没有 没有文件描述符数量限制。

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
typedef struct pollfd {
int fd; // 需要被检测或选择的文件描述符
short events; // 对文件描述符fd上感兴趣的事件
short revents; // 文件描述符fd上当前实际发生的事件
} pollfd_t;

参数一: dfs是一个pollfd类型数组,用于存放监听的描述符,并且调用之后不会被清空。
参数二: 描述符数量

最后一个参数以及返回值与select一致。

poll相比select, 文件描述符集合的表示方式不同,poll使用pollfd结构, 是链式的,没有最大连接数量限制。

EPOLL
函数原型:

int epoll_create(int size); 
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

函数一创建一个epoll句柄,size表示要监听的描述符的数量
函数二,将被监听的文件描述符添加到epoll句柄或者是修改、删除epoll句柄中的文件描述符
函数三,等待事件触发,参数二表示从内核中得到的事件集合,参数三表示这个事件集合size最大值(不能大于函数一的size)

工作流程:

  1. 在内核空间中建立监听列表(红黑树实现),通过epoll_ctl 添加fd

  2. 调用fd对应的驱动程序的poll函数,返回fd的状态,并将事件就绪回调函数加入文件描述符等待队列回调函数将就绪fd加入就绪队列中,同时唤醒本进程

  3. 调用epoll_wait会将本进程添加到等待队列,然后判断就绪队列是否为空,为空的话进程睡眠一段时间,然后重新判断。不为空,将就绪事件拷贝到用户空间中。

与Select/Poll机制相同点:

  1. 主要监测流程一致,都需要当前进程挂载到fd上,fd就绪事件发生后,回调函数基本功能是唤醒本进程
  2. 都需要监测是否有就绪事件,有就返回,没有就睡眠本进程,不同的是select、poll监测每个fd,epoll监测就绪队列。

优点:

  1. 没有最大监听文件描述符数量限制
  2. 将监听的文件描述符放在内核空间,减少了用户空间与内核空间数据拷贝次数
  3. IO不会随着FD数目增加而线性下降
  4. 使用mmap(内存映射)技术传递FD消息,内核空间与用户空间使用同一块内存实现。

EPOLL支持两种事件类型: ET以及LT
ET模式只支持非阻塞SOCKET, LT都支持。

LT模式下,内核通知一个文件描述符就绪,如果不做操作时,内核还是会就绪通知。select、poll都是该模式

ET模式下,只有当描述符从未就绪变为就绪时,内核才会通知。只支持非阻塞IO,避免由于一个文件描述符的阻塞读、阻塞写将整个进程饿死。因此需要循环读、写数据,直到产生一个EAGIN错误才认为此时事件处理完成。

EPOLLONESHOT: 设置了ONESHOT的文件描述符,操作系统最多触发可读、可写或者异常事件中的一种,而且只触发一次。
保证任意时刻同一socket连接只能被一个线程处理

设置了ONESHOT之后,epoll等待注册在epoll句柄上文件描述符的事件发生,如果发生,则将文件描述符以及事件类型记录在events中,然后将注册在句柄上的文件描述符的该事件类型给清除,如果希望继续关注,需要使用epoll_ctl(epoll_fd, EPOLL_CTL_MOD, listen_fd, &events)重新设置事件类型。

设置了ONESHOT事件,在事件的数据处理完成之后,应该立刻重置ONESHOT,保证其他线程能处理该socket上的后续事件。

多线程编程中,如果使用LT模式,只要缓冲区还有未处理的数据,就会一直触发,很可能被多个线程处理。如果此时使用ONESHOT, 每个线程只能取到很小一部分数据,可能会导致错误。

因此,多线程编程中的LT模式,设置EPOLLONESHOT之后,也需要循环读。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值