【问题标题】:Optimal buffer size with boost::asio in WindowsWindows 中使用 boost::asio 的最佳缓冲区大小
【发布时间】:2017-05-09 07:38:50
【问题描述】:

我将 boost::asio 用于需要从服务器接收可变长度消息的客户端(Windows 10、Visual C++)。 消息非常频繁(每秒超过 10 条消息),每条消息大约 40-100 字节。

我以这种方式使用streambufasync_read_some

void Client::readStart(void)
{
    boost::asio::streambuf::mutable_buffers_type buf = _inbox.prepare(std::max((size_t)1024, _socket->available()));

    // Start an asynchronous read and call readHandler when it completes or fails
    _socket->async_read_some(buf,
        boost::bind(&Client::readHandler,
        this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred));
}

即我正在尝试使用_inbox.prepare(std::max((size_t)1024, _socket->available())) 动态调整缓冲区大小,以便在由于客户端仍在处理以前的消息而累积许多消息时使用更大的缓冲区。

我发现我不能总是简单地使用像 _inbox.prepare(262144) 这样的更大缓冲区,因为 readHandler 会被大量数据调用,而不是更频繁地调用。

即使尝试动态分配缓冲区,我还是会遇到奇怪的延迟和数据累积。

这是我的日志:

  2017-05-09 09:02:25 <debug> Received 1024 bytes
  2017-05-09 09:02:25 <debug> Received 372 bytes
  2017-05-09 09:02:25 <debug> Received 844 bytes
  2017-05-09 09:02:25 <debug> Received 169 bytes
  2017-05-09 09:02:25 <debug> Received 1024 bytes
  2017-05-09 09:02:25 <debug> Received 379 bytes
  2017-05-09 09:02:25 <debug> Received 1385 bytes
  2017-05-09 09:02:25 <debug> Received 1421 bytes
  2017-05-09 09:02:25 <debug> Received 108 bytes
  2017-05-09 09:02:25 <debug> Received 1024 bytes
  2017-05-09 09:02:25 <debug> Received 1768 bytes
  2017-05-09 09:02:27 <debug> Received 65536 bytes
  2017-05-09 09:02:33 <debug> Received 65536 bytes
  2017-05-09 09:02:40 <debug> Received 65536 bytes
  2017-05-09 09:02:47 <debug> Received 65536 bytes
  2017-05-09 09:02:55 <debug> Received 65536 bytes
  2017-05-09 09:03:01 <debug> Received 65536 bytes
  2017-05-09 09:03:07 <debug> Received 65536 bytes
  2017-05-09 09:03:15 <debug> Received 65536 bytes
  2017-05-09 09:03:35 <debug> Received 65536 bytes
  2017-05-09 09:03:41 <debug> Received 65536 bytes
  2017-05-09 09:03:46 <debug> Received 65536 bytes
  2017-05-09 09:03:50 <debug> Received 65536 bytes
  2017-05-09 09:03:58 <debug> Received 65536 bytes
  2017-05-09 09:04:02 <debug> Received 65536 bytes
  2017-05-09 09:04:11 <info> Disconnected by remote host

如您所见,在 09:02:25 之前一切正常,然后数据开始累积,readHandler 很少被调用(每次调用之间 7-8 秒)并带有大量数据(65536 字节)。

最后,远程主机断开连接。断开连接是由于服务器向我的客户端发送的 TCP ZeroWindow Probes(使用 Wireshark 跟踪),即我的 TCP 缓冲区已满。

我真的不明白为什么readHandler 被如此不频繁地调用并且有这么多数据(我确信这不是客户端 100% CPU 的问题:客户端处理消息的速度很快CPU负载小).

编辑:

我正在使用以下代码在套接字上禁用 Nagle 算法:

boost::system::error_code error;
_socket->set_option(tcp::no_delay(true), error);

试图阻止 TCP/IP 堆栈对数据包进行分组,但没有帮助。

编辑 2:

我的处理代码中似乎存在瓶颈,因此我实际上接收数据的速度不够快,并且服务器的 Nagle 算法产生了下面 R. Joiny 描述的问题。

