Asio:是否有可自动调整大小的缓冲区来接收输入?

时间:2016-03-30 08:53:17

标签: c++ boost boost-asio

  

Asio:是否有可自动调整大小的缓冲区来接收输入?

我事先并不知道要收到的尺寸,所以我将这个数量发送到标题中。

我查看http://www.boost.org/doc/libs/1_59_0/doc/html/boost_asio/example/cpp03/chat/chat_message.hpp使用标题的示例,但此示例假定指定了最大体型。

查看asio :: buffer类,我必须提供一些底层缓冲区,因此不灵活。相反,我看了一下asio :: streambuf类,但是如下所示使用它会产生分段/内存错误。

我尝试给出最大大小以只读取HEADER_LEN个字节,即标题。

这种方法有误吗?

void do_recv_header() 
{
    asio::streambuf buf(HEADER_LEN);
    asio::async_read(*g_selected_conn, buf, [this, &buf](const system::error_code& ec, std::size_t bytes_transferred)
    {
        if (ec != 0) {
            std::cout << "async_read() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
            remove_closed_conn(g_selected_conn);
            SetEvent(g_wait_event);
        }
        else {
            std::istream is(&buf);
            int body_len;
            is >> body_len;
            std::cout << body_len << std::endl;
            do_recv_body(body_len);
        }
    });
}

1 个答案:

答案 0 :(得分:6)

boost::asio::streambuf是一个可自动调整大小的缓冲类。这种缓冲区类型通常在启动读取操作时使用,该读取操作的完成取决于数据的内容,而不一定是数据的大小。例如,可以使用boost::asio::read_until()来读取换行符,而不知道或指定可读取的数据量。

如果应用程序协议具有包含主体长度的固定大小标头,并且标头后跟可变长度主体,请考虑使用缓冲区类型,例如std::vector<>。这将提供与boost::asio::streambuf相同的灵活性,同时简化部分簿记:

std::vector<char> buffer;

// Read header.
buffer.resize(protocol::header_size);
boost::asio::read(socket, boost::asio::buffer(buffer));

// Extract body size from header, resize buffer, then read
// body.
auto body_size = parse_header(buffer);
buffer.resize(body_size);
boost::asio::read(socket, boost::asio::buffer(buffer));

process_body(buffer);

不调整vector的大小,表明在读取操作中将读取多少数据。使用streambuf时,必须使用以下操作直接管理输入和输出序列:

boost::asio::streambuf streambuf;

// Read header into the streambuf's output sequence.
auto bytes_transferred = boost::asio::read(socket,
  streambuf.prepare(protocol::header_size));
// Commit read data from output sequence into the input
// sequence.
streambuf.commit(bytes_transferred);

// Extract body size from header.  This would likely
// consume all of the streambuf's input sequence.
auto body_size = parse_header(streambuf);
// Clear the input sequence.
streambuf.consume(streambuf.size());

// Ready body into the streambuf's output sequence.
bytes_transferred = boost::asio::read(socket,
  streambuf.prepare(body_size));
// Commit read data from output sequence into the input
// sequence.
streambuf.commit(bytes_transferred);

// Extract all of stream into the body.
process_body(streambuf);

这是一个完整的示例demonstrating这种方法:

#include <array> // std::array
#include <functional> // std::bind
#include <iostream> // std::cout, std::endl
#include <vector> // std::vector
#include <boost/asio.hpp>

// This example is not interested in the handlers, so provide a noop function
// that will be passed to bind to meet the handler concept requirements.
void noop() {}

// The application protocol will consists of a fixed-size header
// containing a std::size_t with the length of the following
// variable length body.  To keep it simple, some details
// are ommitted, such as endian handling.
namespace protocol {
enum
{
  header_size = sizeof(std::size_t)
};
} // namespace protocol

std::vector<char> build_header(const std::string& body)
{
  std::vector<char> buffer(protocol::header_size);
  auto body_size = body.size();
  std::memcpy(&buffer[0], &body_size, sizeof body_size);
  return buffer;
}

std::size_t parse_header(const std::vector<char>& buffer)
{
  return *reinterpret_cast<const std::size_t*>(&buffer[0]);
}

int main()
{
  using boost::asio::ip::tcp;

  // Create all I/O objects.
  boost::asio::io_service io_service;
  tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
  tcp::socket socket1(io_service);
  tcp::socket socket2(io_service);

  // Connect the sockets.
  acceptor.async_accept(socket1, std::bind(&noop));
  socket2.async_connect(acceptor.local_endpoint(), std::bind(&noop));
  io_service.run();
  io_service.reset();

  //  Write a message from socket1 to socket2.
  std::string test_message = "this is a test message";
  {
    auto header = build_header(test_message);

    // Gather header and body into a single buffer.
    std::array<boost::asio::const_buffer, 2> buffers = {{
      boost::asio::buffer(header),
      boost::asio::buffer(test_message)
    }};

    // Write header and body to socket.
    boost::asio::write(socket1, buffers);
  }

  // Read from socket2.
  {
    // Use a vector to allow for re-sizing based on the
    // amount of data needing to be read.  This also reduces
    // on the amount of reallocations if the vector is reused.    
    std::vector<char> buffer;

    // Read header.
    buffer.resize(protocol::header_size);
    boost::asio::read(socket2, boost::asio::buffer(buffer));

    // Extract body size from header, resize buffer, then read
    // body.
    auto body_size = parse_header(buffer);
    buffer.resize(body_size);
    boost::asio::read(socket2, boost::asio::buffer(buffer));

    // Verify body was read.
    assert(std::equal(begin(buffer), end(buffer), 
                      begin(test_message)));
    std::cout << "received: \n"
                 "  header: " << body_size << "\n"
                 "  body: ";
    std::cout.write(&buffer[0], buffer.size());
    std::cout << std::endl;
  }
}

输出:

received: 
  header: 22
  body: this is a test message