2017-05-09 78 views
1

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

我以这种方式使用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 

正如你所看到的,直到9时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描述的问题。

+0

您可以识别Wireshark中的每个发送的以太网包吗?也许发送网络缓冲区决定将它们放在一个以太网包中,所以你的读取处理程序被调用的次数减少,但有更多的数据。我不确定,因为这通常只会以非常高的发送速率出现,但要检查这不会造成影响 –

+0

由于路径MTU,以太网等,您永远不会获得大于1500字节的入站TCP段。 – EJP

+0

@ R.Joiny 65536字节不能位于单个以太网数据包中,EJP是正确的。其他地方一定有问题。在任何情况下,我都会尝试更好地分析Wireshark跟踪,如果发现任何相关的问题,请更新问题。 –

回答

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


解决方案的问题:

  • 如果你有访问服务器的代码,其插座做你的编辑(内格尔)。

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

  • 连接关闭的错误是您的客户端没有足够快地清空缓冲区的问题。但是这种情况会发生,因为随着时间的推移发送的数据x总是相同的。 (每秒或1时的10倍〜100字节每10秒可10000个字节是相同的)


编辑:

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

+0

谢谢你的回答,它看起来正如你所描述的那样。不幸的是,我无法访问服务器代码,但是我也在代码中发现问题:我认为处理数据的速度足够快,但是在我自己的代码中必定存在瓶颈,可能是服务器的Nagle算法将所有数据包填充到一起的原因:因为我没有足够快地确认数据包。因此,即使我无法访问服务器的代码,如果我能够消除我的瓶颈,它可能会阻止服务器对数据包进行分组。 –

+1

是的,您的观点是正确的,如果您的TCP Ack消息将服务器视为太小的缓冲区他开始分组。 –

+1

我编辑了我的答案,并从我的项目中获得了一些经验,我做过一次。也许它可以帮助:) –