我想使用非常方便的Boost async_read_until来读取消息,直到我得到\r\n\r\n
分隔符。
我喜欢使用这个分隔符,因为它很容易使用telnet进行调试并生成多行命令。我只是通过两个新行发出命令结束信号。
我这样打电话给async_read_until
:
void do_read()
{
boost::asio::async_read_until(m_socket,
m_input_buffer,
"\r\n\r\n",
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_disconnect\n";
} 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
,直到我得到这个\r\n\r\n
分隔符。由于我一次只使用async_read_until
一次,当调用处理程序时,缓冲区应该具有完全和完整的数据不是吗?在我得到\r\n\r\n
之前,您建议阅读什么?
答案 0 :(得分:10)
async_read_until()
操作将读取的所有数据提交到streambuf的输入序列中,<img src="<?php echo base_url('application/assests/images/logo.png'); ?>" alt="" />
值将包含直到并包括第一个分隔符的字节数。虽然操作可以读取分隔符之外的更多数据,但可以使用bytes_transferred
和分隔符大小来仅提取所需数据。例如,如果可以从套接字读取bytes_transferred
,并且使用cmd1\r\n\r\ncmd2
的分隔符启动async_read_until()
操作,则streambuf的输入序列可以包含{ {1}}:
\r\n\r\n
因此,可以通过以下方式从streambuf中提取cmd1\r\n\r\ncmd2
字符串:
,--------------- buffer_begin(streambuf.data())
/ ,------------ buffer_begin(streambuf.data()) + bytes_transferred
/ / - delimiter.size()
/ / ,------ buffer_begin(streambuf.data()) + bytes_transferred
/ / / ,-- buffer_end(streambud.data())
cmd1\r\n\r\ncmd2
以下是直接从streambuf输入序列构建cmd1
的完整示例demonstrating:
// 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);
输出:
std::string
答案 1 :(得分:0)
首先回答你的问题:
缓冲区应该具有确切的完整数据吗?
是的,它将包含所有数据,包括&#34; \ r \ n \ r \ n \ n&#34;
在我得到\ r \ n \ r \ n?
之前,您建议阅读什么?
你做得很好。你只需要忽略额外的&#39; \ r&#39;在每个命令的末尾。这可以在从stream
读取时执行,也可以由命令处理器(或为您执行命令处理的任何操作)处理。我的建议是推迟删除额外的&#39; \ r&#39;到命令处理器。
您可能需要以下内容:
#include <iostream>
#include <string>
#include <sstream>
void handle_read()
{
std::stringstream oss;
oss << "key : value\r\nkey2: value2\r\nkey3: value3\r\n\r\n";
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;
}
如果你的协议允许你发送空命令,那么你将不得不改变逻辑,并留意连续2个空的新行。
答案 2 :(得分: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")
];