如何在基于boost :: asio的异步模式下接收未知大小的缓冲区

时间:2018-11-28 09:18:32

标签: sockets boost-asio asyncsocket

我正在尝试接收未知大小的缓冲区,我的代码的一部分很糟糕:

void Connection::asyncRead() 
{
    auto self(shared_from_this());
    socket_.async_read_some(boost::asio::buffer(buffer_out),
        [this, self](boost::system::error_code ec, std::size_t length)
    {
        OnRead(ec, length);
    });
}

我不知道缓冲区的大小,所以我尝试在固定大小的缓冲区中接收缓冲区,如何知道缓冲区是否结束?

1 个答案:

答案 0 :(得分:1)

如果要发送/接收大小不固定的邮件,可以使用 定义邮件标题的方法,例如使用4个字节的字段来存储邮件内容的大小:

[header(4 bytes) to store the size of message][content of message]

那么您始终知道第一步是读取4个字节, 准备数据缓冲区并读取更多数据,直到缓冲区被填满。

另一种方法是关闭套接字(下面是伪代码)

The receiving side             |  the sending side
--------------------------------------------------------------------------------
error_code ec;                 |
asio::read(sock,buf,ec) [2]    |
                               | prepare some buffer with unknown size
                               | string buf;
                               | buf += ...; // add data
                               | asio::write(sock,buf)
                               | sock.shutdown(socket_base::shutdown_send); [1]

通过在发送方[1]呼叫sock.shutdown(),您可以通知 接收方已发送了整个邮件。 然后,在读取消息[2]之后,在接收方应检查错误代码变量ec的状态是否为boost::asio::eof。 如果您收到end-of-file,则说明该消息已完成。如果eceof不同,则表示发生了错误。

从1.66 boost版本开始,您可以使用dynamic_buffer来存储数据,它将字符串或向量改编为缓冲区。 或者,您可以考虑streambuf读取非固定缓冲区。


编辑

添加了dynamic_buffer的使用。 根据参考文献,dynamic_buffer可以在async_readasync_until之类的自由函数中使用,但不能在async_read_some中作为套接字的成员函数使用。以下是客户端和服务器的代码:

服务器:

using namespace boost;

struct Data  {
  std::shared_ptr<asio::ip::tcp::socket> sock;
  std::string buf; // buf is empty [1]
};

void readHandler (
    const boost::system::error_code& ec,
    size_t length,
    std::shared_ptr<Data> d) {
  std::cout << "readHandler" << std::endl;
  if (ec == boost::asio::error::eof)
  {
    // here we got the whole message
    std::cout << d->buf << std::endl;
  }
  else 
  {
    std::cout << "Error" << std::endl;
  }
}

int main() {
  try {
    asio::ip::tcp::endpoint ep(asio::ip::address_v4::any(),9999);
    asio::io_service ios;
    asio::ip::tcp::acceptor acceptor(ios, ep); 

    std::shared_ptr<asio::ip::tcp::socket> sock{new asio::ip::tcp::socket(ios)};

    acceptor.accept(*sock);

    std::shared_ptr<Data> data(new Data);
    data->sock = move(sock);

    boost::asio::async_read (*(data->sock), asio::dynamic_buffer(data->buf), 
        std::bind(readHandler,std::placeholders::_1, std::placeholders::_2,data)); // [2]

    ios.run(); // wait until async_write is complete
  }
  catch (system::system_error &e) {
    std::cout << "error " << e.what() << std::endl;
  }
  return 0;
}

在[1]中,我们创建空缓冲区作为字符串对象,在[2]中,我们调用async_read以使用dynamic_buffer获取数据。当发送方关闭其一侧的套接字时,将调用传递到async_read的处理程序。

客户:

using namespace boost;

int main() {
  try {
    asio::ip::tcp::endpoint ep(asio::ip::address::from_string("127.0.0.1"),9999);
    asio::io_service ios;
    asio::ip::tcp::socket sock(ios, ep.protocol());
    sock.connect(ep);

    std::string buf = "some message";
    for (int i = 0; i < buf.size(); ++i) {
       // synchronous function was used to make simpler code
       asio::write(sock,asio::buffer(buf.c_str()+i,1)); // send 1 char
       std::this_thread::sleep_for(std::chrono::seconds(1)); // delay 1 second
    }
    sock.shutdown(asio::socket_base::shutdown_send);
  }
  catch (system::system_error &e) {
    std::cout << "Error " << e.what() << std::endl;
  }
  return 0;
}

如您所见,我们正在以1秒钟的延迟从字符串中按字符发送char。因此,当您启动服务器然后启动客户端时,服务器应在约12秒后收到整个消息。 async_read在服务器中等待,直到eof出现-它是由客户端在套接字上通过shutdown的调用发送的。