web-dev-qa-db-ja.com

ブーストasioでソケットのブロックにタイムアウトを設定するにはどうすればよいですか?

保留中の操作を(切断せずに)キャンセルする方法、またはブーストライブラリ関数のタイムアウトを設定する方法はありますか?

つまりboost asioでソケットをブロックする際にタイムアウトを設定したいですか?

socket.read_some(boost :: asio :: buffer(pData、maxSize)、error_);

例:ソケットから一部を読み取りたいが、10秒が経過した場合にエラーをスローしたい。

34
Brian R. Bondy

Linux/BSDでは、ソケットでのI/O操作のタイムアウトは、オペレーティングシステムによって直接サポートされます。オプションはsetsocktopt()で有効にできます。 boost::asioが設定方法を提供しているか、ソケットスクリプターを公開して直接設定できるかどうかはわかりません-後者の場合は、実際には移植性がありません。

完全を期すために、manページの説明を次に示します。

SO_RCVTIMEOおよびSO_SNDTIMEO

          Specify the receiving or sending  timeouts  until  reporting  an
          error.  The argument is a struct timeval.  If an input or output
          function blocks for this period of time, and data has been  sent
          or  received,  the  return  value  of  that function will be the
          amount of data transferred; if no data has been transferred  and
          the  timeout has been reached then -1 is returned with errno set
          to EAGAIN or EWOULDBLOCK just as if the socket was specified  to
          be  non-blocking.   If  the timeout is set to zero (the default)
          then the operation  will  never  timeout.   Timeouts  only  have
          effect  for system calls that perform socket I/O (e.g., read(2),
          recvmsg(2), send(2), sendmsg(2)); timeouts have  no  effect  for
          select(2), poll(2), epoll_wait(2), etc.
9
Nicola Bonelli

この質問が尋ねられたとき、私はASIOにOPが必要とするものを達成する方法、つまり、ブロッキングソケット操作などのブロッキング操作をタイムアウトにする例はなかったと思います。これを行う方法を正確に示す例が存在します。例は長いように見えますが、それはそれがよくコメントされているためです。これは、「ワンショット」の種類のモードでioserviceを使用する方法を示しています。

この例は素晴らしいソリューションだと思います。ここでの他のソリューションは、移植性を損ない、ioserviceを利用しません。移植性が重要ではなく、ioserviceがオーバーヘッドのように見える場合は、ASIOを使用しないでください。何があっても、ioserviceが作成されます(ほとんどすべてのASIO機能がそれに依存し、同期ソケットもそれに依存します)。そのため、それを利用してください。

ブロッキングasio tcp操作のタイムアウト

ブロッキングasio udp操作のタイムアウト

ASIOのドキュメントが更新されたので、ASIOが持っている「落とし穴」のいくつかを克服する方法の新しい例をチェックしてください。

17

TL; DR

socket.set_option(boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO>{ 200 });

完全な回答この質問は、長年にわたって何度も繰り返し尋ねられています。これまでに見た答えはかなり貧弱です。この情報を、この質問の最初の発生箇所の1つに追加します。

ASIOを使用してネットワークコードを簡略化しようとするすべての人は、作成者がすべての同期および非同期io関数にオプションのパラメータータイムアウトを追加するだけで完全に満足します。残念ながら、これは起こりそうにありません(私の考えでは、結局のところ、思想的な理由から、ASIOのASには理由があります)。

したがって、これらはこれまで利用可能なこの貧しい猫の皮をむく方法であり、それらのどれも特に食欲をそそるものではありません。 200msのタイムアウトが必要だとしましょう。

1)良い(悪い)古いソケットAPI:

const int timeout = 200;
::setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof timeout);//SO_SNDTIMEO for send ops

