boost asio async_read()似乎跳过了一些空值

时间:2018-11-08 14:36:35

标签: c++ c++14 boost-asio

通过简单的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时,我看到的流不是我期望的前四个字节。

任何指针我在做什么错了?

2 个答案:

答案 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个字节,但随后我死了” )对我来说还不清楚,在某些情况下我无法正确地重新同步。

无论如何,我在此过程中学到了很多东西,我推荐练习。