准确表达自己的诉求并且懂得拒绝,会在很大程度上减少生活中的烦恼
目录
13. 典型IO/多路转接模型
13.1 典型IO
13.1.1 分类
(1) 阻塞IO
- 在内核将数据准备好之前,系统调用会一直等待,所有的套接字默认都是阻塞方式。
(2)非阻塞IO
- 如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码;非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符,这个过程称为轮询。
(3) 信号驱动IO
- 内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作。
(4) 异步IO
- 由内核在数据拷贝完成时,通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。
(5) 多路转接IO
- 对大量的的IO事件是否就绪的监控, 并且可以告诉用户哪一个 IO 当前就绪,这个时候用户可以针对就绪的 IO 进行操作。
13.1.2 IO 流程
- 等待IO就绪+IO数据拷贝
几种典型IO的操作对比
- 这几种IO是一个IO的发展过程, 他们的使用效率以及对资源的利用率越来越高, 但是占用的资源越来越多, 并且流程控制越来越复杂.
13.1.3 阻塞、非阻塞、同步、异步
同步
- 为了完成一个功能发起调用, 若当前不具备完成条件, 则进行进程等待, 直到完成功能;
- 同步功能由进程自身完成, 通常都是阻塞的;
异步
- 为了完成一个功能发起调用,进程自身并不完成,功能由操作系统完成后通知进程,同步与异步强调的是发起调用后,功能是否是由进程自身完成的;异步功能是由别人完成,通常有阻塞也有非阻塞。
异步阻塞
- 等待别人完成功能。
异步非阻塞
- 不等待别人完成,调用发起后则直接返回。
同步与异步优缺点
- 同步流程更加简单,异步对资源的利用更加充分,但是流程控制比较复杂;发起调用之后,是否能够立即完成功能,并调用返回。
13.2 多路转接模型(多路复用)
13.2.1 select
(1)接口使用
- int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
(2)参数解析
- nfds:maxfd+1 最大的描述符+1; 避免空遍历判断, 提高性能;
- readfds/writefds/exceptfds:可读/可写/异常事件集合;
- timeout: struct{tv_sec; tv_usec}为 select 的阻塞监控做一个超时返回;
- NULL: 永久阻塞 内部时间为0则立即返回
- fdset: 描述符集合–位图存储–大小默认为1024, 取决于_FD_SETSIZE这个宏
- 返回值: >0 返回就绪的描述符个数 ==0 等待超时 <0 监控出错
(3)原理流程
- 1.用户自己定义一个描述符集合fd_set, 然后将需要监控的描述符添加到集合中;
- 2.将描述符集合拷贝到内核中;
- 3.对集合中的的描述符进行轮询就绪判断:若没有描述符就绪, 则挂起等待, 在此进行轮询判断, 若超时, 则直接返回;若有描述符就绪, 则select调用返回, 并且返回就绪的描述符个数;
- 4.调用返回之前, 将描述符集合中的非就绪描述符移除(集合中遗留的都是就绪描述符);
- 5.进程判断哪个描述符在集合中, 这个描述符就是就是就绪的, 就而对其进行IO操作
(4)优缺点分析
优点
- 1.select遵循posix标准, 可以跨平台;
- 2.select监控超时等待时间, 可以精确到微秒;
缺点
- 1.select所能监控的描述符数量有最大上限 – 1024(_FD_SETSIZE);
- 2.select每次都需要将描述符集合拷贝到内核进行监控(用户态与内核之间的数据拷贝);
- 3.select在内核中对所有描述符进行轮询遍历判断是否就绪(性能随着描述符增多而降低);
- 4.select就绪后移除集合中非就绪描述符, 修改集合;每次监控都需要新添加描述符(编码复杂);
- 5.select返回给用户就绪的描述符集合, 但是不会直接告诉用户哪些描述符就绪(需要用户进行遍历判断哪些描述符在集合中,才能找出就绪的描述符进而对其进行操作),性能随着描述符增多而降低;
13.2.2 poll
(1)接口使用
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// pollfd结构
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
(2)参数解析
- fds 是一个 poll函数 监听的结构列表,每一个元素中,包含了三部分内容:文件描述符、监听的事件集合、返回的事件集合;
- nfds表示 fds数组 的长度;
timeout 表示 poll函数 的超时时间,单位是毫秒(ms);
返回值::<0 表示出错;==0表示poll函数等待超时; >0返回就绪的描述符个数。
(3)原理流程
- 1.用户Wie每一个关心的描述符定义事件结构(文件描述符/用户所关心的事件);
- 2.将描述事件结构数组拷贝到内核中进行监控;
- 3.轮询遍历事件数组中的描述符, 判断描述符是否就绪了某个事件:若没有描述符就绪用户所关心的事件, 则每隔一会遍历判断一次;若有描述符就绪用户所关心的事件, 则会将就绪的事件放到事件结构的revents中,并调用返回(性能随着描述符增多而降低);
- 4.当调用返回后, 用户遍历事件结构数组, 判断结构中的revents事件中是否包含用户所关心的事件, 通过这种方式判断哪一个描述符就绪了, 进而对描述符进行相应操作。
(4)优缺点分析
优点
- 1.poll采用事件结构的方式对描述符进行监控, 简化了多个描述符集合的监控编码流程;
- 2.poll没有描述符数量上的限制;
缺点
- 1.poll每次监控都需要将所有的事件结构信息拷贝到内核;
- 2.在内核中poll进行就绪判断同样适用轮询遍历的判断(性能随着描述符增多而降低);
- 3.描述符事件就绪后, poll修改描述符事件结构中的revents信息为当前就绪的事件;但是poll不会直接告诉用户哪一个描述符就绪, 需要用户对事件结构数组进行遍历, 判断哪一个结构中的revents是用户关心的事件, 则这个描述符是就绪的进入而对其进行操作;
- 4.poll不能跨平台操作;
- 5 poll相较于select实际上对大量描述符进行监控的原理并没有发生改变, 因此性能并没有多大提高;
13.2.3 epoll
(1)接口使用
- int epoll_create(int size);
- 创建epoll, 在内核中创建eventpoll结构体struct eventpoll{…红黑树rbr, …双向链表rdllist,…}
返回一个文件描述符, 可以通过描述符找到内核中的eventpoll结构体;
(2)参数解析
- size: 限制epoll所能监控的描述符数量上限, 自从Linux2.68之后被忽略,需要>0;
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
epfd: 是epoll_create()的返回值(epoll的句柄).
op: 表示动作,用三个宏来表示,EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL;
fd: 是需要监听的fd.
*event: 告诉内核需要监听什么事.
struct epoll_event{
uint32_t events;
epoll_data_t data;
_EPOLL_PACKED;
}
- int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epfd: epoll_create返回的epoll操作句柄, 描述符;
events: epoll_event结构体数组首地址, 用于接收就绪的描述符事件信息;
maxevents: epoll_event结构体数组结点个数;
timeout: epoll的超时等待时间(毫秒);
返回值: >0就绪的描述符个数 ==0 等待超时 <0 监控出错
(3)原理流程
- 通过epoll_create创建eventpoll结构, 返回操作句柄;
- 通过操作句柄向内核中添加描述符的监控事件epoll_ctl;
- 通过epoll_wait开始监控,异步阻塞操作, 描述符的监控由操作系统完成;
操作系统: 当描述符就绪了用户所关心的事件, 则将事件信息添加到eventpoll的双向链表中,每隔一段时间则会看eventpoll中双向链表是否为空, 判断是否有描述符就绪;
若没有就绪, 则挂起等待, 隔一会继续判断, 若超时则返回;
若有就绪返回时, epoll_wait将就绪的事件信息放置到事件数组events中,返回给用户; - 用户通过epoll_wait传入的events获取到就绪的事件,直接遍历操作;
(4)优缺点分析
优点
- 1.epoll不能跨平台;
- 2.监控超时等待时间只能精细到毫秒;
缺点
- 1.监控的描述符数量无最大上限;
- 2.采用事件结构对描述符进行监控, 简化了多个描述符集合的监控操作流程;
- 3.epoll的事件信息, 每条信息只需要向内核拷贝一次;
- 4.epoll是一个异步阻塞操作, 操作系统对描述符事件进行监控, 而这个操作系统实现的监控采用事件回调的方式, 不需要进行轮询遍历描述符进行判断, 性能不会随着描述符增多而降低;
- 5.当描述符就绪后将描述符事件信息添加到双向链表中,epoll_wait只需要隔会判断双向链表是否为空, 就可以判断出是否有就绪描述符事件就绪;
- 6.当epoll_wait返回时, 直接将就绪的事件信息拷贝到用户给与的事件结构数组中,相当于直接告诉了哪些描述符就绪了(事件结构中包含描述符), 用户可以直接对描述符进行操作(避免非就绪描述事件遍历);
- 说明
IO多路转接模型, 可以实现对大量描述符就绪事件监控, 可以让进进程针对就绪描述符进行操作, 可以让进程,线程避免因为对非就绪描述符进行操作而阻塞, 可以让一个进程轮询对大量的描述符进行操作, 实现一个服务器与多个客户端进行数据通信, 因此IO多路转接模型是一种高并发模型;