web-dev-qa-db-ja.com

複数の接続を作成するときにCでソケットタイムアウトを設定するにはどうすればよいですか?

ステータスチェックのために異なるサーバーに複数の接続を行う簡単なプログラムを書いています。これらの接続はすべてオンデマンドで構築されます。最大10個の接続を同時に作成できます。私はソケットあたり1スレッドという考え方が好きではないので、これらすべてのクライアントソケットを非ブロック化し、select()プールにスローします。

クライアントが、ターゲットサーバーが応答を停止したときにエラーレポートを取得するまでの待ち時間が長すぎると苦情を申し立てるまで、それはうまくいきました。

フォーラムでいくつかのトピックを確認しました。一部の人は、select()関数呼び出しでalarm()シグナルを使用するか、タイムアウトを設定できることを提案していました。しかし、私は1つではなく複数の接続を扱っています。プロセス全体のタイムアウト信号が発生すると、タイムアウト接続を他のすべての接続と区別する方法がありません。

とにかくシステムのデフォルトのタイムアウト時間を変更する必要はありますか?

61
RichardLiu

SO_RCVTIMEOおよびSO_SNDTIMEOソケットオプションを使用して、次のように、ソケット操作のタイムアウトを設定できます。

    struct timeval timeout;      
    timeout.tv_sec = 10;
    timeout.tv_usec = 0;

    if (setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
                sizeof(timeout)) < 0)
        error("setsockopt failed\n");

    if (setsockopt (sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
                sizeof(timeout)) < 0)
        error("setsockopt failed\n");

Edit:from setsockoptman page

SO_SNDTIMEOは、出力操作のタイムアウト値を設定するオプションです。出力操作が完了するまでの待機時間を制限するために使用される秒数とマイクロ秒数を含むstruct timevalパラメーターを受け入れます。送信操作がこれだけの時間ブロックされた場合、データが送信されなかった場合、部分的なカウントまたはエラーEWOULDBLOCKで戻ります。現在の実装では、このタイマーは追加データがプロトコルに配信されるたびに再起動されます。これは、出力の最低水準点から最高水準点までのサイズの出力部分に制限が適用されることを意味します。

SO_RCVTIMEOは、入力操作のタイムアウト値を設定するオプションです。入力操作が完了するまでの待機時間を制限するために使用される秒数とマイクロ秒数を持つstruct timevalパラメーターを受け入れます。現在の実装では、このタイマーは追加のデータがプロトコルによって受信されるたびに再起動されるため、制限は実質的に非アクティブタイマーになります。追加のデータを受信せずに受信操作がこれだけの時間ブロックされた場合、短いカウントで返されるか、データが受信されなかった場合はエラーEWOULDBLOCKで返されます。 struct timevalパラメーターは、正の時間間隔を表す必要があります。そうでない場合、setsockopt()はエラーEDOMを返します。

110
Toby

私が問題を完全に理解しているかどうかはわかりませんが、それは私が持っていたものに関連していると推測し、TCPソケット通信でQtを使用しています。

既に接続されたクライアントに障害が発生したか、完全に消えたときに、切断信号が発生するまでデフォルトの900秒以上待たずに迅速な通知を受け取りたいと考えていました。これを機能させるコツは、SOL_TCPレイヤーのTCP_USER_TIMEOUTソケットオプションをミリ秒単位で指定された必要な値に設定することでした。

これは比較的新しいオプションです。pls http://tools.ietf.org/html/rfc5482 を参照してください。私の10秒の選択は少し長くなりましたが、以前試した他のものよりもはるかに優れています;-)

私が遭遇した唯一の問題は、適切なインクルードを見つけることでした。明らかにこれは標準ソケットインクルードに追加されていないためです(まだ)。

#ifdef WIN32
    #include <winsock2.h>
#else
    #include <sys/socket.h>
#endif

#ifndef SOL_TCP
    #define SOL_TCP 6  // socket options TCP level
#endif
#ifndef TCP_USER_TIMEOUT
    #define TCP_USER_TIMEOUT 18  // how long for loss retry before timeout [ms]
#endif

このソケットオプションの設定は、クライアントが既に接続されている場合にのみ機能します。コード行は次のようになります。

int timeout = 10000;  // user timeout in milliseconds [ms]
setsockopt (fd, SOL_TCP, TCP_USER_TIMEOUT, (char*) &timeout, sizeof (timeout));

また、最初の接続の失敗は、connect()を呼び出すときに開始されるタイマーによってキャッチされます。これは、Qtの信号がないため、接続がないため、接続信号は発生せず、切断信号はまた、まだ接続されていないため、上げられません。

13
wgr

独自のタイムアウトシステムを実装できませんか?

タイムアウトイベントのソートされたリストを保持するか、Heathが示唆する優先ヒープを保持します。選択呼び出しまたはポーリング呼び出しでは、タイムアウトリストの一番上にあるタイムアウト値を使用します。そのタイムアウトが到着したら、そのタイムアウトに関連付けられたアクションを実行します。

そのアクションは、まだ接続されていないソケットを閉じることです。

9
Zan Lynx

connectタイムアウトは、非ブロッキングソケットで処理する必要があります(GNU LibC documentation on connect)。 connectを取得してすぐに戻り、selectを使用して、接続が完了するまでタイムアウトで待機します。

これもここで説明されています: 現在進行中の操作connect(function)error のエラー.

int wait_on_sock(int sock, long timeout, int r, int w)
{
    struct timeval tv = {0,0};
    fd_set fdset;
    fd_set *rfds, *wfds;
    int n, so_error;
    unsigned so_len;

    FD_ZERO (&fdset);
    FD_SET  (sock, &fdset);
    tv.tv_sec = timeout;
    tv.tv_usec = 0;

    TRACES ("wait in progress tv={%ld,%ld} ...\n",
            tv.tv_sec, tv.tv_usec);

    if (r) rfds = &fdset; else rfds = NULL;
    if (w) wfds = &fdset; else wfds = NULL;

    TEMP_FAILURE_RETRY (n = select (sock+1, rfds, wfds, NULL, &tv));
    switch (n) {
    case 0:
        ERROR ("wait timed out\n");
        return -errno;
    case -1:
        ERROR_SYS ("error during wait\n");
        return -errno;
    default:
        // select tell us that sock is ready, test it
        so_len = sizeof(so_error);
        so_error = 0;
        getsockopt (sock, SOL_SOCKET, SO_ERROR, &so_error, &so_len);
        if (so_error == 0)
            return 0;
        errno = so_error;
        ERROR_SYS ("wait failed\n");
        return -errno;
    }
}
4
Couannette