【问题标题】:What is a good buffer size for socket programming?套接字编程的良好缓冲区大小是多少?
【发布时间】:2011-02-18 03:42:18
【问题描述】:

我们正在使用 .Net 和套接字。服务器使用Socket.Sender(bytes[]) 方法,因此它只发送整个有效负载。另一方面,我们是消费数据的客户。 Socket.Receive(buffer[])。在 Microsoft(和其他公司)的所有示例中,它们似乎都坚持使用 8192 的缓冲区大小。我们使用了这个大小,但时不时地向客户端发送超过此缓冲区大小的数据。

有没有办法确定服务器的 send 方法向我们发送了多少数据?最佳缓冲区大小是多少?

【问题讨论】:

    标签: .net sockets


    【解决方案1】:

    不幸的是,Jon Skeet 的回答遗漏了大部分内容 - 发送缓冲区大小,以及您正在写入的管道的 bandwidth-delay product

    如果您尝试使用单个套接字通过大管道发送数据,并且希望 TCP 填充该管道,则需要使用与管道的带宽延迟乘积相等的发送缓冲区大小。否则,TCP 将不会填充管道,因为它不会始终留下足够的“正在传输的字节”。

    TCP 为您处理数据包丢失,这意味着它必须有缓冲区来保存您提供给它的数据,直到它可以确认数据已被另一方正确接收(通过 TCP ACK)。没有缓冲区是无限的,因此在某个地方必须有一个限制。该限制是任意的,您可以选择任何您想要的,但您需要确保它足够大以处理连接的 BDP。

    考虑一个缓冲区大小正好为:1 字节的 TCP 套接字。您正在尝试通过比特率为 1 gbit/sec 且单向延迟为 1 ms 的连接发送数据。

    1. 您将第一个字节提供给 TCP 套接字。
    2. 套接字阻止任何进一步的写入调用(发送缓冲区已满)。
    3. TCP 发送一个字节。 gig eth 适配器的批量传输速率为每字节 8 ns,因此传输时间可以忽略不计。
    4. 1 毫秒后,接收方获得 1 个字节。
    5. 1 毫秒后,您会收到来自接收器的确认。
    6. TCP 会从发送缓冲区中删除第一个字节,因为它已确认接收方正确获得了该字节。
    7. 发送缓冲区解除阻塞,因为它有空间。
    8. 你给 TCP 套接字你的第二个字节...
    9. 等等。

    此连接传输数据的速度有多快?发送 1 个字节需要 2 毫秒,因此,此连接的比特率为 500 字节/秒 == 4 kbit/秒。

    哎呀。

    假设连接速度为 1 Gb,单向延迟平均为 10 毫秒。往返时间(也就是从您的套接字发送数据包到它收到该数据包的 ack 并因此知道要发送更多数据的时间)通常是延迟的两倍。

    因此,如果您有一个 1 Gb 的连接和 20 毫秒的 RTT,那么该管道有 1 Gb/秒 * 20 毫秒 == 2.5 兆字节的数据,如果它被完全利用的话。

    如果您的 TCP 发送缓冲区小于 2.5 兆字节,那么一个套接字将永远无法充分利用管道 - 您永远无法从套接字中获得千兆位/秒的性能。

    如果您的应用程序使用许多套接字,则所有 TCP 发送缓冲区的总大小必须为 2.5 MB,才能充分利用这个假设的 1 吉比特/20 毫秒 RTT 管道。例如,如果您使用 8192 字节的缓冲区,则需要 306 个并发 TCP 套接字来填充该管道。

    编辑问题:

    计算 BDP 只是将带宽乘以 往返延误,注意单位。

    因此,如果您的连接速度为 1 Gb/秒,往返时间为 20 毫秒,那么发生的情况是您乘以位/秒 * 秒,因此秒数抵消,剩下的是位.转换为字节,您就有了缓冲区大小。

    • 1 gbit/sec * 20 ms == 1 * gbit/sec * 0.02 sec == (1 * 0.02) gbit
    • 0.020 gbit == 20 MBit。
    • 20 Mbit * 1 字节 / 8 位 == 20 / 8 MBytes == 2.5 MBytes。

    因此,我们的 TCP 缓冲区需要设置为 2.5 MB 以使这个组成的管道饱和。

    【讨论】:

    • 感谢您的解释。所以一个好的缓冲区大小在 8Ko 和 2.5 MB 之间。 ;-)
    • 应该注意的是,这实际上只适用于 TCP 套接字,因为在这里对方只会发送尽可能多的数据,以适应接收方套接字缓冲区,然后暂停数据流并等待ACK 到达。在 UDP 的情况下,唯一重要的是代码永远不会让套接字缓冲区空运行,然后您还将利用任何可用的线路速度。当然,如果缓冲区更大,您可以提前生成更多数据,因此时序将不那么重要。
    • @antiduh 这是如何计算的? 1 gigabit/sec * 20 milliseconds == 2.5 megabytes of data
    • @kuchi - 我为你写了一些编辑来展示数学。干杯!
    【解决方案2】:

    8192 将是理想的。如果您有超过此大小的数据,最好以恒定长度的数据包发送数据。

    服务器发送的数据大小可以使用WINSOCK中的recv函数来检查,该函数有一个参数可以给出缓冲区的长度。

    【讨论】:

    • 8192 不是“理想的”,而是一个普遍有用的数字。以恒定长度的数据包发送数据并不是更好:最好尽可能快地流式传输所有数据。发送的数据大小不一定与对端recv() 返回的值有任何关系。这是一个流协议。
    【解决方案3】:

    与 Microsoft 无关,但我只是在尝试使用 TCP 端口(不是 Unix 域套接字)的 C++ 线程回显服务器来查看吞吐量。使用各种缓冲区大小对 4M 输入进行计时,结果如下:

    1024 - real 0m0,102s; user  0m0,018s; sys   0m0,009s
    2048 - real 0m0,112s; user  0m0,017s; sys   0m0,009s
    8192 - real 0m0,163s; user  0m0,017s; sys   0m0,007s
     256 - real 0m0,101s; user  0m0,019s; sys   0m0,008s
      16 - real 0m0,144s; user  0m0,016s; sys   0m0,010s
    

    似乎读取 1024 字节块减少了 TCP 开销,而处理时间(只是回显输入)不受缓冲区大小的影响。 8192 字节似乎很高,而非常低的值(如 16)也不好。

    【讨论】:

      【解决方案4】:

      这取决于您的协议。如果您期望消息超过 8192 字节,那么您应该相应地增加缓冲区大小。但请记住,此缓冲区大小仅适用于对 Receive 的一次调用。如果您真的想要/需要,您可以多次循环Receive 并将接收到的数据复制到任意大的数据结构或缓冲区中。

      另外请记住,最好反复调用Receive,直到您确认已阅读给定消息的所有数据;即使单个消息小于您的缓冲区大小,它仍然可能无法通过单个 Receive 调用全部检索到。

      【讨论】:

        【解决方案5】:

        即使您发送的数据多于该数据,也可能无法通过一次调用 Receive 获得。

        您无法确定服务器发送了多少数据 - 这是一个数据流,而您一次只是读取数据块。您可以在一个 Send 调用中读取服务器发送的部分内容,或者您​​可以在一个 Receive 调用中读取来自两个 Send 调用的数据。 8K 是一个合理的缓冲区大小 - 不会太大以至于您会浪费大量内存,也不会太小以至于您必须使用大量浪费的 Receive 调用。 4K 或 16K 也很可能也可以……我个人不会开始超过 16K 的网络缓冲区——我怀疑你很少会填满它们。

        您可以尝试使用一个非常大的缓冲区并记录每次调用中接收到的字节数 - 这会让您大致了解可用的字节数 - 但它不会真正展示了使用较小缓冲区的效果。您对使用 8K 缓冲区有什么顾虑?如果是性能,您是否有任何证据表明您的代码的这方面是性能瓶颈?

        【讨论】:

        • 如果可能的话,我想增加缓冲区大小。我们正在考虑将其设置为 32K,但我担心上述情况,即使我在一次发送中发送,接收命令也无法获取所有内容。
        • 对不起,我忘了说我们是在阻塞模式下做的。不过我不认为它有什么不同。
        • @uriDium:您应该编写代码,使其不依赖于从一次发送中接收所有内容。这不是 TCP 使用的模型——它是一个 stream 模型。如果您想将该流分割成不同的消息,您应该使用分隔符或为每条消息编写一个长度前缀,以便客户端知道要读取多少。
        • 我们使用 0128 作为字节数的前缀来拉出缓冲区。这有助于围绕信息建立良好的框架。我们接触的大多数供应商都熟悉这种做法。你可以做类似的事情。
        • @Tarek:是的,这就是我在“或者为每条消息写一个长度前缀,以便客户端知道要阅读多少”中的建议
        猜你喜欢
        • 2015-05-01
        • 2014-04-23
        • 2012-10-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-06
        • 1970-01-01
        相关资源
        最近更新 更多