IOCPトラップ

9028 ワード

1. AcceptEx 10061
クライアントループ接続、データ送信なし、一定回数後、接続失敗、WSAGEtLastErrorの結果は10061.また、その後は再接続できません.
これは、いずれかのパラメータが、IOCP Input/Output Completion Port IO完了ポートを参照して詳細に使用されるためです.
BOOL AcceptEx(
  SOCKET       sListenSocket,
  SOCKET       sAcceptSocket,
  PVOID        lpOutputBuffer,
  DWORD        dwReceiveDataLength,
  DWORD        dwLocalAddressLength,
  DWORD        dwRemoteAddressLength,
  LPDWORD      lpdwBytesReceived,
  LPOVERLAPPED lpOverlapped
);
dwReceiveDataLength
The number of bytes in lpOutputBuffer that will be used for actual receive data at the beginning of the buffer. This size should not include the size of the local address of the server, nor the remote address of the client; they are appended to the output buffer. If dwReceiveDataLength is zero, accepting the connection will not result in a receive operation. Instead, AcceptEx completes as soon as a connection arrives, without waiting for any data.
ここで、長さが設定されている場合、AcceptExは、接続が成功し、接続の最初のブロックデータが受信されたときに返されることを示す.攻撃されるリスクがあり、接続だけがデータを送信しないと、サーバから送られてくるacceptを待つsocketが消費されてしまいます.クライアントは接続を切断し、GetQueuedCompletionStatusも戻ってこず、接続されたsocketを自分からクエリーしない限り、通知はありません.この現象が発生した後、クライアントは永遠に接続できず、エラー10061を通知します.
WSAECONNREFUSED
10061
Connection refused.
No connection could be made because the target computer actively refused it. This usually results from trying to connect to a service that is inactive on the foreign host—that is, one with no server application running.
解決方法1
マイクロソフトの公式文書の説明に従って、dwReceiveDataLengthを0に設定します.
注意事項
void GetAcceptExSockaddrs(
  PVOID    lpOutputBuffer,
  DWORD    dwReceiveDataLength,
  DWORD    dwLocalAddressLength,
  DWORD    dwRemoteAddressLength,
  sockaddr **LocalSockaddr,
  LPINT    LocalSockaddrLength,
  sockaddr **RemoteSockaddr,
  LPINT    RemoteSockaddrLength
);

  dwReceiveDataLength
The number of bytes in the buffer used for receiving the first data. This value must be equal to the dwReceiveDataLength parameter that was passed to the AcceptEx function. dwReceiveDataLengthとAcceptExのパラメータは一致しなければならないので、ここにも0を記入する.
acceptExFunc(
            listensocket,
            iosocket,
            wsabuf.buf,
            0,
            sizeof(SOCKADDR_IN) + 16,
            sizeof(SOCKADDR_IN) + 16,
            &dwbytes,
            &overlapped
            ))
        {
            if (WSA_IO_PENDING != WSAGetLastError())
            {
                ret = false;
            }
        }
getAcceptExSockFunc(
        wsabuf.buf,
        0,
        sizeof(SOCKADDR_IN) + 16,
        sizeof(SOCKADDR_IN) + 16,
        (LPSOCKADDR*)&localaddr,
        &localaddrlen,
        (LPSOCKADDR*)&clientaddr,
        &clientaddrlen
    );

これによりアドレス情報を得ることができ,接続がデータを送信していない攻撃を回避することができる.
解決方法2
If a receive buffer is provided, the overlapped operation will not complete until a connection is accepted and data is read. Use the getsockopt function with the SO_CONNECT_TIME option to check whether a connection has been accepted. If it has been accepted, you can determine how long the connection has been established. The return value is the number of seconds that the socket has been connected. If the socket is not connected, the getsockopt returns 0xFFFFFFFF. Applications that check whether the overlapped operation has completed, in combination with the SO_CONNECT_TIME option, can determine that a connection has been accepted but no data has been received. Scrutinizing a connection in this manner enables an application to determine whether connections that have been established for a while have received no data. It is recommended such connections be terminated by closing the accepted socket, which forces the AcceptEx function call to complete with an error.
int getsockopt(
  SOCKET s,
  int    level,
  int    optname,
  char   *optval,
  int    *optlen
);

Parameters s
A descriptor identifying a socket.
ソケットハンドルに対応します.level
The level at which the option is defined. Example: SOL_SOCKET.
取得するパラメータがどのレベル分類に属するか.optname
The socket option for which the value is to be retrieved. Example: SO_ACCEPTCONN. The optname value must be a socket option defined within the specified level, or behavior is undefined.
socketの対応プロパティ.optval
A pointer to the buffer in which the value for the requested option is to be returned.
対応するデータのポインタを返します.optlen
A pointer to the size, in bytes, of the optval buffer.
対応するデータの長さを返します.ここから本を読むことができます.上のデータは自分で申請して、伝えなければなりません.
SO_CONNECT_TIMEはSOL_に属するSOCKET.
 
SO_CONNECT_TIME
DWORD
Returns the number of seconds a socket has been connected. This socket option is valid for connection oriented protocols only.
 
 
INT seconds;
INT bytes = sizeof(seconds);
int iResult = 0;

iResult = getsockopt( sAcceptSocket, SOL_SOCKET, SO_CONNECT_TIME,
                      (char *)&seconds, (PINT)&bytes );

if ( iResult != NO_ERROR ) {
    printf( "getsockopt(SO_CONNECT_TIME) failed: %u
", WSAGetLastError( ) ); exit(1); }

 
もう1つの解決策は、AcceptExがデータを受信するパラメータは0ではありませんが、getsockoptを介してsocket接続がどのくらい接続されているかをタイミングよく取得し、データを送信せずにタイムアウトしたsocketを切断する必要があります.しかし,この関数の精度は秒であり,設計もより複雑であり,使用を推奨しない.
2.WSArecv/WSASEndすぐに戻る処理
リファレンスhttps://tboox.org/cn/2018/08/16/coroutine-iocp-some-issues/
開発の過程で、この2つの関数の紹介を見てすぐに戻る可能性がありますが、対応する使い方が見つからず、公式にも特に説明されていないので、気にしませんでした.しかし、すぐに戻ってきたら、どう処理すればいいのか、ワークスレッドで処理しなくてもいいのではないか、と心の中で考えています.
処理の有無にかかわらずGetQueuedCompletionStatusを呼び出すとシステムが戻ります.だから処理しないか、著者が書いたように識別して、処理した作業スレッドは省略しないでください.
3.GetQueuedCompletionStatusExシングルリクエストが遅い
リファレンスhttps://tboox.org/cn/2018/08/16/coroutine-iocp-some-issues/
GetQueuedCompletionStatusExは、1回に複数リクエストでき、呼び出しとスレッドの切り替え回数を減らし、効率を向上させることができます.しかし,著者らは,単一IOリクエストであれば,GetQueuedCompletionStatusExの効率はかえって遅くなると測定した.具体的な原因は不明であるが,著者らの説明によれば,最近のIOリクエストにより呼び出しの関数を動的に調整する.
4.他スレッドIOリクエストのキャンセル
リファレンスhttps://tboox.org/cn/2018/08/16/coroutine-iocp-some-issues/
CancelIOは現在のスレッド配信をキャンセルするioイベントにのみ使用できます.他のスレッド配信をキャンセルするioイベントにはCancelIOExを使用する必要があります.