如何在async_read_until后使用 asio 缓冲区进行连续读取



我正在从串行设备读取,其中必须特别请求每条消息。 例如,您发送请求并获得带有序列化有效负载的响应。

每条消息按顺序包含以下部分:

  1. 前导码(2 个字节,"$M")
  2. 标头(3 个字节,包含有效负载长度 N)
  3. 有效载荷+CRC (N+1 字节)

我对asio的方法是通过使用asio::async_read_until来检测消息的开始(前导码),然后使用asio::async_read来读取HEADER和PAYLOAD+ CRC的确切字节数。由于消息末尾没有静态模式,因此我无法使用async_read_until来读取完整的消息。

收到前导码后,async_read_until处理程序被调用,缓冲区包含前导码字节,并且可能包含来自 HEADER 和 PAYLOAD+CRC 的其他字节。 async_read_until的asio文档说:

成功async_read_until操作后,流布夫可以 包含分隔符以外的其他数据。应用程序将 通常将该数据保留在 Streambuf 中以供后续使用 async_read_until操作进行检查。

我将其解释为您应该只使用请求的字节,并将所有剩余字节保留在缓冲区中以供进一步读取。但是,所有连续读取都会阻塞,因为数据已经在缓冲区中,并且设备上没有留下任何东西。

读取被实现为一个小状态机processState,其中根据要读取消息的哪个部分注册不同的处理程序。所有读取都使用相同的buffer(asio::streambuf)完成。processState在无限循环中调用。

void processState() {
// register handler for incomming messages
std::cout << "state: " << parser_state << std::endl;
switch (parser_state) {
case READ_PREAMBLE:
asio::async_read_until(port, buffer, "$M",
std::bind(&Client::onPreamble, this, std::placeholders::_1, std::placeholders::_2));
break;
case READ_HEADER:
asio::async_read(port, buffer, asio::transfer_exactly(3),
std::bind(&Client::onHeader, this, std::placeholders::_1, std::placeholders::_2));
break;
case READ_PAYLOAD_CRC:
asio::async_read(port, buffer, asio::transfer_exactly(request_received->length+1),
std::bind(&Client::onDataCRC, this, std::placeholders::_1, std::placeholders::_2));
break;
case PROCESS_PAYLOAD:
onProcessMessage();
break;
case END:
parser_state = READ_PREAMBLE;
break;
}
// wait for incoming data
io.run();
io.reset();
}

接收前导码时调用前导码处理程序onPreamble

void onPreamble(const asio::error_code& error, const std::size_t bytes_transferred) {
std::cout << "onPreamble START" << std::endl;
if(error) { return; }
std::cout << "buffer: " << buffer.in_avail() << "/" << buffer.size() << std::endl;
// ignore and remove header bytes
buffer.consume(bytes_transferred);
std::cout << "buffer: " << buffer.in_avail() << "/" << buffer.size() << std::endl;
buffer.commit(buffer.size());
std::cout << "onPreamble END" << std::endl;
parser_state = READ_HEADER;
}

在此处理程序之后,不会调用其他处理程序,因为数据位于缓冲区中,并且设备上不会留下任何数据。

使用asio::streambuf的正确方法是什么,以便调用连续async_read的处理程序,我可以按状态机的顺序处理字节?我不想onPreamble处理剩余的字节,因为不能保证这些字节将包含完整的消息。

不需要调用onPreamble()处理程序中的buffer.commit()。调用buffer.consume()将按预期删除标头字节,并将剩余的字节(如果有)保留在asio::streambuf中以供下次读取。当您用要发送到远程方的数据填充 streambif 时,将使用 streambif 的prepare()commit()调用。

我刚刚完成了一篇关于使用 asio::streambuf 对一些 Web 服务器执行简单 HTTP GET 的博客文章和代码广播。它可能会让您更好地了解如何使用async_read_until()async_read()

最新更新