c - 使用异步 I/O 的消息排序 (epoll)

标签 c sockets scalability epoll

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

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),假设我们要将收到的所有内容写入标准输出:

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 个连接,所有这些连接都在很长一段时间内向我发送了大量消息。假设我的客户每隔几秒向我发送一条消息 你好,我的名字是 {client's name}。假设(以某种方式)此消息足够大,必须将其作为多个数据包进行传输。

因此,read(fd, buf, 512) 有时可能会返回 -1,并带有指示它将阻塞的 errno。因此,我认为上述解决方案最终可能会得到如下输出:

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。

提前致谢。

最佳答案

我认为在您的情况下,每个连接使用一个缓冲区就可以了。然而,为每个不完整的消息创建一个缓冲区可能会更优雅。这意味着您必须以某种方式知道消息何时完成,因此您需要一个小型协议(protocol),例如使用长度字段或终止符(以及可能的超时以在一定时间后杀死不完整的消息)。这也将保证不会分配未使用的内存,因为缓冲区可以在消息完成并传递后立即释放。例如,您可以使用连接 5 元组作为键通过 HashMap 访问这些缓冲区。如果您决定使用消息绑定(bind)标识符(这当然会产生额外的开销),您甚至可以从用于一次传输多个消息的单个 TCP 连接中分离消息。

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

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

关于c - 使用异步 I/O 的消息排序 (epoll),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30696993/

相关文章:

asp.net - 使用httpSendRequest c++上传文件

java - 将字节数组从java程序发送到c++

elasticsearch - 适用于具有大量聚合的大型集群的ElasticSearch设置

java - 将 Terracotta 与 Java EE 应用服务器集群结合起来有意义吗?

c - 调整输出(也许是自动换行?)

c - Gcc 和 g++ 在写时不同()

c - 平台 ABI 中的类型规范

Java套接字与Qt的连接

ios - 如何在 iOS 上设置 UDP 的“不分段”标志?

Azure 服务总线可扩展性