读取直到boost :: asio :: streambuf中的字符串分隔符

时间:2016-11-12 08:40:13

标签: c++ boost boost-asio

我想使用非常方便的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之前,您建议阅读什么?

3 个答案:

答案 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") 
  ];