4

BindIoCompletionCallbackを使用してVisualC++ WinSock TCPサーバーを構築しています。データの送受信は正常に機能しますが、タイムアウトを検出する適切な方法が見つかりません。ピアがブロックされていない場合、SetSockOpt / SO_RCVTIMEO/SO_SNDTIMEOは非ブロッキングソケットに影響を与えません。データを送信すると、CompletionRoutineはまったく呼び出されません。

OVERLAPPEDのhEventフィールドでRegisterWaitForSingleObjectを使用することを考えていますが、これは機能する可能性がありますが、CompletionRoutineはまったく必要ありません。まだIOCPを使用していますか?RegisterWaitForSingleObjectのみを使用し、BindIoCompletionCallbackを使用しない場合、パフォーマンスの問題はありますか?

更新:コードサンプル:

私の最初の試み:

    bool CServer::Startup() {
        SOCKET ServerSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
        WSAEVENT ServerEvent = WSACreateEvent();
        WSAEventSelect(ServerSocket, ServerEvent, FD_ACCEPT);
        ......
        bind(ServerSocket......);
        listen(ServerSocket......);
        _beginthread(ListeningThread, 128 * 1024, (void*) this);
        ......
        ......
    }

    void __cdecl CServer::ListeningThread( void* param ) // static
    {
        CServer* server = (CServer*) param;
        while (true) {
            if (WSAWaitForMultipleEvents(1, &server->ServerEvent, FALSE, 100, FALSE) == WSA_WAIT_EVENT_0) {
                WSANETWORKEVENTS events = {};
                if (WSAEnumNetworkEvents(server->ServerSocket, server->ServerEvent, &events) != SOCKET_ERROR) {
                    if ((events.lNetworkEvents & FD_ACCEPT) && (events.iErrorCode[FD_ACCEPT_BIT] == 0)) {
                        SOCKET socket = accept(server->ServerSocket, NULL, NULL);
                        if (socket != SOCKET_ERROR) {
                            BindIoCompletionCallback((HANDLE) socket, CompletionRoutine, 0);
                            ......
                        }
                    }
                }
            }
        }
    }

    VOID CALLBACK CServer::CompletionRoutine( __in DWORD dwErrorCode, __in DWORD dwNumberOfBytesTransfered, __in LPOVERLAPPED lpOverlapped ) // static
    {
        ......
        BOOL res = GetOverlappedResult(......, TRUE);
        ......
    }

    class CIoOperation {
    public:
        OVERLAPPED Overlapped;
        ......
        ......
    };

    bool CServer::Receive(SOCKET socket, PBYTE buffer, DWORD length, void* context)
    {
        if (connection != NULL) {
            CIoOperation* io = new CIoOperation();
            WSABUF buf = {length, (PCHAR) buffer}; 
            DWORD flags = 0;
            if ((WSARecv(Socket, &buf, 1, NULL, &flags, &io->Overlapped, NULL) != 0) && (GetLastError() != WSA_IO_PENDING)) {
                delete io;
                return false;
            } else return true;
        }
        return false;
    }

私が言ったように、クライアントが実際に私にデータを送信している場合は正常に機能します、「受信」はブロックされていません、CompletionRoutineが呼び出され、データが受信されましたが、クライアントが私にデータを送信していない場合、どうすればよいですか?タイムアウト後にあきらめますか?

SetSockOpt / SO_RCVTIMEO / SO_SNDTIMEOはここでは役に立たないので、IOが完了したときに通知されるOVERLAPPED構造のhEventフィールドを使用する必要があると思いますが、その上のWaitForSingleObject / WSAWaitForMultipleEventsはReceive呼び出しをブロックし、Receiveを常にすぐに戻るので、RegisterWaitForSingleObjectとWAITORTIMERCALLBACKを使用しました。動作し、タイムアウト後にコールバックが呼び出されたか、IOが完了しましたが、単一のIO操作に対して2つのコールバック、CompletionRoutineとWaitOrTimerCallbackがあります。

IOが完了した場合、それらは同時に呼び出されます。IOが完了していない場合、WaitOrTimerCallbackが呼び出され、次にCancelIoExを呼び出します。これにより、CompletionRoutineが何らかのABORTEDエラーで呼び出されましたが、競合状態、おそらくIOです。キャンセルする直前に完了します。それから...何とか、全体として非常に複雑です。

次に、実際にはBindIoCompletionCallbackとCompletionRoutineはまったく必要ないことに気付き、WaitOrTimerCallbackからすべてを実行すると、機能する可能性がありますが、ここで興味深い質問があります。そもそもIOCPベースのWinsockサーバーを構築したかったので、BindIoCompletionCallbackはこれを行う最も簡単な方法は、Windows自体が提供するスレッドプールを使用して、IOCPコードがまったくないサーバーになってしまうのでしょうか。それはまだIOCPですか?または、BindIoCompletionCallbackを忘れて、独自のIOCPスレッドプール実装を構築する必要がありますか?なぜ ?

