2010-05-07 154 views
11

我正在学习Boost :: asio和所有那些异步的东西。我怎样才能异步读取类型为std :: string的变量user_Boost::asio::buffer(user_)仅适用于async_write(),但不适用于async_read()。它适用于矢量,所以它不使用字符串的原因是什么?除了宣布char user_[max_len]和使用Boost::asio::buffer(user_, max_len)之外,还有其他方法吗?如何使用Boost :: asio异步读取到std :: string?

而且,什么是从boost::enable_shared_from_this<Connection>继承和async_read()async_write()使用shared_from_this()代替this点?我在例子中看到了很多。

这里是我的代码的一部分:

class Connection 
{ 
    public: 

     Connection(tcp::acceptor &acceptor) : 
      acceptor_(acceptor), 
      socket_(acceptor.get_io_service(), tcp::v4()) 
     { } 

     void start() 
     { 
      acceptor_.get_io_service().post(
       boost::bind(&Connection::start_accept, this)); 
     } 

    private: 

     void start_accept() 
     { 
      acceptor_.async_accept(socket_, 
       boost::bind(&Connection::handle_accept, this, 
       placeholders::error)); 
     } 

     void handle_accept(const boost::system::error_code& err) 
     { 
      if (err) 
      { 
       disconnect(); 
      } 
      else 
      { 
       async_read(socket_, boost::asio::buffer(user_), 
        boost::bind(&Connection::handle_user_read, this, 
        placeholders::error, placeholders::bytes_transferred)); 
      } 
     } 

     void handle_user_read(const boost::system::error_code& err, 
      std::size_t bytes_transferred) 
     { 
      if (err) 
      { 
       disconnect(); 
      } 
      else 
      { 
       ... 
      } 
     } 

     ... 

     void disconnect() 
     { 
      socket_.shutdown(tcp::socket::shutdown_both); 
      socket_.close(); 
      socket_.open(tcp::v4()); 
      start_accept(); 
     } 

     tcp::acceptor &acceptor_; 
     tcp::socket socket_; 
     std::string user_; 
     std::string pass_; 
     ... 
}; 

回答

30

的Boost.Asio的文档状态:

缓冲对象表示的存储器中的连续区域作为由指针的2元组和大小以字节为单位。一个形式为{void *,size_t}的元组指定了一个可变(可修改)的内存区域。

这意味着,为了调用async_read将数据写入缓冲区,它必须(在底层缓冲区对象中)有连续的内存块。此外,缓冲区对象必须能够写入该内存块。

std::string不允许随意写入其缓冲区,所以async_read不能写的内存块转换成字符串的缓冲区(注意:std::string通过data()方法,保证给底层缓冲区来电只读访问返回的指针在下一次调用非const成员函数之前是有效的。因此,Asio可以很容易地创建一个包含std::stringconst_buffer,并且您可以使用async_write)。

的短耳文档有示例代码简单的“聊天”程序(见http://www.boost.org/doc/libs/1_43_0/doc/html/boost_asio/examples.html#boost_asio.examples.chat),有解决这个问题的一个好方法。基本上,您需要首先发送TCP消息的大小,并按照“标题”排序,并且读取处理程序必须解释头文件以分配适合读取实际数据的固定大小的缓冲区。

只要需要在async_readasync_write中使用shared_from_this(),原因是它保证由boost::bind包围的方法将始终引用活动对象。考虑以下情况:

  1. handle_accept方法调用async_read和发送处理程序“进入反应器” - 基本上你问io_service调用Connection::handle_user_read当它完成从套接字读取数据。 io_service存储此仿函数并继续其循环,等待异步读取操作完成。
  2. 在您致电async_read之后,Connection对象因某种原因(程序终止,错误条件等)被释放)
  3. 假设io_service现在确定异步读取完成时,Connection对象已释放但io_service被破坏之前(这可能发生,例如,如果io_service::run是在单独的线程中运行,如典型)。现在,io_service尝试调用该处理程序,并且它具有对Connection对象的无效引用。

的解决方案是通过一个shared_ptr分配Connection和发送的处理程序“向反应器”,当使用shared_from_this()代替this - 这允许io_service存储一个共享参照对象,shared_ptr保证它赢得”直到最后一个引用过期为止。

所以,你的代码看起来可能是这样的:

class Connection : public boost::enable_shared_from_this<Connection> 
{ 
public: 

    Connection(tcp::acceptor &acceptor) : 
     acceptor_(acceptor), 
     socket_(acceptor.get_io_service(), tcp::v4()) 
    { } 

    void start() 
    { 
     acceptor_.get_io_service().post(
      boost::bind(&Connection::start_accept, shared_from_this())); 
    } 

private: 

    void start_accept() 
    { 
     acceptor_.async_accept(socket_, 
      boost::bind(&Connection::handle_accept, shared_from_this(), 
      placeholders::error)); 
    } 

    void handle_accept(const boost::system::error_code& err) 
    { 
     if (err) 
     { 
      disconnect(); 
     } 
     else 
     { 
      async_read(socket_, boost::asio::buffer(user_), 
       boost::bind(&Connection::handle_user_read, shared_from_this(), 
       placeholders::error, placeholders::bytes_transferred)); 
     } 
    } 
    //... 
}; 

请注意,您现在必须确保每个Connection对象被分配通过shared_ptr,如:

boost::shared_ptr<Connection> new_conn(new Connection(...)); 

希望这有助于!

8

这并非是一个答案本身,而只是一个冗长的评论:一个非常简单的方法来从ASIO缓冲区转换为字符串从中流:

asio::streambuf buff; 
asio::read_until(source, buff, '\r'); // for example 

istream is(&buff); 
is >> targetstring; 

这是一个数据副本,当然,但这就是你需要做的,如果你想在一个字符串中。

4

您可以使用std:stringasync\_read()这样的:

async_read(socket_, boost::asio::buffer(&user_[0], user_.size()), 
      boost::bind(&Connection::handle_user_read, this, 
      placeholders::error, placeholders::bytes_transferred)); 

不过,你最好确保该std::string是大到足以接受,你期待的包,并调用之前用零填充async\_read()

至于为什么你应该NEVER成员函数回调绑定到this指针如果对象可以删除,一个更完整的描述和更可靠的方法可以在这里找到:Boost async_* functions and shared_ptr's

0

Boost Asio有两种类型的缓冲区。有boost::asio::buffer(your_data_structure),其中不能增长,因此对于未知输入通常是无用的,并且有boost::asio::streambuf其中可以增长。

鉴于boost::asio::streambuf buf,你把它变成一个字符串与std::string(std::istreambuf_iterator<char>(&buf), {});

这样做效率不高,因为您最终复制数据一次,但这需要使boost::asio::buffer知道可扩展容器,即具有.resize(N)方法的容器。如果不接触Boost代码,您无法使其高效。

相关问题