2017-06-11 103 views
1

我正在从串行设备中读取每个消息必须特别请求的地方。例如。您发送请求并获得序列化有效负载的响应。如何在连续读取async_read_until后使用asio缓冲区

每个消息包含在顺序包括以下部分:

  1. 前导码(2个字节, “$ M”)
  2. HEADER(3个字节,包含有效载荷长度N)
  3. 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中以供随后的async_read_until操作检查。

我解释这是因为你应该只消耗请求的字节,并将所有剩余的字节留在缓冲区中作进一步读取。 但是,由于数据已经存在于缓冲区中并且设备上没有任何内容,因此所有连续的读取块都将被阻止。

该读取是作为小型状态机processState实现的,其中根据要读取的消息的哪一部分来注册不同的处理程序。所有读数都使用相同的bufferasio::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中的剩余字节,因为不能保证这些字节将包含完整的消息。

回答

0

您不需要在onPreamble()处理程序中调用buffer.commit()。调用buffer.consume()将按照您的预期去除标题字节,并在asio::streambuf中留下剩余的字节(如果有的话)以供下一次读取。当您填写要发送给远程方的数据时,会使用流缓冲的prepare()commit()调用。

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

相关问题