首页 > 代码库 > 高性能网络服务器--I/O复用 select poll epoll_wait之间的区别
高性能网络服务器--I/O复用 select poll epoll_wait之间的区别
一、select
使用的集合的方式,最多只能监听1024个文件描述符,内部使用位操作,将相应的位置为1或者置为0,需要将可读、可写、异常的三类事件分开来用,内部使用轮询的方法,每次返回都需要将所有的套接字从内核到用户空间之间进行拷贝。
二、poll
比select稍微好一点,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪。
三、epoll_wait
把用户关心的文件描述符上事件放在内核里的一个事件表中从而无需像select和poll那样每次调用都要重复传入文件描述符集或者事件集。epoll_wait函数如果检测到事件,就将所有就绪的时间从内核时间表中复制到它的第二个参数events指向的数组中,这个数组适用于输出epoll_wait检测到内核见到的就绪事件,这就极大地提高了应用程序索引就绪文件描述符的效率。其实他的内部是一个红黑树和一个链表,红黑树中国记录了epoll_wait所关注的事件,当某一个描述符上有事件发生事,就会触发一个回调,这个描述符就会被添加到链表中,这个链表就是需要返回个用户空间的描述符,所以每次从内核到用户空间中的传输描述符的个数并不是很多。
LT和ET模式。
LT是电平触发模式,ET是边缘触发模式,默认情况下,使用LT模式,但是ET是高效的模式。
在某一个描述符上可读或者可写时,用户空间开始读写,用户空间没有将缓冲区中的数据读完,那么在LT模式下回继续触发,在ET模式下只触发一次。
EPOLLONESHOT事件
即使我们使用ET模式,一个socket上的某个事件还是可能被触发多次。这在并发程序中就会引起一个问题,比如一个县城或者进程读取完某个socket上的数据后开始处理这些数据,而在数据的处理过程中该socket上又有新数据可读(EPOLLIN再次被触发),此时另外一个线程被唤醒来读取这些新的数据,于是就出现了两个线程同时操作一个socket的局面。这当然不是我们期望的,我们期望的是一个socket连接再任一时刻都只被一个线程处理,这一点可以使用epoll的EPOLLONESHOT事件处理。
对于注册了EPOLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读 可写或者异常事件,这样,当一个在处理某个socket时,其他线程是不可能有机会操作该socket的,但是反过来思考,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个socket
四、上述三种I/O复用的区别
select的参数类型fd_set没有将文件描述符和事件绑定,它仅仅是一个文件描述符的集合,因此select需要提供3个这种类型的参数来分别传入和输出可读可写异常事件,这一方面使得select不能处理更多类型的数据按,由于内核对fd_set集合的在线修改,应用程序下次调用select前不得不重置这3个fd_set集合,poll的参数类型pollfd,他把文件描述符和事件都定义在其中,任何事件都被统一管理,内核每次修改的是pollfd结构体的revents成员,二events成员保持不变,因此下次调用poll时应用程序无需重置pollfd类型的事件集参数,由于每次select和poll调用都返回真个用户注册的事件集合,所以应用程序索引就绪文件描述符的时间复杂度就是O(n),epoll在内核中维护了一个事件表,采用了一个独立的系统调用epoll_ctl来控制往其中添加 删除 修改时间,这样,每次epoll_wait调用都直接从内核事件表中取得用户注册的时间,而无需反复从用户空间读入这些事件,epoll_wait系统调用的event参数仅用来返回就绪事件,这使得应用程序索引就绪文件描述符的时间复杂度达到O(1).
从原理上将,select和poll采用的都是轮询的房还是,即每次调用都要扫描真个注册文件描述符结合,并将其中就绪的文件描述符返回给用户程序,因此他们检测就绪事件的算法复杂度为O(n),epoll_wait采用回调的方式,内核检测到就绪的文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的时间插入内核就绪事件队列,内核最后在适当的时机将就绪事件队列中的内容拷贝到用户空间,因此epoll_wait无需轮询整个文件描述符集合来检测哪些事件已经就绪,其算法事件复杂度为O(1).
五、非阻塞connect
当调用connect时,可能会出现以下三种错误:
ECONNERFUSED 目标端口不存在,连接拒绝
ETIMEDOUT 连接超时
EINPROGRESS 这种错误发生在对非阻塞的socket调用connect,而连接又没有立即建立时,在这种情况下,我们可以调用slect poll epoll_wait等函数来监听这个连接失败的socket上的可写事件,当select poll函数返回后,再利用getsockopt来读取错误码并清除该socket上的错误,如果错误码为0,表示连接成功建立,否则连接失败。
就是在连接失败并且错误是EINPROGRESS时,马上使用一个I/O复用函数监听这个套接字 返回之后查看错误信息
这种方法存在几处移植性问题,首先,非阻塞的socket可能导致connect始终失败,其次,select对处于EINPROGRESS状态下的socket可能不起作用,最后,对于出错的socket ,getsockeiopt在有些系统上返回-1,在有些系统中返回0,。
高性能网络服务器--I/O复用 select poll epoll_wait之间的区别