使用异步I / O(epoll)进行消息排序

时间:2015-06-07 18:26:49

标签: c sockets scalability epoll

假设我已经实现了一个基于epoll的TCP服务器,其中每个线程运行的东西与下面非常相似(取自epoll手册页,其中kdpfd是epoll文件描述符,而监听器是一个正在侦听端口的套接字):

struct epoll_event ev, *events;
for(;;) {
    nfds = epoll_wait(kdpfd, events, maxevents, -1);
    for(n = 0; n < nfds; ++n) {
        if(events[n].data.fd == listener) {
            client = accept(listener, (struct sockaddr *) &local,
                            &addrlen);
            if(client < 0){
                perror("accept");
                continue;
            }
            setnonblocking(client);
            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = client;
            if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
                fprintf(stderr, "epoll set insertion error: fd=%d0,
                        client);
                return -1;
            }
        }
        else
            do_use_fd(events[n].data.fd);
    }
}

对于上面的do_use_fd(events[n].data.fd),我们想把我们收到的所有内容都写到stdout:

int do_use_fd(int fd) {
    int err;
    char buf[512];
    while ((err = read(fd, buf, 512)) > 0) {
        write(1, buf, err);
    }

    if (err == -1 && errno != EAGAIN && errno != EWOULDBLOCK)
       // do some error handling and return -1

    return 0;
}

现在,假设我有10k +连接,所有人都会在很长一段时间内向我发送大量消息。假设我的客户每隔几秒向我发送一条消息hello, my name is {client's name}。假设(某种程度上)此消息足够大,必须将其作为多个数据包传输。

因此,read(fd, buf, 512)偶尔会返回-1,并且错误表明它会阻塞。因此,我认为上述解决方案最终可能会出现以下输出:

hello, my nam
hello, my name is Pau
e is John Le
hello, my name is Geo
nnon
l McCartney
rge
hello, my name is Ringo
Starr
 Harrison

因为只要读取在一个连接上阻塞,另一个读取就可以在另一个连接上启动。相反,我希望打印以下内容:

hello, my name is John Lennon
hello, my name is Paul McCartney
hello, my name is George Harrison
hello, my name is Ringo Starr

是否有推荐的方法来处理此问题?一种选择是为每个连接保留一个缓冲区,并检查消息是否已完成,只有在发生这种情况时才打印。但是有10k +连接,这会是个好主意吗?一方面,有些东西告诉我这个解决方案不能很好地扩展。另一方面,如果消息只有500字节,连接10k,那么这个解决方案只占用5MB。

提前致谢。

1 个答案:

答案 0 :(得分:1)

我认为在你的情况下,每个连接使用一个缓冲区就行了。然而,根据不完整的消息创建缓冲区可能更优雅。这意味着你必须知道你的消息什么时候完成,所以你需要一个小协议,比如使用长度字段或终结符(并且可能超时以在一定时间后杀死不完整的消息)。这也可以保证不会分配未使用的内存,因为缓冲区可以在消息完成并传递完毕后立即释放。例如,您可以使用连接5元组作为键通过哈希映射访问这些缓冲区。如果您决定使用消息绑定标识符,这当然会产生额外的开销,您甚至可以从用于一次传输多条消息的单个tcp连接中解除消息。

如果您需要在这些消息中强制执行排序,则必须详细说明您的情况,因为在许多情况下排序是一个棘手的问题。

编辑:对不起,我现在有很多事要做,所以我不能早点回答。你是正确的,使用基于连接的方法更容易。基于消息的连接使用稀疏性越有利。如果您可以期望所有连接始终接收消息,那么这只是一个开销。如果连接有时闲置一段时间,可能会大大减少内存使用量。另请注意,您的应用程序内存使用量不再随客户端数量而变化,而是消息数量,这通常很好,因为消息速率通常会有所不同。您对TCP流的排序也是正确的。只要您通过连接一次只发送一条完整的消息,TCP就会确保订购。一些应用程序(例如,HTTP2)重用相同的TCP连接以同时发送多个消息。在这种情况下,TCP将没有用处,因为消息片段以未指定的顺序到达,您需要对它们进行解复用(例如,通过HTTP2中的stream-id)。