使用epoll边沿触发的套接字上的数据太多

时间:2018-07-02 11:28:19

标签: c++ linux sockets networking tcp

这种情况是这样的:一个过程是在多个套接字上使用epoll,所有套接字都设置为非阻塞且被边缘触发;然后在一个套接字上发生EPOLLIN事件,然后我们开始在其fd上读取数据,但是问题是传入的数据太多,并且在while循环读取数据时,recv的返回值始终是大于0。因此应用程序卡在其中,无法读取数据并且无法继续运行。

任何想法我应该如何处理?

constexpr int max_events = 10;
constexpr int buf_len = 8192;
....

epoll_event events[max_events];
char buf[buf_len];
int n;
auto fd_num = epoll_wait(...);
for(auto i = 0; i < fd_num; i++) {
    if(events[i].events & EPOLLIN) {
        for(;;) {
            n = ::read(events[i].data.fd, buf, sizeof(buf));
            if (errno == EAGAIN)
                break;
            if (n <= 0)
            {
                on_disconnect_(events[i].data.fd);
                break;
            }
            else
            {
                on_data_(events[i].data.fd, buf, n);
            }
        }
    }
}

1 个答案:

答案 0 :(得分:3)

使用边沿触发模式时,必须在一个recv调用中读取数据,否则可能会饿死其他套接字。这个问题已经在许多博客中发表过,例如Epoll is fundamentally broken

确保用户空间接收缓冲区的大小至少与内核接收套接字缓冲区的大小相同。这样,您可以在一个recv调用中读取整个内核缓冲区。

此外,您可以循环方式处理就绪的套接字,以使控制流不会陷入一个套接字的recv循环中。在与内核空间大小相同的用户空间接收缓冲区中,这种方法最有效。例如:

auto n = epoll_wait(...);
for(int dry = 0; dry < n;) {
    for(auto i = 0; i < n; i++) {
        if(events[i].events & EPOLLIN) {
            // Do only one read call for each ready socket
            // before moving to the next ready socket.
            auto r = recv(...);
            if(-1 == r) {
                if(EAGAIN == errno) {
                    events[i].events ^= EPOLLIN;
                    ++dry;
                }
                else
                    ; // Handle error.
            }
            else if(!r){
                // Process client disconnect.
            }
            else {
                // Process data received so far.
            }
        }
    }
}

可以进一步改进此版本,以避免在每次迭代时扫描整个events数组。


在您的原始帖子do {} while(n > 0);中,它是不正确的,并导致无休止的循环。我认为这是一个错字。