Linux IO多路复用 epoll 心得(2)

考虑这种情况: TCP连接被客户端夭折, 即在服务器调用accept之前, 客户端主动发送RST终止连接, 导致刚刚建立的连接从就绪队列中移出, 如果套接口被设置成阻塞模式, 服务器就会一直阻塞在accept调用上, 直到其他某个客户建立一个新的连接为止。但是在此期间, 服务器单纯地阻塞在accept调用上, 就绪队列中的其他描述符都得不到处理。

解决办法是把监听套接口设置为非阻塞, 当客户在服务器调用accept之前中止某个连接时, accept调用可以立即返回-1, 这时源自Berkeley的实现会在内核中处理该事件, 并不会将该事件通知给epoll, 而其他实现把errno设置为ECONNABORTED或者EPROTO错误,我们应该忽略这两个错误。

(2)ET模式下accept存在的问题

考虑这种情况: 多个连接同时到达, 服务器的TCP就绪队列瞬间积累多个就绪连接, 由于是边缘触发模式, epoll只会通知一次, accept只处理一个连接, 导致TCP就绪队列中剩下的连接都得不到处理。

解决办法是用while循环抱住accept调用, 处理完TCP就绪队列中的所有连接后再退出循环。如何知道是否处理完就绪队列中的所有连接呢? accept返回-1并且errno设置为EAGAIN就表示所有连接都处理完。

综合以上两种情况, 服务器应该使用非阻塞地accept, accept在ET模式下的正确使用方式为:

while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote, (size_t *)&addrlen)) > 0) {

handle_client(conn_sock);

}

if (conn_sock == -1) {

if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)

perror("accept");

}

3. 总结

从ET的处理过程中可以看到, ET的要求是需要一直读写, 直到返回EAGAIN, 否则就会遗漏事件。而LT的处理过程中, 直到返回EAGAIN不是硬性要求, 但通常的处理过程都会读写直到返回EAGAIN, 但LT比ET多了一个开关EPOLLOUT事件的步骤。LT的编程与poll/select接近,符合一直以来的习惯,不易出错。ET的编程可以做到更加简洁,某些场景下更加高效,但另一方面容易遗漏事件,容易产生bug。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/797e42ca55d3ef845caadc614dc7eaef.html