これらの特性に注意してください。-タイムアウト用のconst int-Windowsでは必要なタイプは実際にはDWORDですが、現在のコンパイラのセットは幸いにも同じなので、const intはWinとPosixの両方の世界で機能します。 const char *)値。 Windowsではconst char *が必要ですが、Posixではconst void *が必要です。C++では、const char *は暗黙的にconst void *に変換されますが、その逆は当てはまりません。

利点:ソケットAPIが古くて安定しているため、機能し、おそらく常に機能します。十分に単純です。速い。短所:技術的には、setsockoptとマクロに適切なヘッダーファイル(Winでも、異なるUNIXフレーバーでも異なる)が必要になる場合がありますが、ASIOの現在の実装では、グローバルネームスペースが汚染されます。タイムアウトの変数が必要です。タイプセーフではありません。 Windowsでは、動作するためにソケットがオーバーラップモードになっている必要があります(現在のASIO実装は幸いにも使用していますが、実装の詳細です)。醜い!

2)カスタムASIOソケットオプション:

typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option; //somewhere in your headers to be used everywhere you need it
//...
socket.set_option(rcv_timeout_option{ 200 });

利点:十分に単純です。速い。美しい(typedef付き)。短所:変更される可能性のあるASIO実装の詳細に依存します(ただし、OTOHはすべて最終的に変更され、そのような詳細は、標準化の対象となるパブリックAPIよりも変更される可能性が低くなります)。ただし、これが発生した場合は、---(https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/SettableSocketOption.html に従ってクラスを記述する必要があります。 =(もちろん、これはASIOのこの部分の明らかなオーバーエンジニアリングのおかげで主要なPITAです)または1に戻すのが適切です。

3)C++非同期/将来の機能を使用します。

#include <future>
#include <chrono>
//...
auto status = std::async(std::launch::async, [&] (){ /*your stream ops*/ })
    .wait_for(std::chrono::milliseconds{ 200 });
switch (status)
    {
    case std::future_status::deferred:
    //... should never happen with std::launch::async
        break;
    case std::future_status::ready:
    //...
        break;
    case std::future_status::timeout:
    //...
        break;
    }

利点:標準。短所:常に新しいスレッドを開始します(実際には)。これは比較的低速です(クライアントには十分かもしれませんが、スレッドとソケットは「高価な」リソースであるため、サーバーのDoS脆弱性につながります)。新しいスレッドの起動を避けるために、std :: launch :: asyncの代わりにstd :: launch :: deferredを使用しないでください。wait_forは常にコードを実行せずにfuture_status :: deferredを返すためです。

4)ASIOで規定されている方法-非同期操作のみを使用します(これは実際には質問に対する答えではありません)。

利点:短いトランザクションに大きなスケーラビリティが必要ない場合は、サーバーにも十分対応できます。短所:非常に冗長です(したがって、例も含めません-ASIOの例を参照してください)。非同期操作とその完了ハンドラーの両方で使用されるすべてのオブジェクトのライフタイム管理を非常に慎重に行う必要があります。実際には、非同期操作でそのようなデータを含み、使用するすべてのクラスがenable_shared_from_thisから派生する必要があります。すべてのヒープの割り当て/割り当て解除がメモリバリアを使用するため、少なくとも短い操作の場合)スケーラビリティは約16スレッド後に徐々に減少し始めます。

12
Pavel Verevkin

Async_readを実行し、希望するタイムアウトのタイマーを設定することもできます。次に、タイマーが作動した場合は、ソケットオブジェクトでキャンセルを呼び出します。それ以外の場合は、読み取りが発生した場合、タイマーをキャンセルできます。もちろん、これにはio_serviceオブジェクトを使用する必要があります。

編集:これを行うコードスニペットを見つけました

http://lists.boost.org/Archives/boost/2007/04/120339.php

9
grepsedawk

