为什么要用epoll,用了epoll还用不用多线程或者多进程了?
高性能的网络服务器需要同时并发处理大量的客户端,而采用那种对于每个连接使用一个分开的线程或进程的方法效率不高.
因为在处理大量的客户端的时候,资源的使用以及进程上下切换会影响服务器的性能. 那么有一个可代替的方法就是在单一的线程中使用非阻塞的I/O.
epoll与多线程与多进程是互不冲突的.使用了epoll并不是说就不能用多线程,可以另开一个线程在分支上工作以节省时间.
那么epoll有哪些相关的函数呢
epoll_create
orepoll_create1
:用来创建epoll实例epoll_ctl
:用来增加或移除被epoll所监听的文件描述符epoll_wait
:用来等待发生在监听描述符上的事件,它会一直阻塞直到事件发生.
这里要特别强调一个参数EPOLLONESHOT,如果要保证套接字同一时段只被一个线程处理,必须加上。
解决方案:给accept()
后的套接字加上参数EPOLLONESHOT
,线程结束后处理完之后,再重置EPOLLONESHOT属性,但是,千万不可以给listen()后的监听套接字设置此属性,这会造成同一时刻只能处理一个连接的情况。
在epoll的ET模式下,正确的读写方式为:
读:只要可读,就一直读,直到返回0,或者 errno = EAGAIN 写:只要可写,就一直写,直到数据发送完,或者 errno = EAGAIN。
什么叫非阻塞模式
非阻塞方式:
非阻塞方式是指: 如果没有数据可读,或者不可写,读写函数马上返回,而不会等待。
举个栗子:小明去找一个女神聊天,女神却不在。 如果小明舍不得走,只能在女神大门口死等着,当然小明可以休息。当女 神来了,她会把你唤醒(囧,因为挡着她门了),这就是阻塞方式。如果小明发现女神不在,立即离开,以后每隔十分钟回来看一下(采用轮询方式),不在的话仍然立即离开,这就是非阻塞方式。
为什么要设置epoll成非阻塞模式
epoll有两种模式,阻塞模式与非阻塞模式.
TCP连接被客户端夭折,即在服务器调用accept之前,客户端主动发送RST终止连接,导致刚刚建立的连接从就绪队列中移出,如果套接口被设置成阻塞模式,服务器就会一直阻塞在accept调用上,直到其他某个客户建立一个新的连接为止。但是在此期间,服务器单纯地阻塞在accept调用上,就绪队列中的其他描述符都得不到处理。
解决方案:把监听套接口设置为非阻塞,当客户在服务器调用accept之前中止某个连接时,accept调用可以立即返回-1,这时源自Berkeley的实现会在内核中处理该事件,并不会将该事件通知给epoll,而其他实现把errno设置为ECONNABORTED或者EPROTO错误,我们应该忽略这两个错误。
还有,阻塞 connfd 的边缘触发,如果不一次性读取一个事件上的数据,会干扰下一个事件,所以必须在读取数据的外部套一层循环,这样才能完整的处理数据。但是外层套循环之后会导致另外一个问题:处理完数据之后,程序会一直卡在 recv() 函数上,因为是阻塞 IO,如果没数据可读,它会一直等在那里,直到有数据可读。但是这个时候,如果用另一个客户端去连接服务器,服务器就不能受理这个新的客户端了。
epoll必须用非阻塞模式吗
- 对于监听的 sockfd,最好使用水平触发模式,边缘触发模式会导致高并发情况下,有的客户端会连接不上。如果非要使用边缘触发,可以用 while 来循环 accept()。
-
- 对于读写的 connfd,水平触发(LT)模式下,阻塞和非阻塞效果都一样,建议设置非阻塞,不用非阻塞也可以。
-
- 对于读写的 connfd,边缘触发(ET)模式下,必须使用非阻塞 IO,并要求一次性地完整读写全部数据。
eppoll的常见问题
考虑这种情况:多个连接同时到达,服务器的TCP就绪队列瞬间积累多个就绪连接,由于是边缘触发模式,epoll只会通知一次,accept只处理一个连接,导致TCP就绪队列中剩下的连接都得不到处理。
解决办法是用while循环抱住accept调用,处理完TCP就绪队列中的所有连接后再退出循环。如何知道是否处理完就绪队列中的所有连接呢?accept返回-1并且errno设置为EAGAIN就表示所有连接都处理完。