我使用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所描述的问题。
答案 0 :(得分:1)
我写的评论太长了所以我决定回答,虽然我不是100%但99%肯定。
@MSalters在那里有一种观点(尽管甚至巨型帧也远小于64K)。 TCP包可以精确到64K大小,这显然在您的日志中显示。以太网MTU也不会影响tcp包大小,因为如果Socket决定将所有tcp包打包成一个最大64K大小的包,当然它会通过多个以太网包发送,但是接收套接字完成了1 tcp包之后收到最后一个以太网包。
这是评论。我想说的是:
您的服务器快速发送数据=服务器程序快速写入套接字缓冲区。
操作系统正在将大于MTU的数据包传递到网络适配器,并且网络适配器驱动程序将它们分解,以便它们适合MTU。 (来源:Wireshark forums)
这可能是interesting for you。
如果您有权访问服务器的代码,请使用其套接字进行编辑(Nagle)。
如果不是,您必须定义一种带有结束标志字节或类似内容的协议,因此您知道每个小包的结束位置。 (您仍然需要访问服务器:D)
连接关闭错误是客户端没有足够快地清空缓冲区的问题。但这会以任何一种方式发生,因为随时间发送的数据x总是相同的。 (10次~100次/秒或1次10000字节/ 10秒是相同的)
我建议使用......就像一个线程安全的循环缓冲区,用于在tcp_client线程中写回数据并在主线程中弹出它来计算数据。通过这种结构,我曾经能够接收并保存500字节的数据到1s内发送给我的csv。我使用ArchLinux和我(也使用boost / asio实现)托管tcp服务器的应用程序在BeagleBoneBlack上做了所有这些。