web-dev-qa-db-ja.com

boost :: asioでソケットを正しく閉じるにはどうすればよいですか?

私はboost :: asioでリソース管理に頭を悩ませようとしています。対応するソケットがすでに破棄された後に呼び出されたコールバックが表示されます。この良い例は、boost :: asioの公式例です: http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/example/cpp11/chat/chat_client.cpp

私は特にcloseメソッドに関心があります。

void close()
{
   io_service_.post([this]() { socket_.close(); });
}

この関数を呼び出した後、socket_を保持するchat_clientインスタンスを破棄すると、closeメソッドが呼び出される前にsocket_が破棄されます。また、保留中のasync_ *コールバックは、chat_clientが破棄された後に呼び出すことができます。

これをどのように正しく処理しますか?

12

socket_.close();はほぼいつでも実行できますが、次の点に注意する必要があります。

  • スレッドがある場合、この呼び出しはstrandでラップする必要があります。そうしないと、クラッシュする可能性があります。 ブーストストランドのドキュメント を参照してください。
  • closeを実行するときは常に、io_serviceがすでにキューに入れられたハンドラーを持つことができることに注意してください。そして、それらはとにかく古い状態/エラーコードで呼び出されます。
  • closeは例外をスローできます。
  • closeにはip :: tcp :: socketの破棄は含まれません。システムソケットを閉じるだけです。
  • オブジェクトの存続期間を自分で管理して、ハンドラーがなくなったときにのみオブジェクトが破棄されるようにする必要があります。通常、これはConnectionまたはsocketオブジェクトでenable_shared_from_thisを使用して行われます。
11
PSIAlt

socket.close() を呼び出しても、ソケットは破棄されません。ただし、アプリケーションは、操作ハンドラーと完了ハンドラーが依存するオブジェクトの存続期間を管理する必要がある場合がありますが、これは必ずしもソケットオブジェクト自体である必要はありません。たとえば、バッファとソケットを保持し、client::handle_read()の完了ハンドラーを持つ単一の未処理の読み取り操作を持つclientクラスについて考えてみます。 close()してソケットを明示的に破棄することはできますが、少なくともハンドラーが呼び出されるまで、バッファーとclientインスタンスは有効なままである必要があります。

class client
{
  ...

  void read()
  {
    // Post handler that will start a read operation.
    io_service_.post([this]() {
      async_read(*socket, boost::asio::buffer(buffer_);
        boost::bind(&client::handle_read, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
    });
  }

  void handle_read(
    const boost::system::error_code& error,
    std::size_t bytes_transferred
  )
  {
    // make use of data members...if socket_ is not used, then it
    // is safe for socket to have already been destroyed.
  }

  void close()
  {
    io_service_.post([this]() {
      socket_->close();
      // As long as outstanding completion handlers do not
      // invoke operations on socket_, then socket_ can be 
      // destroyed.
      socket_.release(nullptr);
    });
  }

private:
  boost::asio::io_service& io_service_;

  // Not a typical pattern, but used to exemplify that outstanding
  // operations on `socket_` are not explicitly dependent on the 
  // lifetime of `socket_`.
  std::unique_ptr<boost::asio::socket> socket_;
  std::array<char, 512> buffer_;
  ...
}

アプリケーションは、操作とハンドラーが依存するオブジェクトの存続期間を管理する責任があります。 チャットクライアントの例 これは、chat_clientインスタンスが破棄されることを保証することによって実現されますafterio_service.run()を待つことにより、使用されなくなります。スレッド内に戻るにはjoin()

int main(...)
{
  try
  {
    ...

    boost::asio::io_service io_service;
    chat_client c(...);

    std::thread t([&io_service](){ io_service.run(); });

    ...

    c.close();
    t.join(); // Wait for `io_service.run` to return, guaranteeing
              // that `chat_client` is no longer in use.
  }           // The `chat_client` instance is destroyed.
  catch (std::exception& e)
  {
    ...
  }
}

オブジェクトの存続期間を管理する一般的なイディオムの1つは、I/Oオブジェクトを enable_shared_from_this<> から継承する単一のクラスで管理することです。クラスがenable_shared_from_thisから継承する場合、thisを管理する有効なshared_ptrインスタンスを返すshared_from_this()メンバー関数を提供します。 shared_ptrのコピーは、ラムダのキャプチャリストなどの完了ハンドラーに渡されるか、インスタンスハンドルとしてbind()に渡され、I/Oオブジェクトの有効期間がに延長されます。少なくともハンドラーと同じくらい。このアプローチの使用例については、Boost.Asio asynchronous TCP daytime server チュートリアルを参照してください。

5
Tanner Sansbury