2

非ブロッキング モードでソケットを使用すると、WSAConnect 関数が WSAEINVAL エラーを返すことがあります。問題を調査したところ、WSAConnect 関数呼び出しの間に一時停止がない (または非常に短い) 場合に発生することがわかりました。この状況を回避する方法を知っている人はいますか?以下に、問題を再現するソース コードを示します。Sleep 機能のパラメータの値を 50 以上に増やすと、問題はなくなります。
PS この問題は Windows XP でのみ再現され、Win7 では問題なく動作します。

    #undef UNICODE
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #include <stdio.h>
    #include <iostream>
    #include <windows.h>

    #pragma comment(lib, "Ws2_32.lib")

    static int getError(SOCKET sock)
    {
        DWORD error = WSAGetLastError();
        return error;
    }

    void main()
    {
        SOCKET sock;
        WSADATA wsaData;
        if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
            fprintf(stderr, "Socket Initialization Error. Program aborted\n");
            return;
        }

        for (int i = 0; i < 1000; ++i) {
            struct addrinfo hints;
            struct addrinfo *res = NULL;
            memset(&hints, 0, sizeof(hints));
            hints.ai_flags = AI_PASSIVE;
            hints.ai_socktype = SOCK_STREAM;
            hints.ai_family = AF_INET;
            hints.ai_protocol = IPPROTO_TCP;

            if (0 != getaddrinfo("172.20.1.59", "8091", &hints, &res)) {
                fprintf(stderr, "GetAddrInfo Error. Program aborted\n");
                closesocket(sock);
                WSACleanup();
                return;
            }

            struct addrinfo *ptr = 0;
            for (ptr=res; ptr != NULL ;ptr=ptr->ai_next) {
                sock = WSASocket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol, NULL, 0, NULL);    // 

                if (sock == INVALID_SOCKET) 
                    int err = getError(sock);
                else {
                    u_long noblock = 1;
                    if (ioctlsocket(sock, FIONBIO, &noblock) == SOCKET_ERROR) {
                        int err = getError(sock);
                        closesocket(sock);
                        sock = INVALID_SOCKET;
                    }   
                    break;
                }
            }
            int ret;

            do {
                ret = WSAConnect(sock, ptr->ai_addr, (int)ptr->ai_addrlen, NULL, NULL, NULL, NULL);

                if (ret == SOCKET_ERROR) {
                    int error = getError(sock);

                    if (error == WSAEWOULDBLOCK) {
                        Sleep(5);
                        continue;
                    }
                    else if (error == WSAEISCONN) {
                        fprintf(stderr, "+");
                        closesocket(sock);
                        sock = SOCKET_ERROR;
                        break;
                    }
                    else if (error == 10037) {
                        fprintf(stderr, "-");
                        closesocket(sock);
                        sock = SOCKET_ERROR;
                        break;
                    }
                    else {
                        fprintf(stderr, "Connect Error. [%d]\n", error);
                        closesocket(sock);
                        sock = SOCKET_ERROR;
                        break;
                    }
                }
                else {
                    int one = 1;
                    setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one));
                    fprintf(stderr, "OK\n");
                    break;
                }
            }
            while (1);
        }

        std::cout<<"end";
        char ch;
        std::cin >> ch;
    }
4

1 に答える 1

4

ここには、かごいっぱいのエラーと疑わしい設計およびコーディングの決定があります。それらを 2 つのグループに分ける必要があります。

明らかなエラー

