2016-11-12 58 views
4

我想使用非常方便的Boost async_read_until来读取消息,直到获得\r\n\r\n分隔符。直到在boost :: asio :: streambuf中使用字符串分隔符

我喜欢使用这个分隔符,因为它很容易使用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

回答

6

async_read_until()操作提交读入流缓冲的输入序列中的所有数据,并且bytes_transferred值将包含的字节数直到并包括第一分隔符。虽然操作可能会读取分隔符之外的更多数据,但可以使用bytes_transferred和分隔符大小来仅提取所需的数据。例如,如果cmd1\r\n\r\ncmd2是可用于从一个插座被读取,并且一个async_read_until()操作与的\r\n\r\n定界符开始,则流缓冲的输入序列可能包含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 

这样,一个可以提取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); 

下面是一个完整的例子demonstrating直接构建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 = "\r\n\r\n"; 

    // 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. 
+0

如果实际的缓冲区被读到'cmd1 \ r \ n \ r \ ncmd2 \ r \ n \ r \ n'怎么办?只有当发送第三个命令时,第二个命令才会被解析。所以我们需要循环,而有一些数据可用不是吗? – markand

+0

@markand No.最优雅的解决方案是发出另一个'async_read_until'操作。由于缓冲区已经包含分隔符,因此在尝试I/O之前会检测到完成条件,并且完成处理程序将准备好运行。 –

+0

啊,你是对的,非常感谢:) – markand

0

先回答你的问题:

缓冲区应该有确切的和完整的数据是不是?

是,它将拥有所有的数据,包括 “\ r \ n \ r \ n” 个

你有什么建议阅读,直到我得到\ r \ n \ r \ n?

你在做什么就好了。你只需要在每个命令结尾处忽略额外的'\ r'。您可以在从stream进行读取时执行此操作,也可以通过命令处理器(或执行命令为您执行的任何操作)对其进行处理。我的建议是推迟删除额外的'\ r'给命令处理器。

你可能需要上线的东西:

#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个连续的空新行了望。

+0

谢谢,我已经实现了你的想法与临时缓冲区和清理的字符串,而迭代。 http://ideone.com/KWj32L。我将添加一些关于空字符串的测试。 – markand

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") 
    ]; 
相关问题