web-dev-qa-db-ja.com

Boost :: asio-ブロックされたtcpサーバースレッドを中断する方法は?

私は、1つのスレッドがクライアントからコマンドを受信するtcpサーバーとして機能するマルチスレッドアプリケーションに取り組んでいます。スレッドは、Boostソケットとアクセプターを使用して、クライアントが接続するのを待ち、クライアントからコマンドを受け取り、そのコマンドをアプリケーションの残りの部分に渡してから、再び待ちます。コードは次のとおりです。

_void ServerThreadFunc()
{
    using boost::asio::ip::tcp;
    boost::asio::io_service io_service;
    tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), port_no));

    for (;;)
    {
        //  listen for command connection
        tcp::socket socket(io_service);
        acceptor.accept(socket);

        //  connected; receive command
        boost::array<char,256> msg_buf;
        socket.receive(boost::asio::buffer(msg_buf));

        //  do something with received bytes here
    }
}
_

このスレッドは、ほとんどの時間をacceptor.accept()の呼び出しでブロックされています。現時点では、スレッドはアプリケーションが終了したときにのみ終了します。残念ながら、これはmain()が戻った後にクラッシュを引き起こします-シングルトンが破壊された後、スレッドがアプリのロギングシングルトンにアクセスしようとするためだと思います。 (私がここに着いたとき、それはそのようなものでした、正直なguv。)

アプリケーションを終了するときに、このスレッドをクリーンにシャットダウンするにはどうすればよいですか? rawソケットでのブロッキングaccept()呼び出しは、別のスレッドからソケットを閉じることで中断できることを読みましたが、これはBoostソケットでは機能しないようです。 Boost非同期tcpエコーサーバーの例 を使用してサーバーロジックを非同期I/Oに変換しようとしましたが、それはacceptor::accept()へのブロッキング呼び出しをブロッキング呼び出しと交換しているようですio_service::run()に移動するので、同じ問題が残ります。中断できない呼び出しがブロックされます。何か案は?

26
bythescruff

つまり、2つのオプションがあります。

  • コードを非同期に変更し(acceptor::async_accept()および_async_read_)、io_service::run()を介してイベントループ内で実行し、io_service::stop()を介してキャンセルします。
  • 信号などの低レベルのメカニズムで割り込みをブロックする呼び出しを強制します。

最初のオプションは、ポータブルで保守が容易である可能性が高いため、お勧めします。理解しておくべき重要な概念は、 io_service::run() は、保留中の作業がある限りブロックするだけであるということです。 io_service::stop() が呼び出されると、io_service::run()でブロックされているすべてのスレッドができるだけ早く戻ろうとします。同期操作がイベントループ内で呼び出された場合でも、acceptor::accept()socket::receive()などの同期操作は中断されません。 io_service::stop()は非ブロッキング呼び出しであるため、io_service::run()でブロックされたスレッドとの同期には、 thread::join()

これは、10秒間実行され、ポート8080をリッスンする例です。

_#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <iostream>

void StartAccept( boost::asio::ip::tcp::acceptor& );

void ServerThreadFunc( boost::asio::io_service& io_service )
{
  using boost::asio::ip::tcp;
  tcp::acceptor acceptor( io_service, tcp::endpoint( tcp::v4(), 8080 ) );

  // Add a job to start accepting connections.
  StartAccept( acceptor );

  // Process event loop.
  io_service.run();

  std::cout << "Server thread exiting." << std::endl;
}

void HandleAccept( const boost::system::error_code& error,
                   boost::shared_ptr< boost::asio::ip::tcp::socket > socket,
                   boost::asio::ip::tcp::acceptor& acceptor )
{
  // If there was an error, then do not add any more jobs to the service.
  if ( error )
  {
    std::cout << "Error accepting connection: " << error.message() 
              << std::endl;
    return;
  }

  // Otherwise, the socket is good to use.
  std::cout << "Doing things with socket..." << std::endl;

  // Perform async operations on the socket.

  // Done using the socket, so start accepting another connection.  This
  // will add a job to the service, preventing io_service::run() from
  // returning.
  std::cout << "Done using socket, ready for another connection." 
            << std::endl;
  StartAccept( acceptor );
};

void StartAccept( boost::asio::ip::tcp::acceptor& acceptor )
{
  using boost::asio::ip::tcp;
  boost::shared_ptr< tcp::socket > socket(
                                new tcp::socket( acceptor.get_io_service() ) );

  // Add an accept call to the service.  This will prevent io_service::run()
  // from returning.
  std::cout << "Waiting on connection" << std::endl;
  acceptor.async_accept( *socket,
    boost::bind( HandleAccept,
      boost::asio::placeholders::error,
      socket,
      boost::ref( acceptor ) ) );
}

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

  // Create io service.
  boost::asio::io_service io_service;

  // Create server thread that will start accepting connections.
  boost::thread server_thread( ServerThreadFunc, boost::ref( io_service ) );

  // Sleep for 10 seconds, then shutdown the server.
  std::cout << "Stopping service in 10 seconds..." << std::endl;
  boost::this_thread::sleep( boost::posix_time::seconds( 10 ) );
  std::cout << "Stopping service now!" << std::endl;

  // Stopping the io_service is a non-blocking call.  The threads that are
  // blocked on io_service::run() will try to return as soon as possible, but
  // they may still be in the middle of a handler.  Thus, perform a join on 
  // the server thread to guarantee a block occurs.
  io_service.stop();

  std::cout << "Waiting on server thread..." << std::endl;
  server_thread.join();
  std::cout << "Done waiting on server thread." << std::endl;

  return 0;
}
_

実行中に、2つの接続を開きました。出力は次のとおりです。

10秒でサービスを停止しています... 
接続を待機しています
ソケットで処理を行っています... 
ソケットを使用して完了し、別の接続の準備ができています。
接続を待機しています
ソケットで処理を行っています... 
ソケットを使用して完了し、別の接続の準備ができました。
接続を待機しています
サービスを今すぐ停止しています!
待機中サーバースレッド... 
サーバースレッドが終了します。
サーバースレッドの待機が完了しました。
29
Tanner Sansbury

終了する時間であるというイベントを受け取ったら、acceptor.cancel()を呼び出すことができます。これにより、保留中の受け入れがキャンセルされます(エラーコードは_operation_canceled_)。一部のシステムでは、安全のためにアクセプターもclose()する必要がある場合があります。

4
Dave S

それに関しては、ローカルホストで一時的なクライアント接続を開くことができます-それはそれを目覚めさせます。パブからサーバーをシャットダウンできるように、特別なメッセージを送信することもできます-そのためのアプリがあるはずです:)

3
Martin James

ネイティブハンドルとSHUT_RDオプションを使用してshutdownを呼び出すだけで、既存の受信(受け入れ)操作をキャンセルできます。

0
JohnYu