我正在从串行设备读取,其中必须特别请求每条消息。 例如,您发送请求并获得带有序列化有效负载的响应。
每条消息按顺序包含以下部分:
- 前导码(2 个字节,"$M")
- 标头(3 个字节,包含有效负载长度 N)
- 有效载荷+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()
。