在上一篇文章里面,我们了解到了一些典型的IO模型,并对此进行了一一探究,详情大家可以移步这里哦:https://blog.51cto.com/14632688/2663786 目录

  1. select 多路转接模型原理
  2. select优缺点剖析
  3. poll通信模型 + 优缺点分析
  4. epoll通信模型 + 优缺点分析

注:本篇内容有失整理,写的不是很满意,所以仅供参考

body (一)select多路转接模型 待补充: -其实已经写好了,但这篇文章逻辑不够好,而且感觉不太完善,已经放到草稿箱好久了,放出来慢慢改吧

优缺点分析: 优点:具有跨平台性 缺点: 1.需要频繁拷贝,效率低 2.有最大的监控设置,一般为1024 3.在内核遍历,返回后还需要在用户态再遍历,且遍历方式为轮询,随着监控数量变大性能下降 (二)poll模型 操作流程

  • 用户定义描述符事件结构体数组(一个数组,数组中元素是结构体),将需要监控的描述符事件信息添加到这个数组中
    • 注:struct{
      • int fd //需要监控的描述符
      • short* events //需要监控的事件
      • short* revents //实际就绪的事件(实际返回的)事件
      • };//这个结构体我们这里稍微了解即可,后续内容将位置匹配内容
      • events和revents值为:
  • 发起监控调用,相应的接口(API)是: * int poll (struct pollfd* fds,nfds_t nfds, int timeout) fd:描述符结构体的首地址 nfds:fds数组的长度,数组中有效事件的个数 timeout:超时时间控制,默认单位是毫秒 成功返回就绪事件的个数,失败返回-1;
  •   监控调用返回后,我们需主动遍历事件结构体数据,通过每个节点中的revents成员来确定
    
  • 其实这里是有些晦涩的,但是我们回过头来总结一下: * 1,我们建立了一个事件结构体数组,初始化的时候已经将我们要监控的所有事件(通过上述结构体)写进数组内了, * 2.我们知道结构体的组成是 fd, events,revents,在监控状态下, 等到事件状态为就绪时,我们将revents值做标识 * 监控调用返回后,我们知道了那些“fd描述的事件”的个数,然后我们由每个节点的revents成员的值和通过遍历,确定就绪事件,执行IO操作; 优缺点分析 优点:
  1. 可监控的描述符没有上限
  2. poll通过事件结构化了多种集合的操作流程(体现在与selsect对比上),并且不需要每次监控重新添加描述符 缺点:
  3. 跨平台的移植性差
  4. 监控原理是:数据拷贝内核,轮询遍历监控,所以他的性能随着描述符的增多而下降
  5. 监控返回后只得知就绪事件的个数,还需要再次遍历每个界沟通节点中的revents成员来确定当前就绪了哪个事件;

(三)epoll模型 写在前面:很多人说“Linux下最好用的IO多路转接模型”便是epoll模型 操作流程 1,创建epoll句柄

int epoll_creat(int size)  
	如果大家看过man手册的话,就会知道,目前这个接口中的size已经被忽略掉了,曾经描述的是:“最大描述符数量”,现在只要求大于0即可;
	成功返回epoll操作句柄,失败返回-1;
  1. 向内核中epoll添加要监控的描述符事件
int epoll_ctl(int epfd,int op,int fd, struct epoll_event* event)
edfd:epoll的操作句柄
op: 有三种,add --  del ---  mod
![](https://s4.51cto.com/images/blog/202103/28/fd19baf80beb219ead18f22fb8c1e375.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
fd:需要监控的fd
struct epoll_event*{
	uint32 events;//要监控的事件以及返回后实际就绪的事件
	union{
		int fd; //通常会别设置为要监控的描述符
		void* ptr 
		还有俩个成员,这里不用太关注,
	}data;
};
![](https://s4.51cto.com/images/blog/202103/28/437cfef96f5f29852bcaf75140b47aa2.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
  1. 发起调用,开始监控
int epoll_wait(int epdf,struct epoll_event* events,int maxevents,int timeout);

	epfd:操作句柄
	events: 事件结构体数组首地址---用于获取就绪事件
	maxevents:第二个参数events数组的节点数量
	timeout:超时控制
成功返回就绪事件个数,0表示超时,失败返回-1;

4.epoll_wait 调用返回后,只需要根据返回值,遍历events数组,就可逐个对就绪的描述符事件操作了 5.上面的逻辑没有理论支持很难受,很抽象,这里进行一定的分析,我们首先有必要知道,epoll通信模型是一种“异步阻塞 方式的模型,它的功能实现都是由内核完成的, 在内核里,我们可以在epoll中找到,俩个这里比较重要的数据结构模型,一个是红黑树,一个是双向链表, 红黑树放了所有需要被监听的fd,当某一fd下的事件就绪后,我们可以快速的从中找到节点,并插入到底链表中

struct eventpoll{ 
 .... 
 /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ 
 struct rb_root rbr; 
 /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/ 
 struct list_head rdlist; 
 .... 
};

因此,系统通过链表是否为空来判断是否有事件就绪,同时,链表中的所以节点多代表的数据,都是就绪的 注意:上述从红黑树里找节点,插入链表以及条件判断,都是内核执行的,我们是看不到的, 然后这里我们在用epoll_wait()接口,(将链表中的值插入到events事件结构数组中了,这样我们才可以显示的使用就绪事件),根据返回值,遍历events数组,就可以逐个对就绪描述符操作了 优缺点分析 缺点:跨平台移植性差,只在Linux下使用; 优点:

  1. epoll和poll一样,描述符没有上限
  2. 监控的原理实现中,只需要向内核拷贝一次
  3. 监控的原理是异步阻塞操作,任务由系统内核完成,进程只判断就绪双向链表是否为空就可以确定是都有描述符就绪,因此,描述符的数量不会对性能产生影响(1个也不为空,10个也不为空,1000个也不为空)
  4. 直接返回的就是就绪描述符事件,直接对就绪的描述符进行操作,不需要用户进行遍历