boost::asio::streambuf - 如何重复使用缓冲区?

7

我正在实现一个TCP服务器,使用asio socket.async_read()和boost :: asio :: async_read_until()方法来异步读取socket中的数据。两者都使用相同的处理程序从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;
}

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

但是,如果handle_read()从第二个boost::asi::async_read_until()调用中调用,则在dataBytes中会得到许多零,然后才会出现有效数据。

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

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

Live Coliru

如果使用USE_STREAM=1编译,则实时示例可以正常工作。但是std::istream与缓冲区consume()有何不同?

1个回答

11

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

该示例中的问题在于违反了 API 合同,导致未初始化的内存被提交到输入序列。 commit() 文档说明:

  

需要先调用 prepare(x)(其中 x >= 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);
这是一个完整的例子,演示如何使用带有注释的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 的更多信息,请考虑阅读 回答。


1
感谢您的评论。最后,我查看了boost::asio::async_read_until()的实现,发现它确实使用了streambuf_.commit(bytes_transferred)。因此,在我的处理程序中不应该调用commit()。但是,在使用boost::asio::ip::tcp::socket.async_read()的情况下,我的处理程序应该调用commit()将字符从输出序列移动到输入序列。 - drus
@drus 不,你不应该这样做。不要乱动任何那些操作。我所做的只是调用 buffer.consume(buffer.size() + 1),因为这能够保证完全清空缓冲区。 - user562566
是的,我转而使用了boost::asio::async_read()和boost::asio::async_read_until(),并且它们按预期工作。正如我所说,这两种方法都调用streambuf.commit()。 - drus
为了澄清,buffer.consume 的最佳实践是使用 boost::asio::async_read* 的返回值。例如:size_t bytes = boost::asio::async_read_until()buffer.consume(bytes) - PStarczewski
我想知道哪种方法更安全、更快速,是清除并重用现有缓冲区还是重新创建缓冲区? - triclosan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接