このセクションのすべての項目を修正すると、症状が消えると思いますが、どれが重要な修正であるかについては推測したくありません。

  • connect()単一のソケットでループを呼び出すのは、単純に間違っています。

    接続を確立し、ドロップし、再確立することを 1000 回closesocket()繰り返す場合は、各ループの最後に呼び出してから、socket()再度呼び出して新しいソケットを取得する必要があります。同じソケットを再接続し続けることはできません。電源プラグのようなものだと考えてください。2 回差し込む場合は、2 回closesocket()の間にプラグを抜く ( ) 必要があります。

    代わりに、1000 の同時接続を確立する場合は、socket()反復ごとに新しいソケットを割り当てる必要があります。connect()それから、もう一度戻って別のソケットを取得します。closesocket()呼び出しがないことを除いて、基本的には前のケースと同じループです。

    XP は Windows のクライアント バージョンであるため、数千の同時ソケットを処理するように最適化されていないことに注意してください。

  • connect()再度呼び出すことは、次に対する正しい応答ではありませんWSAEWOULDBLOCK

    if (error == WSAEWOULDBLOCK) {
        Sleep(5);
        continue; /// WRONG!
    }
    

    そのcontinueコードは上記と同じエラーを効果的にコミットしますが、さらに悪いことに、前のエラーを修正してこれをそのままにしておくと、この使用法によりコードでソケットがリークし始めます。

    WSAEWOULDBLOCKエラーではありません。connect()ブロックしていないソケットで接続した後、接続がすぐに確立されなかったことを意味します。スタックは、そのときにプログラムに通知します。

    select()WSAEventSelect()、またはのいずれかを呼び出して、その通知を取得しますWSAAsyncSelect()。を使用するselect()と、接続が確立されたときにソケットが書き込み可能とマークされます。他の 2 つはFD_CONNECT、接続が確立されたときにイベントを取得します。

    これら 3 つの API のどれを呼び出すかは、そもそもノンブロッキング ソケットが必要な理由と、プログラムの残りの部分がどのようになるかによって異なります。これまでのところ、ノンブロッキングソケットはまったく必要ありませんが、決定を通知する将来の計画があると思います. どの I/O 戦略を使用すべきか( Winsock Programmers' FAQ の一部)という記事を書きました。これは、これらのオプションのどれを使用するかを決定するのに役立ちます。代わりに、完全に別のオプションに案内される場合があります。

  • 同じソケットでAI_PASSIVEandを使用しないでください。withをconnect()使用すると、このソケットを使用して着信接続を受け入れる予定であることをスタックに伝えます。次に、そのソケットを使用して発信接続を確立します。AI_PASSIVEgetaddrinfo()

    あなたは基本的にここでスタックに嘘をつきました。コンピューターは、あなたが嘘をついたときに復讐する方法を見つけます。

  • Sleep()Winsock の問題を解決する正しい方法ではありません。TIME_WAITおよびNagle アルゴリズムなど、プログラムが認識できるスタック内に組み込みの遅延がありますがSleep()、これらに対処する正しい方法でもありません。

疑わしいコーディング/設計上の決定

このセクションは、あなたの症状がなくなるとは思わないものについてのものですが、とにかくそれらを修正することを検討する必要があります:

  • 使用する主な理由はgetaddrinfo()inet_addr()IPv6 をサポートする必要がある場合です。XP の IPv6 スタックは、XP が Windows の現在のバージョンであった間、その IPv4 スタックほど厳密にテストされていなかったため、このようなことは XP をサポートしたいというあなたの希望と矛盾します。結果として、すべてのパッチをインストールしたとしても、XP の IPv6 スタックにはまだバグがあると思います。

    IPv6 サポートが本当に必要ない場合は、古い方法で行うと、症状が消える可能性があります。XP 用の IPv4 のみのビルドが必要になる場合があります。

  • このコード:

    for (int i = 0; i < 1000; ++i) {
        // ...
        if (0 != getaddrinfo("172.20.1.59", "8091", &hints, &res)) {
    

    ...非効率です。res各ループで再初期化を続ける必要がある理由はありません。

    表示されない何らかの理由がある場合でも、 を呼び出さないことでメモリ リークが発生していfreeaddrinfo()ますres

    ループに入る前にこのデータ構造を一度初期化してから、反復ごとに再利用する必要があります。

  • else if (error == 10037) {

    なんでWSAEALREADYここ使わないの?

  • ここを使う必要はありませんWSAConnect()。Winsock が BSD ソケットと共有する 3 つの引数のサブセットを使用しています。connect()代わりにここを使用することもできます。

    コードを必要以上に複雑にする意味はありません。

  • なぜswitchこれに声明を使わないのですか?

    if (error == WSAEWOULDBLOCK) {
        // ...
    }
    else if (error == WSAEISCONN) {
        // ...
    }
    // etc.
    
  • Nagle アルゴリズムを無効にしないでください。

    setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, ...);
    
于 2013-03-14T23:19:34.960 に答える