【问题讨论】:

  • 你能识别 Wireshark 中每个发送的以太网包吗?也许发送网络缓冲区决定将它们放在一个以太网包中,因此您的读取处理程序被调用的频率较低,但数据更多。我不确定,因为这通常只会以非常高的发送率出现,但检查一下不会有伤害
  • 由于路径 MTU、以太网等原因,您永远不会获得大于 1500 字节的入站 TCP 段。
  • @R.Joiny 65536 字节不能在单个以太网数据包中,EJP 是对的。其他地方肯定有问题。在任何情况下,我都会尝试更好地分析 Wireshark 跟踪并在发现任何相关内容时更新问题。
  • 当速度从 100M 变为 1G 时,以太网没有移动到巨型帧 (9kb) 吗?那就是忽略本地主机连接。

标签: c++ boost boost-asio


【解决方案1】:

我写的评论太长了,所以我决定回答,虽然我不是 100% 但 99% 肯定。

@MSalters 有一种观点(尽管即使是巨型帧也远小于 64K)。 TCP 包可以精确到 64K 大小,这在您的日志中明显显示。以太网MTU也不影响tcp包大小,因为如果Socket决定将所有tcp包打包成一个最大64K大小的包,当然它会通过多个以太网包发送,但是接收套接字在完成1个tcp包之后接收最后一个以太网包。

这是评论。我想说的是:

您的服务器快速发送数据 = 服务器程序快速写入套接字缓冲区。

  • 然后 Socket 决定使用计时器等待更多数据,如果在计时器处于活动状态时数据到达,则将数据添加到传出的 tcp 包中。由于您的发送速度非常快,几乎总是如此,因此 tcp 包达到了 64K 的最大大小。
  • 现在套接字发送包,因此操作系统将其拆分为 MTU 大小的部分。

操作系统正在将大于 MTU 的数据包传递给网络适配器,并且网络适配器驱动程序正在分解它们以使其适合 MTU。 (来源:Wireshark forums

  • 然后,接收套接字会获取所有这些以太网包,但会发现它们都是一个 TCP 包并等待直到接收到最后一个以太网包。
  • 它构建所有以太网小包的tcp包并将其写入接收缓冲区,这...
  • ...唤醒您的 async_read 处理程序。

这可能是interesting for you


问题的解决方案:

  • 如果您有权访问服务器的代码,请使用其套接字进行编辑 (Nagle)。

  • 否则,您必须定义一种带有结束标志字节或类似内容的协议,这样您就知道每个小包的结束位置。 (您仍然需要访问服务器:D)

  • 连接关闭的错误是您的客户端没有足够快地清空缓冲区的问题。但这无论如何都会发生,因为随着时间的推移发送的数据 x 总是相同的。 (10 次 ~ 100 Bytes per second 或 1 time 10000 Bytes per 10 seconds 是一样的)


编辑:

我推荐使用某物。就像一个线程安全的循环缓冲区,用于在 tcp_client 线程中写回数据并将其弹出到主线程中以计算数据。使用这种结构,我曾经能够接收 500 字节的数据并将其保存到 csv 中,该 csv 在 1ms 内发送给我。我在 BeagleBoneBlack 上使用 ArchLinux 和我的(也实现了 boost/asio)应用程序托管了一个 tcp 服务器。

【讨论】:

  • 感谢您的回答,看来和您描述的完全一样。不幸的是,我无法访问服务器代码,但我也在我的代码中发现了一个问题:我认为处理数据的速度足够快,相反,我自己的代码中一定存在瓶颈,这可能是服务器的 Nagle 算法将所有数据包塞在一起的原因:因为我没有足够快地确认数据包。因此,即使我无法访问服务器的代码,如果我能够消除我的瓶颈,它可能会阻止服务器对数据包进行分组
  • 是的,你是对的,如果你的 TCP Ack 向服务器发送你的缓冲区太小,他开始分组。
  • 我根据我曾经做过的项目的一些经验编辑了我的答案。也许它会有所帮助:)
  • 谢谢。与此同时,我能够缓解瓶颈,这是由于将接收到的消息存储到 Sqlite 数据库中,现在我再也没有收到完整的 65k 缓冲区,服务器不再断开我的连接,所以现在我可以尝试继续使用单线程异步模型(您的双线程模型,一个用于接收的线程和一个用于计算的线程对我来说更难以编写和维护“正确”,所以我更喜欢坚持使用单线程模型直到我可以)。
猜你喜欢
  • 1970-01-01
  • 2013-02-10
  • 1970-01-01
  • 2014-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多