WSAConnect在Windows XP上返回WSAEINVAL

时间:2013-03-14 07:07:57

标签: winsock nonblocking

我在非阻塞模式下使用套接字,有时WSAConnect函数返回WSAEINVAL错误。 我调查了一个问题并发现,如果之间没有暂停(或者它非常小),就会发生这种情况 WSAConnect函数调用。 有谁知道如何避免这种情况? 您可以在下面找到重现问题的源代码。如果我将Sleep函数中的参数值增加到50或者很大 - 问题消失 附:此问题仅在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;
    }

1 个答案:

答案 0 :(得分:4)

这里有一大堆错误和可疑的设计和编码决策。我将不得不将它们分成两组:

直接错误

我希望如果你修复了本节中的所有项目,你的症状就会消失,但我不想推测哪一个是关键修复:

  • 在单个套接字上循环调用connect()是完全错误的。

    如果您要建立连接,将其删除并重新建立1000次,则需要在每次循环结束时调用closesocket(),然后再次调用socket()以获取新的套接字。您不能继续重新连接相同的套接字。可以把它想象成一个电源插头:如果要将其插入两次,则必须在两次之间拔掉插头(closesocket())。

    如果您要建立1000个并发连接,则需要在每次迭代socket()上分配一个带connect()的新套接字,然后再次返回以获取另一个套接字。除了没有closesocket()调用之外,它与前一个案例的循环基本相同。

    请注意,由于XP是Windows的客户端版本,因此未针对处理数千个同步套接字进行优化。

  • 再次致电connect() WSAEWOULDBLOCK的正确回复:

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

    continue代码有效地提交了与上面相同的错误,但更糟糕的是,如果您只修复了上一个错误并保留此错误,则此用法将使您的代码开始泄漏套接字。

    WSAEWOULDBLOCK is not an error.所有这意味着在非阻塞套接字上的connect()之后,连接没有立即建立。堆栈将在何时通知您的程序。

    您可以通过致电select()WSAEventSelect()WSAAsyncSelect()来获取该通知。如果使用select(),则在建立连接时,套接字将被标记为可写。使用其他两个,当连接建立时,您将收到FD_CONNECT事件。

    要调用这三个API中的哪一个取决于您首先想要非阻塞套接字的原因,以及程序的其余部分是什么样的。到目前为止我所看到的根本不需要非阻塞套接字,但我想你有一些未来的计划可以告知你的决定。我写了一篇文章 Which I/O Strategy Should I Use the Winsock Programmers' FAQ的一部分),它将帮助您决定使用哪些选项;它可能会完全引导您进入另一个选项。

  • 您不应在同一套接字上使用AI_PASSIVEconnect()。您对AI_PASSIVE getaddrinfo()的使用告诉您打算使用此套接字接收传入连接的堆栈。然后,您可以使用该套接字建立传出连接。

    你基本上在这里撒谎。当你骗他们时,计算机会找到报复的方法。

  • Sleep()永远不是解决Winsock问题的正确方法。堆栈中存在程序可以看到的内置延迟,例如TIME_WAITthe Nagle algorithm,但Sleep()也不是解决这些问题的正确方法。

可疑的编码/设计决策

此部分适用于我不希望让您的症状消失的内容,但无论如何您应该考虑修复它们:

  • 使用getaddrinfo()的主要原因 - 与inet_addr()等较旧,较简单的功能相反 - 是否必须支持IPv6。这与您支持XP的愿望存在冲突,因为在XP的当前版本作为其IPv4堆栈的时候,XP的IPv6堆栈并没有经过多次测试。我希望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.
    
  • You shouldn't disable the Nagle algorithm:

    setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, ...);