【问题标题】:boost::asio::streambuf - how to reuse buffer?boost::asio::streambuf - 如何重用缓冲区?
【发布时间】:2016-09-19 06:31:12
【问题描述】:

我正在实现使用 asio socket.async_read() 和 boost::asio::async_read_until() 方法从套接字异步读取数据的 TCP 服务器。两者都使用相同的处理程序从 boost::asio::streambuf 读取数据。

通过 async_read() 调用的完美处理程序:

void handle_read(const boost::system::error_code& ec, std::size_t ytes_transferred) )
{
    m_request_buffer.commit(bytes_transferred);
    boost::asio::streambuf::const_buffers_type rq_buf_data = m_request_buffer.data();
    std::vector<uint8_t> dataBytes(boost::asio::buffers_begin(rq_buf_data), boost::asio::buffers_begin(rq_buf_data) + bytes_transferred);

    //process data here

    m_request_buffer.consume(bytes_transferred);
    bytes_transferred = 0;
}

根据数据处理,我的服务器可能会关闭连接或通过同一套接字继续读取。

但是,如果从第二次 boost::asi::async_read_until() 调用中调用了 handle_read(),我会在 dataBytes 中得到多个零,然后有效数据会消失。

我尝试了一个简单的测试用例,发现在将数据写入streambuf后,commit()+consume()后streambuf中的数据仍然保留之前的缓冲区。

那么,有没有办法清除 boost::asio::streambuf 中的数据并在 boost::asio::async_read_until() 中重用它?

Live Coliru

如果使用 USE_STREAM=1 编译,现场示例可以正常工作。但是 std::istream 与 buffer consume() 相比有何不同?

【问题讨论】:

    标签: c++ boost boost-asio


    【解决方案1】:

    当使用对streambuf 进行操作的Boost.Asio 操作或使用streambuf 的流对象(例如std::ostreamstd::istream)时,将正确管理底层输入和输出序列。如果为操作提供缓冲区,例如将prepare() 传递给读操作或将data() 传递给写操作,则必须显式处理commit()consume()

    示例中的问题是它违反了 API 约定,导致未初始化的内存被提交给输入序列。 commit() 文档指出:

    需要前面调用 prepare(x) where x &gt;= n,并且没有修改输入或输出序列的干预操作。

    prepare()commit() 之间使用std::ostream 违反了这个约定,因为它会修改输入序列:

    // Prepare 1024 bytes for the output sequence.  The input sequence is
    // empty.
    boost::asio::streambuf streambuf;
    streambuf.prepare(1024);
    
    // prepare() and write to the output sequence, then commit the written
    // data to the input sequence.  The API contract has been violated.
    std::ostream ostream(&streambuf);
    ostream << "1234567890";
    
    // Commit 10 unspecified bytes to the input sequence.  Undefined
    // behavior is invoked.
    streambuf.commit(10);
    

    这是一个完整的示例 demonstrating 使用带有注释 cmets 的 streambuf:

    #include <iostream>
    #include <vector>
    #include <boost/asio.hpp>
    
    int main()
    {
      std::cout << "with streams:" << std::endl;
      {
        boost::asio::streambuf streambuf;
    
        // prepare() and write to the output sequence, then commit the written
        // data to the input sequence.  The output sequence is empty and
        // input sequence contains "1234567890".
        std::ostream ostream(&streambuf);
        ostream << "1234567890";
    
        // Read from the input sequence and consume the read data.  The string
        // 'str' contains "1234567890".  The input sequence is empty, the output
        // sequence remains unchanged.
        std::istream istream(&streambuf);
        std::string str;
        istream >> str;
        std::cout << "str = " << str << std::endl;
    
        // Clear EOF bit.
        istream.clear();
    
        // prepare() and write to the output sequence, then commit the written
        // data to the input sequence.  The output sequence is empty and
        // input sequence contains "0987654321".
        ostream << "0987654321";
    
        // Read from the input sequence and consume the read data.  The string
        // 'str' contains "0987654321".  The input sequence is empty, the output
        // sequence remains unchanged.
        istream >> str;
        std::cout << "str = " << str << std::endl;
      }
    
      std::cout << "with streams and manual operations:" << std::endl;
      {
        boost::asio::streambuf streambuf;
    
        // prepare() and write to the output sequence, then commit the written
        // data to the input sequence.  The output sequence is empty and
        // input sequence contains "1234567890".
        std::ostream ostream(&streambuf);
        ostream << "1234567890";
    
        // Copy 10 bytes from the input sequence.  The string `str` contains
        // "1234567890".  The output sequence is empty and the input
        // sequence contains "1234567890".
        auto data = streambuf.data();
        std::string str(boost::asio::buffers_begin(data),
                        boost::asio::buffers_begin(data) + 10);
        std::cout << "str = " << str << std::endl;
    
        // Consume 10 bytes from the input sequence.  The input sequence is
        // now empty.
        streambuf.consume(10);
    
        // prepare() and write to the output sequence, then commit the written
        // data to the input sequence.  The output sequence is empty and
        // input sequence contains "0987654321".
        ostream << "0987654321";
    
        // Copy 10 bytes from the input sequence.  The string `str` contains
        // "0987654321.  The output sequence is empty and the input
        // sequence contains "0987654321".    
        data = streambuf.data();
        str.assign(boost::asio::buffers_begin(data),
                   boost::asio::buffers_begin(data) + 10);
        std::cout << "str = " << str << std::endl;
    
        // Consume 10 bytes from the input sequence.  The input sequence is
        // now empty.
        streambuf.consume(10);
      }
    }
    

    输出:

    with streams:
    str = 1234567890
    str = 0987654321
    with streams and manual operations:
    str = 1234567890
    str = 0987654321
    

    有关 streambuf 使用的更多信息,请考虑阅读this 答案。

    【讨论】:

    • 感谢您的评论。最后我查看了 boost::asio::async_read_until() 实现,发现它确实是 st​​reambuf_.commit(bytes_transferred);。所以,我不应该在我的处理程序中调用 commit() 。但是如果使用 boost::asio::ip::tcp::socket.async_read() 我的处理程序应该调用 commit() 将字符从输出序列移动到输入序列。
    • @drus 不,你不应该。不要搞乱任何这些操作。我所做的只是调用buffer.consume(buffer.size() + 1),因为这样可以保证完全清除缓冲区。
    • 是的,我切换到使用 boost::asio::async_read() 和 boost::asio::async_read_until() 并且它按预期工作。正如我所说,这两种方法都调用 streambuf.commit()。
    • 为了澄清,buffer.consume 的最佳实践是使用来自boost::asio::async_read* 的返回值。例如。 size_t bytes = boost::asio::async_read_until()buffer.consume(bytes)
    • 我想知道哪种方法更安全、更快速地清除和重用现有缓冲区或重新创建缓冲区?
    猜你喜欢
    • 1970-01-01
    • 2018-04-03
    • 1970-01-01
    • 1970-01-01
    • 2017-09-02
    • 1970-01-01
    • 1970-01-01
    • 2011-08-06
    • 1970-01-01
    相关资源
    最近更新 更多