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

Posted

技术标签:

【中文标题】如何在 async_read_until 之后使用 asio 缓冲区进行连续读取【英文标题】:How to use asio buffer after async_read_until for consecutive reads 【发布时间】:2017-06-11 21:51:30 【问题描述】:

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

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

    PREAMBLE(2 个字节,“$M”) HEADER(3 个字节,包含有效载荷长度 N) PAYLOAD+CRC(N+1 字节)

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

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

async_read_until 操作成功后,streambuf 可能 包含超出分隔符的附加数据。一个应用程序将 通常将该数据留在 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();

收到 PREAMBLE 时调用 PREAMBLE 处理程序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 中的剩余字节,因为不能保证它们包含完整的消息。

【问题讨论】:

【参考方案1】:

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

我刚刚完成了blog post and codecast 关于使用 asio::streambuf 通过几个 Web 服务器执行简单的 HTTP GET 的内容。它可能会让您更好地了解如何使用async_read_until()async_read()

【讨论】:

以上是关于如何在 async_read_until 之后使用 asio 缓冲区进行连续读取的主要内容,如果未能解决你的问题,请参考以下文章

我应该如何在 boost::asio 的客户端应用程序中同时使用 async_read_until 和 async_write?

实现真正 boost::asio::async_read_until 的最简单方法

可以 boost::asio::async_read_until 检测到无穷无尽的流

boost::asio::async_read_until 与自定义匹配条件运算符重载混淆

Boost asio - 从标准输入异步读取已建立的字符数

Boost asio:无法确认文件传输