私は同じ質問をしました、そしていくつかの調査の後、私が思いつくことができる最も単純でクリーンな解決策は、基礎となるネイティブソケットを取得し、読み取るデータがあるまで選択を行うことでした。 Selectはタイムアウトパラメータを取ります。もちろん、ネイティブソケットでの作業は、最初からasioを使用するという点に反し始めますが、これも最もクリーンな方法のようです。私の知る限り、asioは同期使用のためにこれを簡単に行う方法を提供していません。コード:

        // socket here is:  boost::shared_ptr<boost::asio::ip::tcp::socket> a_socket_ptr

        // Set up a timed select call, so we can handle timeout cases.

        fd_set fileDescriptorSet;
        struct timeval timeStruct;

        // set the timeout to 30 seconds
        timeStruct.tv_sec = 30;
        timeStruct.tv_usec = 0;
        FD_ZERO(&fileDescriptorSet);

        // We'll need to get the underlying native socket for this select call, in order
        // to add a simple timeout on the read:

        int nativeSocket = a_socket_ptr->native();

        FD_SET(nativeSocket,&fileDescriptorSet);

        select(nativeSocket+1,&fileDescriptorSet,NULL,NULL,&timeStruct);

        if(!FD_ISSET(nativeSocket,&fileDescriptorSet)){ // timeout

                std::string sMsg("TIMEOUT on read client data. Client IP: ");

                sMsg.append(a_socket_ptr->remote_endpoint().address().to_string());

                throw MyException(sMsg);
        }

        // now we know there's something to read, so read
        boost::system::error_code error;
        size_t iBytesRead = a_socket_ptr->read_some(boost::asio::buffer(myVector), error);

        ...

おそらくこれはあなたの状況に役立つでしょう。

8
Ty Hoffman

Grepsedawkが述べたことに続いて。 asio docoのTimeoutsセクションに、長時間実行されている非同期操作をキャンセルする方法を示すいくつかの例があります。 Boost Asioの例非同期TCPクライアントが私を最も助けてくれました。

ハッピー非同期:)

3
denn

元の質問から数年経っても、満足のいく答えはまだありません。

手動で選択を使用することは良いオプションではありません

  1. ファイル記述子番号は1024未満でなければなりません
  2. チェックサムが間違っているため、FDが準備完了として誤って報告される場合があります。

常にio_service.run_one()を実行するためにio_serviceを必要とする他の非同期オプションが存在する可能性があるため、run()を呼び出すこともお勧めできません。そしてtcpクライアントのブロックについてのboostのドキュメントは理解するのが難しい。

これが私の解決策です。重要なアイデアは次のとおりです。

{
    Semaphore r_sem;
    boost::system::error_code r_ec;
    boost::asio::async_read(s,buffer,
                            [this, &r_ec, &r_sem](const boost::system::error_code& ec_, size_t) {
                                r_ec=ec_;
                                r_sem.notify();
                            });
    if(!r_sem.wait_for(std::chrono::seconds(3))) // wait for 3 seconds
    {
        s.cancel();
        r_sem.wait();
        throw boost::system::system_error(boost::asio::error::try_again);
    }
    else if(r_ec)
        throw boost::system::system_error(r_ec);
}

ここでSemaphoreはミューテックスと条件変数です。
wait_forhttp://en.cppreference.com/w/cpp/thread/condition_variable/wait_for によって実装されています

完全なコードは https://github.com/scinart/cpplib/blob/master/include/asio.hpp にあります
例: https://github.com/scinart/cpplib/blob/6e9a1690bf68971b809be34dfe432949d9a9f727/standalone_example/boost_block_tcp_client_server.cpp

-update-サンプルリンクが更新されました。

1
scinart

SO_RCVTIMEOSO_SNDTIMEOは、timevalの代わりに"sys/time.h"からint構造体を受け取ります。したがって、@ Pavel Verevkinのオプション1.はtimevalではなくintを取り込む必要があり、オプション2.は boost::asio::detail::socket_option::integerからクラスを実装する必要があります は単一の整数値のみを格納します。

0
dtsull