c++ 如何确定具有Winsock超时的连接是否成功

ee7vknir  于 2023-04-13  发布在  其他
关注(0)|答案(1)|浏览(145)

我使用the following C++ code创建了一个TCP/IP客户端。在实际使用中,我在一个循环中使用它,并不断检查连接,直到服务器准备就绪。
在连接到服务器之前,这段代码会按顺序0.2、0.6、1循环。
在成功连接到服务器之后,什么都不会打印。没有错误被打印,几乎没有任何地方被传递。
如何知道TCP/IP连接是否成功?

int connect_timeout(int & socket, struct sockaddr * name, int namelen, timeval timeout)
{
    unsigned long mode = 1;
    int result= ioctlsocket(socket, FIONBIO, &mode);
    if(NO_ERROR != result)
    {
        //std::cout << "0.0---------------" << std::endl;
        return -1;
    }
    else
    {
        //std::cout << "0.1---------------" << std::endl;
    }

    result= connect(socket, name, namelen);
    if(SOCKET_ERROR == result)
    {
        int errono = WSAGetLastError();
        if(WSAEWOULDBLOCK == errono)
        {
            std::cout << "0.2---------------" << std::endl;
            errono = 0;
        }
        else
        {
            //std::cout << "0.3---------------" << std::endl;
            mode = 0;
            ioctlsocket(socket, FIONBIO, &mode);
            return -1;
        }
    }
    else
    {
        std::cout << "0.4---------------" << std::endl;
    }

    mode = 0;
    result= ioctlsocket(socket, FIONBIO, &mode);
    if(0 < result)
    {
        //error
        std::cout << "0.5---------------" << std::endl;
        return -1;
    }
    else
    {
        std::cout << "0.6---------------" << std::endl;
    }
    
    fd_set readFd, writeFd, errFd;
    FD_ZERO(&readFd);
    FD_ZERO(&writeFd);
    FD_ZERO(&errFd);
    FD_SET(socket, &readFd);
    FD_SET(socket, &writeFd);
    FD_SET(socket, &errFd);
    int sockNum = select(socket + 1, &readFd, &writeFd, &errFd, &timeout);
    if(0 == sockNum)
    {
        //timeout
        std::cout << "1---------------" << std::endl;
        return -1;
    }
    else if(FD_ISSET(socket, &readFd) || FD_ISSET(socket, &writeFd) )
    {
        std::cout << "2---------------" << std::endl;
    }
    else
    {
        //error
        std::cout << "3---------------" << std::endl;
        return -1;
    }
    std::cout << "4---------------" << std::endl;

    return 0;
}
uujelgoq

uujelgoq1#

connect() documentation说道:
使用非阻塞套接字,连接尝试不能立即完成。在这种情况下,connect将返回SOCKET_ERRORWSAGetLastError将返回WSAEWOULDBLOCK。在这种情况下,有三种可能的情况:

*使用select函数检查套接字是否可写,判断连接请求是否完成

  • ...

在连接尝试在非阻塞套接字上完成之前,在同一套接字上对connect的所有后续调用都将失败,并在连接成功完成时显示错误代码WSAEALREADYWSAEISCONN。当连接已经挂起时从connect返回的错误代码可能因实现而异。**因此,不建议应用程序使用对connect的多个调用来检测连接完成。**如果它们这样做,它们必须准备以与处理WSAEALREADY相同的方式处理WSAEINVALWSAEWOULDBLOCK误差值,以确保稳健的操作。
...
对于面向连接的非阻塞套接字,通常不可能立即完成连接。在这种情况下,此函数返回错误WSAEWOULDBLOCK。但是,操作继续进行。
当成功或失败结果变得已知时,可以通过两种方式之一来报告,这取决于客户端如何注册通知。

*如果客户端使用select函数,则在writefds集合中报告成功,在exceptfds集合中报告失败。

  • ...

select() documentation说:
参数writefds标识要检查可写性的套接字。如果套接字正在处理connect调用(非阻塞),则在连接建立成功完成时套接字是可写的。...
参数exceptfds标识要检查是否存在OOB数据或任何异常错误条件的套接字。
注意:只有当选项SO_OOBINLINE为FALSE时,才会以这种方式报告带外数据。**如果套接字正在处理connect调用(非阻塞),exceptfds中指示连接尝试失败(然后应用程序必须调用getsockoptSO_ERROR来确定错误值,以描述发生故障的原因)。**本文档未定义将包括哪些其他错误。
在调用select()之前,您关闭了套接字的非阻塞模式。您需要等待非阻塞connect()操作完成,然后才能更改套接字的设置。
尝试更像这样的东西:

int connect_timeout(int & socket, struct sockaddr * name, int namelen, timeval timeout)
{
    u_long mode = 1;
    int errNo;

    int result = ioctlsocket(socket, FIONBIO, &mode);
    if (SOCKET_ERROR == result)
    {
        errNo = WSAGetLastError();
        std::cout << "Unable to set socket to non-blocking mode. Error: " << errNo << std::endl;
        return -1;
    }

    std::cout << "Connecting..." << std::endl;

    result = connect(socket, name, namelen);
    if (SOCKET_ERROR == result)
    {
        errNo = WSAGetLastError();
        if (WSAEWOULDBLOCK == errNo)
        {
            fd_set writeFd, errFd;
            FD_ZERO(&writeFd);
            FD_ZERO(&errFd);
            FD_SET(socket, &writeFd);
            FD_SET(socket, &errFd);

            result = select(socket + 1, NULL, &writeFd, &errFd, &timeout);
            if (SOCKET_ERROR == result)
            {
                errNo = WSAGetLastError();
            }
            else if (0 == result)
            {
                result = SOCKET_ERROR;
                errNo = WSAETIMEDOUT;
            }
            else if (FD_ISSET(socket, &errFd))
            {
                result = SOCKET_ERROR;
                errNo = 0;
                int len = sizeof(errNo);
                getsockopt(socket, SOL_SOCKET, SO_ERROR, (char*)&errNo, &len);
            }
            else
            {
                result = errNo = 0;
            }
        }

        if (SOCKET_ERROR == result)
        {
            if (WSAETIMEDOUT == errNo)
                std::cout << "Connection timed out" << std::endl;
            else
                std::cout << "Unable to connect. Error: " << errNo << std::endl;

            mode = 0;
            ioctlsocket(socket, FIONBIO, &mode);
            return -1;
        }
    }

    std::cout << "Connected" << std::endl;

    mode = 0;
    result = ioctlsocket(socket, FIONBIO, &mode);
    if (SOCKET_ERROR == result)
    {
        errNo = WSAGetLastError();
        std::cout << "Unable to set socket to blocking mode. Error: " << errNo << std::endl;
        return -1;
    }

    return 0;
}

相关问题