4

2 に答える 2

0

基本的な考え方は、システム スレッド プールで非同期 I/O を使用しているため、スレッドをブロックしていないため、イベントを介してタイムアウトをチェックする必要はないということです。

古い接続を確認するための推奨される方法getsockoptは、SO_CONNECT_TIMEオプションを使用して呼び出すことです。これは、ソケットが接続されている秒数を返します。これがポーリング操作であることはわかっていますが、この値をいつどのように照会するかを理解していれば、実際には接続を管理するための非常に優れたメカニズムです。これがどのように行われるかを以下で説明します。

通常getsockopt、2 つの場所で呼び出します。1 つは完了コールバック中 (そのソケットで最後に I/O 完了が発生したときのタイムスタンプを取得するため) で、もう 1 つは受け入れスレッドにあります。

WSAEventSelect受け入れスレッドは、パラメーターを介してソケットのバックログを監視しFD_ACCEPTます。これは、受け入れが必要な着信接続があると Windows が判断した場合にのみ、受け入れスレッドが実行されることを意味します。この時点で、受け入れたソケットを列挙し、ソケットSO_CONNECT_TIMEごとに再度クエリを実行します。接続の最後の I/O 完了のタイムスタンプをこの値から差し引き、その差が指定されたしきい値を超えている場合、私のコードは接続がタイムアウトしたと見なします。

于 2012-01-10T22:41:15.177 に答える
0

私がしたことは、タイムアウト/完了通知がソケット オブジェクトのクリティカル セクションに入るようにすることでした。いったん入ると、勝者はソケット状態変数を設定し、それが何であれ、そのアクションを実行できます。I/O の完了が最初に行われると、I/O バッファ配列が通常の方法で処理され、タイムアウトが発生すると、ステート マシンによって再起動するように指示されます。同様に、タイムアウトが最初に発生した場合、I/O は CancelIOEx を取得し、後でキューに入れられた完了通知は状態エンジンによって破棄されます。これらの「遅延」通知の可能性があるため、TCP スタック自体がソケットを「TIME_WAIT」に入れる方法と同様の方法で、解放されたソケットをタイムアウト キューに入れ、5 分後にのみソケット オブジェクト プールにリサイクルします。

タイムアウトを行うために、タイムアウト オブジェクトの FIFO デルタ キューで動作する 1 つのスレッド、つまりタイムアウト制限ごとに 1 つのキューがあります。スレッドは、キューの先頭にあるオブジェクトの最小のタイムアウト有効期限から計算されたタイムアウトで、新しいオブジェクトを入力キューで待機します。

サーバーで使用されるタイムアウトはごくわずかだったので、コンパイル時に固定されたキューを使用しました。適切な「コマンド」メッセージを新しいソケットと混合してスレッド入力キューに送信することにより、新しいキューを追加したり、タイムアウトを変更したりするのはかなり簡単ですが、そこまでは行きませんでした。

タイムアウト時に、スレッドはオブジェクト内のイベントを呼び出し、ソケットの場合、ソケット オブジェクト CS 保護ステート マシンに入ります (これらは、特にソケットが派生した TimeoutObject クラスでした)。

もっと:

タイムアウト スレッドの入力キューを制御するセマフォを待機します。通知された場合は、入力キューから新しい TimeoutObject を取得し、要求されたタイムアウト キューの末尾に追加します。セマフォの待機がタイムアウトした場合は、タイムアウト FIFO キューの先頭にあるアイテムをチェックし、タイムアウト時間から現在の時間を引いて、残りの間隔を再計算します。間隔が 0 または負の場合、タイムアウト イベントが呼び出されます。キューとそのヘッドを反復している間、次のタイムアウトまでの残りの最小間隔をローカルに保持します。すべてのキューのすべてのヘッド アイテムの残りの間隔が 0 でない場合、蓄積した最小の残りの間隔を使用して、キュー セマフォでの待機に戻ります。

イベント呼び出しは列挙を返します。この列挙は、イベントが発生したばかりのオブジェクトを処理する方法をタイムアウト スレッドに指示します。1 つのオプションは、タイムアウト時間を再計算してタイムアウトを再開し、最後にオブジェクトをタイムアウト キューに戻すことです。

私は RegisterWaitForSingleObject() を使用しませんでした。.NET が必要で、私の Delphi サーバーはすべて管理されていなかったからです (私はずっと前にサーバーを書きました!)。

それと、IIRC では、WaitForMultipleObjects() のように 64 個のハンドルに制限されているためです。私のサーバーでは、23000 以上のクライアントがタイムアウトしました。単一のタイムアウト スレッドと複数の FIFO キューがより柔軟であることがわかりました。TimeoutObject から派生したものである限り、古いオブジェクトはタイムアウトになる可能性があります。追加の OS 呼び出し/ハンドルは必要ありません。

于 2012-01-04T13:12:12.713 に答える