【问题标题】:Consume only part of data in boost::asio basic_stream_socket::async_read_some handler仅使用 boost::asio basic_stream_socket::async_read_some 处理程序中的部分数据
【发布时间】:2012-04-13 06:28:44
【问题描述】:

我是 boost::asio 的新手,所以我的问题可能很愚蠢 - 如果是这样,对不起。 我正在使用 keepalive 编写异步服务器应用程序(可能在单个连接上发送多个请求)。

连接处理例程很简单:

循环中

  • 使用 socket->async_read_some(buffer, handler) 调度读取请求

  • 使用 async_write 来自处理程序调度写入响应。

我面临的问题是,当 传递给 async_read_somehandlerio_service 个线程调用,buffers 实际上可能包含比单个请求更多的数据(例如客户端发送的下一个请求的一部分)。

我现在不想(如果它只是请求的一部分,也不能)处理剩余的字节。 我想在处理完之前的请求之后再做。

如果我有可能将不必要的剩余数据重新注入到套接字中,这将很容易解决。所以它会在下一次 async_read_some 调用中处理。

boost::asio 中是否有这种可能性,还是我必须将剩余的数据存储在某个地方,并使用额外的代码自己处理。

【问题讨论】:

    标签: c++ boost boost-asio


    【解决方案1】:

    我想你要找的是asio::streambuf

    基本上,您可以检查作为 char* 的种子 streambuf,读取尽可能多的内容,然后告知consume(amount)实际处理了多少。

    将 HTTP-header 解析为客户端的工作代码示例:

    #include <boost/asio.hpp>
    #include <boost/bind.hpp>
    #include <iostream>
    #include <string>
    
    namespace asio = boost::asio;
    
    std::string LINE_TERMINATION = "\r\n";
    
    class Connection {
      asio::streambuf _buf;
      asio::ip::tcp::socket _socket;
    public:
    
      Connection(asio::io_service& ioSvc, asio::ip::tcp::endpoint server)
        : _socket(ioSvc)
      {
        _socket.connect(server);
        _socket.send(boost::asio::buffer("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"));
        readMore();
      }
    
      void readMore() {
        // Allocate 13 bytes space on the end of the buffer. Evil prime number to prove algorithm works.
        asio::streambuf::mutable_buffers_type buf = _buf.prepare(13);
    
        // Perform read
        _socket.async_read_some(buf,  boost::bind(
              &Connection::onRead, this,
              asio::placeholders::bytes_transferred, asio::placeholders::error
        ));
      }
    
      void onRead(size_t read, const boost::system::error_code& ec) {
        if ((!ec) && (read > 0)) {
          // Mark to buffer how much was actually read
          _buf.commit(read);
    
          // Use some ugly parsing to extract whole lines.
          const char* data_ = boost::asio::buffer_cast<const char*>(_buf.data());
          std::string data(data_, _buf.size());
          size_t start = 0;
          size_t end = data.find(LINE_TERMINATION, start);
          while (end < data.size()) {
            std::cout << "LINE:" << data.substr(start, end-start) << std::endl;
            start = end + LINE_TERMINATION.size();
            end = data.find(LINE_TERMINATION, start);
          }
          _buf.consume(start);
    
          // Wait for next data
          readMore();
        }
      }
    };
    
    int main(int, char**) {
      asio::io_service ioSvc;
    
      // Setup a connection and run 
      asio::ip::address localhost = asio::ip::address::from_string("127.0.0.1");
      Connection c(ioSvc, asio::ip::tcp::endpoint(localhost, 80));
    
      ioSvc.run();
    }
    

    【讨论】:

    【解决方案2】:

    在使用可靠且有序的传输(如 TCP)时,解决此问题的一种方法是:

    1. 写一个已知大小的标头,包含消息其余部分的大小
    2. 写下消息的其余部分

    在接收端:

    1. 仅读取足够的字节以获取标头
    2. 阅读邮件的其余部分,不再赘述

    【讨论】:

    • 不幸的是,这是不可行的,因为协议是固定的,我无法更改它:(
    • @losipiuk 所以你是说当前形式的协议不包含任何指示消息大小的元数据?您的消息是否有开始和结束分隔符?您如何确定一条消息确实是完整的?
    • no - 它不包含任何固定大小的标题。它实际上是 HTTP :)
    【解决方案3】:

    如果您知道消息的长度是固定的,您可以执行以下操作:

    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
    void
    Connection::readMore()
    {
        if (m_connected)
        {
            // Asynchronously read some data from the connection into the buffer.
            // Using shared_from_this() will prevent this Connection object from
            // being destroyed while data is being read.
            boost::asio::async_read(
                m_socket, 
                boost::asio::buffer(
                    m_readMessage.getData(), 
                    MessageBuffer::MESSAGE_LENGTH
                ),
                boost::bind(
                    &Connection::messageBytesRead, 
                    shared_from_this(), 
                    boost::asio::placeholders::error, 
                    boost::asio::placeholders::bytes_transferred
                ),
                boost::bind(
                    &Connection::handleRead, 
                    shared_from_this(),
                    boost::asio::placeholders::error
                )
            );
        }
    }
    
    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
    std::size_t
    Connection::messageBytesRead(const boost::system::error_code& _errorCode, 
                                 std::size_t _bytesRead)
    {
        return MessageBuffer::MESSAGE_LENGTH - _bytesRead;
    }
    
    //-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
    void
    Connection::handleRead(const boost::system::error_code& _errorCode)
    {
        if (!_errorCode)
        {
            /// Do something with the populated m_readMessage here.
            readMore();
        }
        else
        {
            disconnect();
        }
    }
    

    messageBytesRead 回调将在读取完整消息时向boost::asio::async_read 指示。这个 sn-p 是从正在运行的代码中的现有 Connection 对象中提取的,所以我知道它可以工作......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-08
      • 2014-11-22
      相关资源
      最近更新 更多