在Windows

时间:2017-05-09 07:38:51

标签: c++ boost boost-asio

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

我以streambuf方式使用async_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的问题&# #39; s:客户端快速处理消息并且CPU负载很小)

修改

我使用此代码禁用套接字上的Nagle算法:

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

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

编辑2:

似乎我的处理代码中存在瓶颈,所以我实际上并没有足够快地接收数据,服务器的Nagle算法产生了R. Joiny所描述的问题。

1 个答案:

答案 0 :(得分: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次/秒或1次10000字节/ 10秒是相同的)

编辑:

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