2010-05-07 52 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代碼,您無法使其高效。

相關問題