asio::async_read_until:处理多行的稳健而优雅的方式

asio::async_read_until: robust and graceful way of handling multiple lines

我使用带有 '\n' 定界符的 asio::async_read_until 来支持从服务器获取字符数据的 TCP 客户端。 该服务器连续发送 '\n' 终止行;准确地说,它可以同时写入单行或多行的串联字符串。

根据文档,我了解到 asio::async_read_until 可以读取:

更新:采用更好的方法。

恕我直言,你最好直接使用 async_read_some 而不是 read until 操作。这需要更少的整体操作,让您更好地控制缓冲区处理,并且可以减少您必须制作的数据副本的数量。您可以使用 asio::streambuf 实现,但您也可以使用 vector<char> 实现,例如:

vector<char> buffer(2048); // whatever size you want, note: you'll need to somehow grow this if message length is greater...
size_t content = 0; // current content

// now the read operation;
void read() {
  // This will cause asio to append from the last location
  socket.async_read_some(boost::asio::buffer(buffer.data() + content, buffer.size() - content), [&](.. ec, size_t sz) {
    if (ec) return; // some error
    // Total content in the vector
    content += sz;
    auto is = begin(buffer);
    auto ie = next(is, content); // end of the data region

    // handle all the complete lines.
    for (auto it = find(is, ie, '\n'); it != ie; it = find(is, ie, '\n')) {
      // is -> it contains the message (excluding '\n')
      handle(is, it);
      // Skip the '\n'
      it = next(it);
      // Update the start of the next message
      is = it;
    }
    // Update the remaining content
    content -= distance(begin(buffer), is);
    // Move the remaining data to the begining of the buffer
    copy(is, ie, begin(buffer));
    // Setup the next read
    read();
  });
}

来自此处的文档:

http://www.boost.org/doc/libs/1_59_0/doc/html/boost_asio/reference/async_read_until/overload1.html

如果流缓冲区已经包含一个换行符,将调用处理程序而不对流执行 async_read_some 操作。

因此,当您的处理程序执行时,您只能执行一个 getline()。一旦 getline 返回并且您已完成处理,只需从处理程序中再次调用 async_read_until

示例:

void handler(const boost::system::error_code& e, std::size_t size)
{
  if (e)
  {
    // handle error here
  }
  else
  {
    std::istream is(&b);
    std::string line; 
    std::getline(is, line);
    do_something(line)
    boost::asio::async_read_until(s, b, '\n', handler);
  }
}

// Call the async read operation
boost::asio::async_read_until(s, b, '\n', handler);

此答案与 相关:

我强烈建议在循环中调用 std::getline() 并测试 return 值。

while (std::getline(is, line)) {
  ...
  do_something(line);
}

std::getline return是对istream引用的引用,可以隐式转换为bool,表示getline操作是否真的成功。

为什么要这样做:

  1. std::getline 可能会失败,即如果输入流已达到其限制,并且没有换行符存在
  2. 你可能在 asio 的 streambuf 中有不止一行。如果您在处理完第一行后盲目地重新开始读取,您最终可能会超出 streambuf 的内存限制(或者 streambuf 不断增长)。

2017-08-23 更新:

bytes_transferred 实际上为您提供了在底层缓冲区中找到分隔符的位置。可以通过简单地向上转换 streambuf 并从中创建一个字符串来利用它。

void client::on_read(const std::error_code &ec, size_t bytes_transferred) {

    if (ec) {
        return handle_error(ec);
    }

    std::string line(
        asio::buffer_cast<const char*>(m_rxbuf.data()),
        bytes_transferred
    );

    // todo: strip of trailing delimiter

    m_rxbuf.consume(bytes_transferred); // don't forget to drain

    handle_command(line); // leave restarting async_read_until to this handler
}

不是将数据从 streambuf 复制到字符串中,而是可以从中创建一个 string_view,或者用 std::string 替换底层的 streambuf 并砍掉 bytes_transferred而不是从缓冲区中消耗。

干杯, Argonaut6x