在 Windows 中使用 boost::asio 实现最佳缓冲区大小



我正在使用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探针(使用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 描述的问题。

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

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

这是评论。我想说的是:

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

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

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

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

这对你来说可能很有趣。


问题的解决方案:

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

  • 如果没有,你必须定义一种带有结束标志字节或类似东西的协议,这样你就知道每个小包在哪里结束。(您仍然需要访问服务器:D)

  • 连接关闭的错误是客户端清空缓冲区的速度不够快的问题。但无论哪种方式,这都会发生,因为随时间推移发送的数据 x 总是相同的。(10 次 ~ 100 字节每秒或 1 次 10000 字节/10 秒相同)


编辑:

我建议使用 sth. 就像线程安全的循环缓冲区一样,用于写回tcp_client线程中的数据并将其弹出到主线程中以计算数据。使用这种结构,我曾经能够接收 500 字节的数据并将其保存到 csv,该 csv 在 1 毫秒内发送给我。我在带有ArchLinux的BeagleBoneBlack和我的(也使用boost/asio实现的)托管tcp服务器的应用程序上完成了所有这些工作。

最新更新