写入框架数据而无需额外的write()成本

时间:2015-04-07 17:31:18

标签: c sockets unix

所以我在TCP套接字上发送数据,前缀为数据大小,如下所示:

write(socket, &length, sizeof(length));
write(socket, data, length);

(注意:我有Unix网络编程书中描述的包装器写入功能,我正在检查错误等。以上只是为了简化这个问题。)

现在,我的经验是将数据分解为多次写入会导致显着减速。通过创建自己的缓冲区,然后发送一个大块,我已经成功地加快了速度。

然而,在上述情况下,数据可能非常大(比如1 Gig)。我不想创建1千兆字大+4字节的缓冲区,只是为了能够进行一次write()调用。有没有办法做类似的事情:

 write(socket, &length, data, sizeof(length) + length)

没有提前支付大量内存分配的价格?我想我可以预先分配一个与写缓冲区大小相同的块,并不断发送(下面的代码有错误,即在某些情况下应该发送& chunk + 4,但这只是想法):

length += 4;

char chunk[buffer_size];
var total = 0;

while (total < length)
{
     if (total < 4)
     {
         memcpy(&chunk, &length, 4);
         total += 4;
     }

     memcpy(&chunk, data + total, min(buffer_size, length - total));
     write(sock, &chunk, min(buffer_size, length - total));

     total += min(buffer_size, length - total);
}

但在这种情况下,我不知道写的缓冲区大小究竟是什么(有没有API可以获得它?)我也不知道这是否是一个合适的解决方案。< / p>

3 个答案:

答案 0 :(得分:4)

有一个选项可以做到这一点。它将告知您的网络层您将要发送更多数据,并且您想要缓冲而不是尽快发送它。

setsockopt(sock_descriptor, IPPROTO_TCP, TCP_CORK, (char *)&val, sizeof(val));

val是一个int,应该是0或1,&#34; cork&#34;在,你的网络层将尽可能缓冲的东西,只发送完整的数据包,你可能想要&#34;弹出软木塞&#34;和&#34;软木塞&#34;再次处理你需要在插座上进行的下一批传输。

您的想法是正确的,这样可以省去实施它的麻烦,因为它已经在网络堆栈中完成了。

答案 1 :(得分:3)

我建议您查看writev()(有关完整详情,请参阅man writev。)

这允许您一次发送多个缓冲区,只需一次调用。举个简单的例子,一次发送两个块(一个用于长度,一个用于数据):

struct iovec bits[2];

/* First chunk is the length */
bits[0].iov_base = &length;
bits[0].iov_len = sizeof(length);

/* Second chunk is the payload */
bits[1].iov_base = data;
bits[1].iov_base = length;

/* Send two chunks at once */
writev(socket, bits, 2);

如果需要使用可变数量的块(可能需要动态分配struct iov数组),它会变得更复杂,但优点是,如果你的块很大,你可以避免复制它们,只需操作指针/长度对,它们要小得多。

答案 2 :(得分:2)

我认为你的假脱机解决方案已经出现在正确的轨道上。我认为buffer_size应该大于网络堆栈内部使用的缓冲区。这样,您可以最大限度地减少每次写入开销的数量,而无需分配巨大的缓冲区。换句话说,通过为底层网络子系统提供比它能够同时处理的数据更多的数据,它可以以最快的速度运行,花费大部分时间来移动数据,而不是等待提供更多的数据。

最佳buffer_size值可能因系统而异。我将从1MB开始,从那里上下做一些实验,看看哪种方法效果最好。您可以使用sysctl调用提取和调整值,以获取系统上使用的当前内部缓冲区大小。阅读this以获取建议的技巧。您也可以使用getsockopt(..., SO_MAX_MSG_SIZE, ...)之类的内容,如here所述。

以太网数据包的大小最大可达64K,因此任何大于64K的数据都可能就足够了。阅读有关maximum transmission unit(MTU)大小的信息,以了解网络堆栈的最低层正在做什么,并且不要忘记MTU随网络接口而变化,而不是进程或内核。

请注意,MTU可能会随着从服务器到数据目的地的路径而变化。您可以使用ifconfigtraceroute/tracepath来发现它。通过网络,链中的每个环节都很薄弱。 ;)