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
可以读取:
- 一个
'\n'
终止行,如 "some_data\n"
。这是最简单的情况,通过在与 asio::streambuf
关联的流上调用 std::getline
来处理
- 一个
'\n'
终止行加上下一行的开头,如 "some_data1\nbla"
。这可以用 std::getline
来处理;第二行的其余部分将在下一次完成处理程序调用时处理。
- 多行;在这种情况下,新读取的数据可能包含 2 个或更多
'\n'
。知道我不想冒险在不完整的线路上调用 std::getline
(我最终会在未来的数据包中获得),我怎么知道我应该进行多少次 std::getline
调用?我是否应该查看流缓冲区以检查是否存在多个 '\n'
?不做很多副本甚至可能吗?
更新:采用更好的方法。
恕我直言,你最好直接使用 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操作是否真的成功。
为什么要这样做:
- std::getline 可能会失败,即如果输入流已达到其限制,并且没有换行符存在
- 你可能在 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
我使用带有 '\n'
定界符的 asio::async_read_until
来支持从服务器获取字符数据的 TCP 客户端。
该服务器连续发送 '\n'
终止行;准确地说,它可以同时写入单行或多行的串联字符串。
根据文档,我了解到 asio::async_read_until
可以读取:
- 一个
'\n'
终止行,如"some_data\n"
。这是最简单的情况,通过在与asio::streambuf
关联的流上调用 - 一个
'\n'
终止行加上下一行的开头,如"some_data1\nbla"
。这可以用std::getline
来处理;第二行的其余部分将在下一次完成处理程序调用时处理。 - 多行;在这种情况下,新读取的数据可能包含 2 个或更多
'\n'
。知道我不想冒险在不完整的线路上调用std::getline
(我最终会在未来的数据包中获得),我怎么知道我应该进行多少次std::getline
调用?我是否应该查看流缓冲区以检查是否存在多个'\n'
?不做很多副本甚至可能吗?
std::getline
来处理
更新:采用更好的方法。
恕我直言,你最好直接使用 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操作是否真的成功。
为什么要这样做:
- std::getline 可能会失败,即如果输入流已达到其限制,并且没有换行符存在
- 你可能在 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