11

我正在编写一个处理图像(大数据)的应用程序服务器。在将图像数据发送回客户端时,我试图最小化副本。我需要发送给客户端的已处理图像位于从 jemalloc 获得的缓冲区中。我想到将数据发送回客户端的方法是:

1)简单的写调用。

// Allocate buffer buf.
// Store image data in this buffer.
write(socket, buf, len);

2)我通过mmap而不是jemalloc获取缓冲区,尽管我认为jemalloc已经使用mmap创建了缓冲区。然后我打了一个简单的电话来写。

buf = mmap(file, len);  // Imagine proper options.
// Store image data in this buffer.
write(socket, buf, len);

3)我像以前一样通过mmap获得一个缓冲区。然后我使用 sendfile 发送数据:

buf = mmap(in_fd, len);  // Imagine proper options.
// Store image data in this buffer.
int rc;
rc = sendfile(out_fd, file, &offset, count);
// Deal with rc.

看起来(1)和(2)可能会做同样的事情,因为 jemalloc 可能首先通过 mmap 分配内存。不过,我不确定(3)。这真的会带来任何好处吗?本文关于 Linux 零复制方法的图 4表明,可以使用 sendfile 防止进一步的复制:

没有数据被复制到套接字缓冲区中。相反,只有带有数据位置和长度信息的描述符被附加到套接字缓冲区。DMA 引擎将数据直接从内核缓冲区传递到协议引擎,从而消除了剩余的最终副本。

如果一切顺利,这似乎是一场胜利。我不知道我的映射缓冲区是否算作内核缓冲区。我也不知道什么时候可以安全地重新使用这个缓冲区。由于 fd 和 length 是唯一附加到套接字缓冲区的内容,因此我假设内核实际上将此数据异步写入套接字。如果确实如此, sendfile 的返回意味着什么?我怎么知道什么时候重新使用这个缓冲区?

所以我的问题是:

  1. 将大缓冲区(在我的情况下为图像)写入套接字的最快方法是什么?图像保存在内存中。
  2. 在映射文件上调用 sendfile 是个好主意吗?如果是,有什么问题?这甚至会导致任何胜利吗?
4

2 回答 2

5

看来我的猜想是对的。我从这篇文章中得到了我的信息。引用它:

此外,这些网络写入系统调用(包括 sendfile)可能并且在许多情况下确实会在方法调用通过 TCP 发送的数据得到确认之前返回。一旦所有数据写入套接字缓冲区(sk buff)并被推送到 TCP 写入队列,这些方法就会返回,从那时起 TCP 引擎可以单独管理。换句话说,在 sendfile 返回最后一个 TCP 发送窗口时,实际上并没有发送到远程主机,而是排队。在支持分散-聚集 DMA 的情况下,没有单独的缓冲区来保存这些字节,而是缓冲区(sk 缓冲区)只保存指向文件内容所在的 OS 缓冲区缓存页面的指针。如果我们在 sendfile 返回后立即修改与最后一个 TCP 发送窗口中的数据相对应的文件内容,这可能会导致竞争条件。因此,TCP 引擎可能会将新写入的数据发送到远程主机,而不是我们最初打算发送的数据。

如果来自映射文件的缓冲区甚至被认为是“支持 DMA”的,那么在没有来自实际客户端的明确确认(通过网络)的情况下,似乎无法知道何时可以安全地重新使用它。我可能不得不坚持简单的写调用并产生额外的副本。有一篇论文(也来自文章)有更多细节。

编辑这篇关于拼接调用的文章也显示了这些问题。引用它:

请注意,在将数据从 mmap'ed 缓冲区拼接到网络套接字时,无法确定所有数据何时已发送。即使 splice() 返回,网络堆栈也可能尚未发送所有数据。因此重用缓冲区可能会覆盖未发送的数据。

于 2013-11-16T21:24:32.050 回答
1

对于情况 1 和 2 - 您标记为 // 将图像数据存储在此缓冲区中的操作是否需要任何转换?它只是从内存复制到 buf 吗?

如果只是普通的copy,可以直接在jemalloc得到的指针上使用write。

假设 img 是从 jemalloc 获得的指针,而 size 是图像的大小,只需运行以下代码:

int result;
int sent=0;
while(sent<size) {
    result=write(socket,img+sent,size-sent);
    if(result<0) {
        /* error handling here */
        break;
    }
    sent+=result;
}

它可以正常工作以阻止 I/O(默认行为)。如果您需要以非阻塞方式编写数据,您应该可以自己重新编写代码,但是现在您有了想法。

对于案例 3 - sendfile 用于将数据从一个描述符发送到另一个描述符。这意味着,例如,您可以将数据从文件直接发送到 tcp 套接字,而无需分配任何额外的缓冲区。因此,如果您要发送给客户端的图像在文件中,只需使用 sendfile。如果你有它在内存中(因为你以某种方式处理了它,或者只是生成了它),使用我之前提到的方法。

于 2013-11-16T20:48:10.517 回答