通过简单的asio TCP对话,我有点发疯了。
我有一个服务器和一个客户端。我使用长度前缀的消息。客户端发送“一个”,服务器发送“两个”。所以这就是我所看到的:
客户端发送,服务器接收00 00 00 03 6F 6E 65
(== 0x0003一个)。
服务器通过发送00 00 00 03 74 77 6F
(== 0x0003两个)进行响应。
现在这是非常奇怪的地方(下面的代码)。如果客户端读取四个字节,我希望它得到00 00 00 03
。如果读数为7,我希望看到00 00 00 03 74 77 6F
。 (实际上,它将读取四个(长度标题),然后读取三个(正文)。)
但是我实际看到的是,虽然我一次读7个,我确实看到了00 00 00 03 74 77 6F
,但是如果我只问4个,我看到了74 77 6F 03
。这对我来说毫无意义。
这是我用来接收它的代码(减去一些打印语句等):
const int kTcpHeaderSize = 4;
const int kTcpMessageSize = 2048;
std::array<char, kTcpMessageSize + kTcpHeaderSize> receive_buffer_;
void TcpConnection::ReceiveHeader() {
boost::asio::async_read(
socket_, boost::asio::buffer(receive_buffer_, kTcpHeaderSize),
[this](boost::system::error_code error_code,
std::size_t received_length) {
if (error_code) {
LOG_WARNING << "Header read error: " << error_code;
socket_.close(); // TODO: Recover better.
return;
}
if (received_length != kTcpHeaderSize) {
LOG_ERROR << "Header length " << received_length
<< " != " << kTcpHeaderSize;
socket_.close(); // TODO: Recover better.
return;
}
uint32_t read_length_network;
memcpy(&read_length_network, receive_buffer_.data(),
kTcpHeaderSize);
uint32_t read_length = ntohl(read_length_network);
// Error: read_length is in the billions.
ReceiveBody(read_length);
});
}
请注意,kTcpHeaderSize
为4。如果将其更改为7(这没有意义,但仅出于实验目的),我看到了我期望的7个字节的流。当它是4时,我看到的流不是我期望的前四个字节。
任何指针我在做什么错了?
答案 0 :(得分:1)
根据我在您的代码中看到的内容,它应该可以按照https://github.com/terraform-providers/terraform-provider-google/issues/2234进行工作:
异步操作将继续,直到满足以下条件之一:
- 提供的缓冲区已满。也就是说,传输的字节等于缓冲区大小的总和。
- 发生错误。
但是请参阅底部的说明:
此重载等效于调用:
boost::asio::async_read( s, buffers, boost::asio::transfer_all(), handler);
看起来async_read documentation条件可能是唯一要检查的东西。
尝试使用transfer_all条件,如果它可以工作,请报告transfer_exactly上的问题。
答案 1 :(得分:0)
@sergiopm提出的使用transfer_all
的建议很好,我敢肯定它会有所帮助。另一个问题涉及异步发送/接收函数中的缓冲区生存期。显然,我对某些事物的使用寿命以及我需要它们存活的时间感到困惑,因此我不时地覆盖事物。那可能比transfer_all
更重要,但是我仍然很高兴为@sergiopm表示赞赏,帮助我前进。
意图是拥有一个我可以声明的简单tcp客户端或服务器,将其交给回调,然后继续前进,知道我只能注意那些回调。
我很确定这样的东西一定存在(成千上万次)。如果您认为对于此任务有比asio更好的库,那么对我和以后的那些人都可以在下面随意评论(即,我所涉及的代码要少得多)。原则约束是,由于多种语言和服务,我们需要拥有有线协议。否则,我们会遇到诸如“库X是否有用于语言Y的模块?”之类的问题。
顺便说一句,对我来说有趣的是,基本上我发现的每个示例都进行长度前缀编码,而不是数据包编码的开始/结束。长度前缀确实很容易实现,但是除非我完全误会,否则它会遭受重新同步的麻烦:如果流被中断(“我将向您发送100个字节,这是前50个字节,但随后我死了” )对我来说还不清楚,在某些情况下我无法正确地重新同步。
无论如何,我在此过程中学到了很多东西,我推荐练习。