使用epoll时需要将socket设为非阻塞吗?

我觉得只有边沿触发才必须设置为非阻塞。

边沿触发的问题:

1. sockfd 的边缘触发,高并发时,如果没有一次处理全部请求,则会出现客户端连接不上的问题。不需要讨论 sockfd 是否阻塞,因为 epoll_wait() 返回的必定是已经就绪的连接,所以不管是阻塞还是非阻塞,accept() 都会立即返回。

2. 阻塞 connfd 的边缘触发,如果不一次性读取一个事件上的数据,会干扰下一个事件,所以必须在读取数据的外部套一层循环,这样才能完整的处理数据。但是外层套循环之后会导致另外一个问题:处理完数据之后,程序会一直卡在 recv() 函数上,因为是阻塞 IO,如果没数据可读,它会一直等在那里,直到有数据可读。但是这个时候,如果用另一个客户端去连接服务器,服务器就不能受理这个新的客户端了。

3. 非阻塞 connfd 的边缘触发,和阻塞版本一样,必须在读取数据的外部套一层循环,这样才能完整的处理数据。因为非阻塞 IO 如果没有数据可读时,会立即返回,并设置 errno。这里我们根据 EAGAIN 和 EWOULDBLOCK 来判断数据是否全部读取完毕了,如果读取完毕,就会正常退出循环了。

总结一下:

1. 对于监听的 sockfd,最好使用水平触发模式,边缘触发模式会导致高并发情况下,有的客户端会连接不上。如果非要使用边缘触发,可以用 while 来循环 accept()。

2. 对于读写的 connfd,水平触发模式下,阻塞和非阻塞效果都一样,建议设置非阻塞。

3. 对于读写的 connfd,边缘触发模式下,必须使用非阻塞 IO,并要求一次性地完整读写全部数据。

扫描二维码关注公众号,回复: 9121908 查看本文章

作者:oscarwin
链接:https://www.zhihu.com/question/23614342/answer/261242885
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

用个故事来开始我们的答案,故事就是man文档里的,我只是搬运一下。

Level-triggered and edge-triggered:

1. The file descriptor that represents the read side of a pipe (rfd) is registered on the epoll instance.

2. A pipe writer writes 2 kB of data on the write side of the pipe.

3. A call to epoll_wait(2) is done that will return rfd as a ready file descriptor.

4. The pipe reader reads 1 kB of data from rfd.

5. A call to epoll_wait(2) is done.

这五步过程我们可以简化为这样一个场景:

  1. A与B通过套接字在文件描述符rfd上建立了一个连接。
  2. A在epoll上注册一个文件描述符rfd。
  3. B向这个文件描述符写2kB的数据。
  4. epoll_wait告诉A:B发了数据过来,你可以读了。
  5. A粗心大意只读取了1kB数据,还有1kB的数据忘记读了,就以为自己读完了。
  6. 这时候epoll_wait调用完成。

If the rfd file descriptor has been added to the epoll interface using the EPOLLET (edge-triggered) flag, the call to epoll_wait(2) done in step 5 will probably hang despite the available data still present in the file input buffer; meanwhile the remote peer might be expecting a response based on the data it already sent. The reason for this is that edge-triggered mode delivers events only when changes occur on the monitored file descriptor. So, in step 5 the caller might end up waiting for some data that is already present inside the input buffer. In the above example, an event on rfd will be generated because of the write done in 2 and the event is consumed in 3. Since the read operation done in 4 does not consume the whole buffer data, the call to epoll_wait(2) done in step 5 might block indefinitely.

故事继续:

第五步做完后,如果epoll_wait设置为了边缘触发,尽管文件描述符中还有数据,但是epoll_wait不会被该文件描述中剩余的数据触发,除非有新的数据到达。而这剩余的1kB的数据是B告诉A:你读完整条消息后给我回个消息,我再给你继续发后面的数据。但是因为A的粗心,没有读到后面的信息,所以A也就没有给B回复,于是B就不停的等啊,等啊,等到海枯石烂。A也在等B的消息,心想:mmp,只发1kB就不发了,话也不说完。于是等啊,等啊,等到沧海桑田。

之所以出现这样的情况呢?是因为A不知道B到底给他发了多少信息,导致A不知道有没有读完。为了避免这样的事情再次发生,他们就决定采用非阻塞IO了,A收到信息后反复读,直到出现EAGAIN错误。

回到题主的问题,使用epoll时是否要设置非阻塞IO。

在边缘触发模式下,为了避免上面提及的这种情况的发生,应该设置成非阻塞的IO。

在水平触发模式下,使用阻塞式IO结构简单,但是阻塞式IO可能会导致在某个IO上阻塞一段时间(区别是:上面提到边缘触发是可能会产生永久阻塞),而其实这时候是可以去处理其他已经准备好的IO,这样就导致CPU利用率不是特别高。因此,水平触发模式下,如果需要提高并发 ,避免恶意攻击,也应该采用非阻塞IO。

转自:https://www.zhihu.com/question/23614342

发布了47 篇原创文章 · 获赞 20 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/boiled_water123/article/details/104161471
今日推荐