2

我使用asio::async_read_untilwith '\n'delimiter 来支持从服务器获取字符数据的 TCP 客户端。该服务器连续发送'\n'终止的线路;准确地说,它可以一次写入单行或多行的串联字符串。

从文档中,我了解到asio::async_read_until可以阅读:

  • 一个'\n'终止的行,例如"some_data\n". 这是最简单的情况,通过std::getline对与asio::streambuf
  • 一个'\n'终止的行加上下一行的开头,例如"some_data1\nbla". 这可以用std::getline; 第二行的其余部分将在下一次完成处理程序调用中处理。
  • 多行;在这种情况下,新读取的数据可能包含 2 个或更多'\n'. 我怎么知道std::getline我应该打多少电话,知道我不想冒险拨打std::getline不完整的线路(我最终会在未来的数据包中获得)?我应该查看流缓冲区以检查是否存在多个'\n'?甚至可以不做很多副本吗?
4

3 回答 3

2

从这里的文档:

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);
于 2015-11-26T13:02:44.823 回答
2

这个答案与接受的答案有关:

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

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

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

为什么要这样做:

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

2017 年 8 月 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

于 2017-08-22T19:16:08.563 回答
0

更新:采用更好的方法。

恕我直言,你最好async_read_some直接使用而不是读取直到操作。这需要更少的操作,让您更好地控制缓冲区处理,并且可以减少您必须对数据制作的副本数量。您可以使用该asio::streambuf实现,但也可以使用 a 来执行此操作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();
  });
}
于 2015-11-26T11:55:48.540 回答