Linux I/O多路复用技术
五种IO模型 https://www.cnblogs.com/ittinybird/p/4666044.html
高性能IO模型 线程模型https://www.jianshu.com/p/30fac4e11513
socket多路复用实现 https://blog.csdn.net/jyy305/article/details/73012706
epoll模型
参考:
https://blog.csdn.net/zhaobryant/article/details/80557262
https://www.cnblogs.com/lojunren/p/3856290.html
http://blog.chinaunix.net/uid-28541347-id-4273856.html
——————————https://www.linuxidc.com/Linux/2012-04/57876.htm
主通信线程使用epoll所有需要监控的FD,有事件交给多线程去处理
https://www.zhihu.com/question/23614342 使用epoll时需要将socket设为非阻塞吗
https://blog.51cto.com/10706198/1785000
概念和简介
为了处理大批量句柄而做了改进的poll 上限是系统可以打开fd的数目 1GB内存约10w
相对于其他模型的改进:
1)支持进程打开较大数目的fd文件描述符
select模型由FD_SETSIZE设置 默认1024/2048
select可以修改FD_SETSIZE重新编译内核—网络效率下降
也可以使用多进程—数据同步低效
2)I/O效率不随文件描述符fd增加而下降,仅对活跃socket操作
select和pool每次调用都会线性扫描整个socket集合
3)使用mmap加速内核和用户空间的消息传递
mmap将用户空间的一块地址和内核空间的一块地址同时映射到相同的一块物理内存地址(不管是用户空间还是内核空间都是虚拟地址,最终要通过地址映射映射到物理地址),使得这块物理内存对内核和对用户均可见,减少用户态和内核态之间的数据交换。内核可以直接看到epoll监听的句柄,效率高。
4)能够内核微调
API
#include <sys/epoll.h>//1.int epoll_create(int size);//创建epoll句柄 (占用fd使用后及时关闭)//2.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//epoll的事件注册函数,它不同于select是在监听事件时告诉内核要监听什么类型的事件,//而是通过epoll_ctl注册要监听的事件类型。//第一个参数epfd:epoll_create函数的返回值。//第二个参数events:表示动作类型。有三个宏来表示://* EPOLL_CTL_ADD:注册新的fd到epfd中;//* EPOLL_CTL_MOD:修改已经注册的fd的监听事件;//* EPOLL_CTL_DEL:从epfd中删除一个fd。//第三个参数fd:需要监听的fd。//第四个参数event:告诉内核需要监听什么事件。// struct epoll_event结构// 保存触发事件的某个文件描述符相关的数据typedef union epoll_data {void *ptr;int fd;__uint32_t u32;__uint64_t u64;} epoll_data_t;// 感兴趣的事件和被触发的事件struct epoll_event {__uint32_t events; // Epoll eventsepoll_data_t data; // User data variable};//对于Epoll Events,其可以是以下几个宏的集合://EPOLLIN:表示对应的文件描述符可读(包括对端Socket);//EPOLLOUT:表示对应的文件描述符可写;//EPOLLPRI:表示对应的文件描述符有紧急数据可读(带外数据);//EPOLLERR:表示对应的文件描述符发生错误;//EPOLLHUP:表示对应的文件描述符被挂断;//EPOLLET:将EPOLL设为边缘触发(Edge Triggered),这是相对于水平触发(Level Triggered)而言的。//EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket,需要再次//3.int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);//收集在epoll监控的事件中已经发生的事件。参数events是分配好的epoll_event结构体数组,//epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据赋值到这个event数组中//,不会去帮助我们在用户态分配内存)。maxevents告诉内核这个events数组有多大,这个maxevents的值不能大于//创建epoll_create时的size。参数timeout是超时时间(毫秒)。如果函数调用成功,则返回对应IO上已准备好的文件//描述符数目,如果返回0则表示已经超时。
工作模式
1. LT模式(Level Triggered,水平触发)
该模式是epoll的缺省工作模式,其同时支持阻塞和非阻塞socket。内核会告诉开发者一个文件描述符是否就绪,如果开发者不采取任何操作,内核仍会一直通知。
2. ET模式(Edge Triggered,边缘触发)
该模式是一种高速处理模式,当且仅当状态发生变化时才会获得通知。在该模式下,其假定开发者在接收到一次通知后,会完整地处理该事件,因此内核将不再通知这一事件。注意,缓冲区中还有未处理的数据不能说是状态变化,因此,在ET模式下,开发者如果只读取了一部分数据,其将再也得不到通知了。正确的做法是,开发者自己确认读完了所有的字节(一直调用read/write直到出错EAGAGIN为止)。
epoll高效性
(1)select/poll每次调用都要传递所要监控的所有fd给select/poll系统调用,这意味着每次调用select/poll时都要将fd列表从用户空间拷贝到内核,当fd数目很多时,这会造成性能低效。对于epoll_wait,每次调用epoll_wait时,其不需要将fd列表传递给内核,epoll_ctl不需要每次都拷贝所有的fd列表,只需要进行增量式操作。因此,在调用epoll_create函数之后,内核已经在内核开始准备数据结构用于存放需要监控的fd了。其后,每次epoll_ctl只是对这个数据结构进行简单的维护操作即可。
(2)内核使用slab机制,为epoll提供了快速的数据结构。在内核里,一切都是文件。因此,epoll向内核注册了一个文件系统,用于存储所有被监控的fd。当调用epoll_create时,就会在这个虚拟的epoll文件系统中创建一个file节点。epoll在被内核初始化时,同时会分配出epoll自己的内核告诉cache区,用于存放每个我们希望监控的fd。这些fd会以红黑树的形式保存在内核cache里,以支持快速查找、插入和删除。这个内核高速cache,就是建立连续的物理内存页,然后在之上建立slab层,简单的说,就是物理上分配好想要的size的内存对象,每次使用时都使用空闲的已分配好的对象。
(3)当调用epoll_ctl往epfd注册百万个fd时,epoll_wait仍然能够快速返回,并有效地将发生的事件fd返回给用户。原因在于,当我们调用epoll_create时,内核除了帮我们在epoll文件系统新建file节点,同时在内核cache创建红黑树用于存储以后由epoll_ctl传入的fd外,还会再建立一个list链表,用于存储准备就绪的事件。当调用epoll_wait时,仅仅观察这个list链表中有无数据即可。如果list链表中有数据,则返回这个链表中的所有元素;如果list链表中没有数据,则sleep然后等到timeout超时返回。所以,epoll_wait非常高效,而且,通常情况下,即使我们需要监控百万计的fd,但大多数情况下,一次也只返回少量准备就绪的fd而已。因此,每次调用epoll_wait,其仅需要从内核态复制少量的fd到用户空间而已。那么,这个准备就绪的list链表是怎么维护的呢?过程如下:当我们执行epoll_ctl时,除了把fd放入到epoll文件系统里file对象对应的红黑树之外,还会给内核中断处理程序注册一个回调函数,其告诉内核,如果这个fd的中断到了,就把它放到准备就绪的list链表中。
执行epoll_create时,创建了红黑树和就绪list链表;
执行epoll_ctl时,如果增加fd,则检查在红黑树中是否存在,存在则立即返回,不存在则添加到红黑树中,然后向内核注册回调函数,用于当中断事件到来时向准备就绪的list链表中插入数据。
执行epoll_wait时立即返回准备就绪链表里的数据即可。
EPOLLONESHOT事件
使用场合:
一个线程在读取完某个socket上的数据后开始处理这些数据,而数据的处理过程中该socket又有新数据可读,此时另外一个线程被唤醒来读取这些新的数据。
于是,就出现了两个线程同时操作一个socket的局面。可以使用epoll的EPOLLONESHOT事件实现一个socket连接在任一时刻都被一个线程处理。
作用:
对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多出发其上注册的一个可读,可写或异常事件,且只能触发一次。
使用:
注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个sockt。
效果:
尽管一个socket在不同事件可能被不同的线程处理,但同一时刻肯定只有一个线程在为它服务,这就保证了连接的完整性,从而避免了很多可能的竞态条件。
select模型
参考:https://blog.csdn.net/qq_28098067/article/details/80501750
https://blog.csdn.net/coder_yi_liu/article/details/8249421
https://blog.csdn.net/zhongbeida_xue/article/details/50917413
