读取直到boost::asio::streambuf中的字符串分隔符
Read until a string delimiter in boost::asio::streambuf
我想使用非常方便的Boost async_read_until来读取消息,直到我得到rnrn
分隔符。
我喜欢使用这个分隔符,因为它很容易调试telnet和多行命令。我只是用两行新行来表示命令的结束。
我这样调用async_read_until
:
void do_read()
{
boost::asio::async_read_until(m_socket,
m_input_buffer,
"rnrn",
std::bind(&player::handle_read, this, std::placeholders::_1, std::placeholders::_2));
}
我的处理器现在看起来是这样的:
void handle_read(boost::system::error_code ec, std::size_t nr)
{
std::cout << "handle_read: ec=" << ec << ", nr=" << nr << std::endl;
if (ec) {
std::cout << " -> emit on_disconnectn";
} else {
std::istream iss(&m_input_buffer);
std::string msg;
std::getline(iss, msg);
std::cout << "dump:n";
std::copy(msg.begin(), msg.end(), std::ostream_iterator<int>(std::cout, ", "));
std::cout << std::endl;
do_read();
}
}
我想使用std::getline
就像这个例子一样,但在我的系统上,这保留了r
字符。如您所见,如果我连接到服务器并写入hello
加上两个CRLF,我将得到以下转储服务器端:
handle_read: ec=system:0, nr=9
dump:
104, 101, 108, 108, 111, 13,
^^^ r here
顺便说一下,这也将在缓冲区中保留下一个新行。所以我认为std::getline
不会为我做这项工作。
我搜索了一种方便有效的方法来从boost::asio::streambuf
读取,直到我得到这个rnrn
分隔符。因为我使用async_read_until
一次,当处理程序被调用时,缓冲区应该有确切的和完整的数据,不是吗?在我得到rnrn
之前,你建议我读什么?
async_read_until()
操作将所有读取的数据提交到流buf的输入序列中,bytes_transferred
值将包含到并包括第一个分隔符的字节数。虽然该操作可以读取超出分隔符的更多数据,但可以使用bytes_transferred
和分隔符大小来仅提取所需的数据。例如,如果cmd1rnrncmd2
可以从套接字读取,并且async_read_until()
操作是用rnrn
分隔符启动的,那么streambuf的输入序列可以包含cmd1rnrncmd2
:
,--------------- buffer_begin(streambuf.data())
/ ,------------ buffer_begin(streambuf.data()) + bytes_transferred
/ / - delimiter.size()
/ / ,------ buffer_begin(streambuf.data()) + bytes_transferred
/ / / ,-- buffer_end(streambud.data())
cmd1rnrncmd2
因此,可以通过:
从流buf中提取cmd1
到字符串中。// Extract up to the first delimiter.
std::string command{
boost::asio::buffers_begin(streambuf.data(),
boost::asio::buffers_begin(streambuf.data()) + bytes_transferred
- delimiter.size()};
// Consume through the first delimiter.
m_input_buffer.consume(bytes_transferred);
下面是一个完整的示例,演示直接从streambuf的输入序列构造std::string
:
#include <functional> // std::bind
#include <iostream>
#include <boost/asio.hpp>
const auto noop = std::bind([]{});
int main()
{
using boost::asio::ip::tcp;
boost::asio::io_service io_service;
// Create all I/O objects.
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
tcp::socket socket1(io_service);
tcp::socket socket2(io_service);
// Connect sockets.
acceptor.async_accept(socket1, noop);
socket2.async_connect(acceptor.local_endpoint(), noop);
io_service.run();
io_service.reset();
const std::string delimiter = "rnrn";
// Write two commands from socket1 to socket2.
boost::asio::write(socket1, boost::asio::buffer("cmd1" + delimiter));
boost::asio::write(socket1, boost::asio::buffer("cmd2" + delimiter));
// Read a single command from socket2.
boost::asio::streambuf streambuf;
boost::asio::async_read_until(socket2, streambuf, delimiter,
[delimiter, &streambuf](
const boost::system::error_code& error_code,
std::size_t bytes_transferred)
{
// Verify streambuf contains more data beyond the delimiter. (e.g.
// async_read_until read beyond the delimiter)
assert(streambuf.size() > bytes_transferred);
// Extract up to the first delimiter.
std::string command{
buffers_begin(streambuf.data()),
buffers_begin(streambuf.data()) + bytes_transferred
- delimiter.size()};
// Consume through the first delimiter so that subsequent async_read_until
// will not reiterate over the same data.
streambuf.consume(bytes_transferred);
assert(command == "cmd1");
std::cout << "received command: " << command << "n"
<< "streambuf contains " << streambuf.size() << " bytes."
<< std::endl;
}
);
io_service.run();
}
输出:received command: cmd1
streambuf contains 8 bytes.
先回答你的问题:
缓冲区应该有准确和完整的数据,不是吗?
是的,它将包含所有数据,包括"rnrn"
你建议我读什么直到我得到rnrn?
你所做的已经足够好了。您只需要忽略每个命令末尾的附加'r'。您可以在从stream
读取数据时执行此操作,也可以让命令处理程序(或为您执行命令处理的任何程序)处理该操作。我的建议是将额外的'r'的删除推迟到命令处理程序。
你可能需要这样的内容:
#include <iostream>
#include <string>
#include <sstream>
void handle_read()
{
std::stringstream oss;
oss << "key : valuernkey2: value2rnkey3: value3rnrn";
std::string parsed;
while (std::getline(oss, parsed)) {
// Check if it'a an empty line.
if (parsed == "r") break;
// Remove the additional 'r' here or at command processor code.
if (parsed[parsed.length() - 1] == 'r') parsed.pop_back();
std::cout << parsed << std::endl;
std::cout << parsed.length() << std::endl;
}
}
int main() {
handle_read();
return 0;
}
如果你的协议允许你发送空命令,那么你将不得不改变逻辑,并注意两个连续的空新行。
您实际希望解析什么?
当然,你可以使用你的领域的知识,然后说
std::getline(iss, msg, 'r');
在更高的层次上,考虑解析您需要的内容:
std::istringstream linestream(msg);
std::string command;
int arg;
if (linestream >> command >> arg) {
// ...
}
甚至更好,考虑一个解析器生成器:
std::string command;
int arg;
if (qi::phrase_parse(msg.begin(), msg.end(), command_ >> qi::int_, qi::space, command, arg))
{
// ...
}
其中command_
可能像
qi::rule<std::string::const_iterator> command_ = qi::no_case [
qi::lit("my_cmd1") | qi::lit("my_cmd2")
];
- 使用 char 分隔符解析C++中的字符串,但将可重复的字符保留为每个解析的子字符串 (C++ STL) 中的分隔符
- 字符串开头的分隔符
- 尝试将 c 字符串数组与分隔符连接起来
- 根据新的行分隔符从字符串中删除子字符串
- 同一分隔符之间的多个子字符串
- 字符串流分隔符
- 使用正则表达式c++从单词和分隔符之间的字符串中提取所有子字符串
- C++获取两个分隔符之间的字符串并替换它
- 是否存在像C++中那样带有分隔符的C#原始字符串
- 如何从字符串开头到第二个分隔符中提取子字符串?
- 如何使用两个不同的分隔符拆分字符串
- 将子字符串提取到最后 n 个分隔符C++
- 基于 C++ 中的多个字符串分隔符拆分字符串
- 读取 iostream,直到找到字符串分隔符
- 带有字符串分隔符或两个字符的fstream
- 读取直到boost::asio::streambuf中的字符串分隔符
- 使用两个字符串分隔符选择文本文件的一部分
- C++std::ifstream读取字符串分隔符
- 使用多个字符串分隔符拆分字符串
- 使用字符串分隔符(标准C++)解析(拆分)C++中的字符串