我们知道epoll有两种触发模式:水平触发(LT)和边缘触发(ET)
LT模式
- 若数据可读,epoll返回可读事件 若开发者没有把数据完全读完,epoll会不断通知数据可读,直到数据全部被读取。
- 若socket可写,epoll返回可写事件,而且是只要socket发送缓冲区未满,就一直通知可写事件。
- 优点是对于read操作比较简单,只要有read事件就读,读多读少都可以。
ET模式
- 若socket可读,返回可读事件。
- 若没有把所有数据读取完毕,epoll不会再次通知epoll read事件,也就是说存在一种隐患,如果在读到可读事件时,没有读取全部数据,那么可能导致epoll再也不会通知该socket的read事件。
- ET模式下,只有socket的状态发生变化时才会通知,也就是读取缓冲区由无数据到有数据时通知read事件,发送缓冲区由满变成未满通知write事件。
简单说明一下两种触发模式的特点后,来说一下我遇到的问题:我自己写了一个http服务器,使用了epoll加线程池的reactor模式,并且只支持HTTP短连接,所以当浏览器请求一个较复杂的web页面时,服务器会接收到较多的并发连接,这些是前提。今天在服务器代码基本完成后,进行了测试。
在测试中我发现:在浏览器发出较多请求或者频繁刷新页面时,总会有几个请求得不到响应。所以就抓包来分析了一下,抓包结果可以看到未响应的请求事实上已经和服务器完成了三次握手,而我在服务器中打log却并没有看到服务器与该请求建立了连接。所以分析可能是accept函数没有成功,经测试发现实际上accept并没有被调用。
这里要说明一下,accept实际上与TCP的三次握手并没有关联,它只是从监听套接字的监听队列中取出一个套接字,当然了,前提是监听队列非空。这就可以联想到,抓包发现浏览器已经与服务器完成了三次握手并建立了连接,也就是说现在的监听队列非空,然而accept却没有被调用,这显然产生了矛盾。
问题出在哪里呢?我想到可能是epoll中的监听套接字没有触发EPOLLIN事件,想到我对于监听套接字设置了ET模式,那么可能会发生下面的情况:有多个连接同时到达监听端口,这时监听队列由空变为非空,EPOLLIN事件被触发,但是我在程序中只进行了一次accept,所以只有一个连接被接受,之后尽管监听队列非空,由于在ET模式下,也不会再次触发EPOLL事件了。
想到这里,我赶紧去把代码中监听套接字的触发模式改为LT模式,果然bug被消除了。所以可以得到结论:在epoll中,监听套接字的触发模式最好设置为LT模式,当然了也可以使用ET,不过要使用while循环调用accept,直到监听队列为空。