多路IO转接模型select、poll、epoll,以及epoll的底层实现

select、poll、epoll都是IO多路复用也叫多路IO转接,是通过一种机制,使一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪)能够通知程序进行相应的读写操作。

select

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态

(2)同时每次调用select都需要在内核遍历传递进来的所有fd

(3)select支持的文件描述符数量太小了,默认是1024

poll

poll的实现和select非常相似,只是描述fd集合的方式不同,poll没有最大连接数的限制,是因为它是基于链表存储的,但是poll还是有select的(1)和(2)两个问题,而且poll还有一个特点是“水平触发”,如果报告了fd后没有被处理,那么下一次poll时会再次报告该fd

epoll

select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。但是同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降,epoll解决了这个问题。但是在监听的文件描述符大部分都处于就绪状态的情况下,三者的性能就差不多了。

epoll有以下几个特点:

1、没有最大并发连接的限制,能打开的fd上限远大于1024(1g的内存上能监听10万个端口)

2、在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活了这个文件描述符,当文件描述符就绪,就会在内核中建立一个就绪队列,这样调用epoll_wait获取就绪文件描述符的时候,只要取队列中的元素即可,操作的时间复杂度恒为O(1)。(此处去掉了遍历文件描述符,而是通过监听回调的的机制)

3、select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,epoll通过内核与用户空间mmap同一块内存来实现。(还有的说,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时,在epoll_ctl中指定EPOLL_CTL_ADD,会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次)

epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死

epoll底层的数据结构:

epoll之所以有上面的优点,是因为它底层的数据结构。epoll底层有一个红黑树来存放所以要监听的文件描述符,由于红黑树的结构,每次查找、添加、删除一个文件描述符都很快

epoll的回调机制把就绪的文件描述符放在就绪队列里面,就绪队列是链表实现的,当调用epoll_wait函数只需要查看就绪队列是否有数据即可

总结起来有三点:

1、执行epoll_create时,创建了红黑树和就绪list链表;
2、执行epoll_ctl时,如果增加fd,则检查在红黑树中是否存在,存在则立即返回,不存在则添加到红黑树中,然后向内核注册回调函数,用于当中断事件到来时向准备就绪的list链表中插入数据。
3、执行epoll_wait时立即返回准备就绪链表里的数据即可。

参考:

https://blog.csdn.net/qq_37941471/article/details/80954592

https://www.cnblogs.com/Anker/p/3265058.html

https://www.cnblogs.com/jeakeven/p/5435916.html

https://blog.csdn.net/zhaobryant/article/details/80557262

你可能感兴趣的:(socket编程)