【问题标题】:How to use select() to set a timer for sockets?如何使用 select() 为套接字设置计时器?
【发布时间】:2021-06-15 16:26:34
【问题描述】:

我目前正在使用 Winsock2 来测试与多个本地 telnet 服务器的连接,但如果服务器连接失败,default Winsock client 将永远超时。​​

我从其他帖子中看到select() 可以为连接部分设置超时,而setsockopt()timeval 可以使代码的接收部分超时,但我不知道如何实现.我从其他答案复制/粘贴的代码片段对我来说似乎总是失败。

如何在默认客户端代码中同时使用这两个函数?或者,如果在默认客户端代码中无法使用这些功能,有人可以给我一些关于如何正确使用这些功能的指导吗?


#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctime>

// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")


#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "23"



int __cdecl main(int argc, char** argv)
{
    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo* result = NULL,
        * ptr = NULL,
        hints;
    const char* sendbuf = "this is a test";
    char recvbuf[DEFAULT_BUFLEN];
    int iResult;
    int recvbuflen = DEFAULT_BUFLEN;

    // Validate the parameters
    if (argc != 2) {
        printf("usage: %s server-name\n", argv[0]);
        return 1;
    }

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    /*
    struct timeval timeout;
    timeout.tv_sec = 10;
    timeout.tv_usec = 0;

    if (setsockopt(ConnectSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout,
        sizeof(timeout)) < 0) {
        printf("setsockopt failed\n");
    }
    */

    




    // Resolve the server address and port
    iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }





    // Attempt to connect to an address until one succeeds
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {

        // Create a SOCKET for connecting to server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }

        // Connect to server.
        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }

    freeaddrinfo(result);

    if (ConnectSocket == INVALID_SOCKET) {
        printf("Unable to connect to server!\n");
        WSACleanup();
        return 1;
    }

    // Send an initial buffer
    iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
    if (iResult == SOCKET_ERROR) {
        printf("send failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    printf("Bytes Sent: %ld\n", iResult);

    // shutdown the connection since no more data will be sent
    iResult = shutdown(ConnectSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // Receive until the peer closes the connection
    do {

        iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0)
            printf("Bytes received: %d\n", iResult);
        else if (iResult == 0)
            printf("Connection closed\n");
        else
            printf("recv failed with error: %d\n", WSAGetLastError());

    } while (iResult > 0);

    // cleanup
    closesocket(ConnectSocket);
    WSACleanup();

    return 0;
}

【问题讨论】:

    标签: c++ winsock2


    【解决方案1】:

    select()可以设置连接部分的超时时间。

    是的,但前提是你在调用connect()之前将socket设置为非阻塞模式,这样connect()会立即退出然后代码可以使用select()等待socket在connect操作时报告已完成。但是显示的代码并没有这样做。

    setsockopt()timeval 可以使代码的接收部分超时

    是的,尽管select() 也可用于使读取操作超时。只需先调用select(),然后仅当select() 报告套接字可读(有待读取的数据)时才调用recv()

    试试这样的:

    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <ctime>
    
    // Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
    #pragma comment (lib, "Ws2_32.lib")
    #pragma comment (lib, "Mswsock.lib")
    #pragma comment (lib, "AdvApi32.lib")
    
    #define DEFAULT_BUFLEN 512
    #define DEFAULT_PORT "23"
    
    int __cdecl main(int argc, char** argv)
    {
        WSADATA wsaData;
        SOCKET ConnectSocket = INVALID_SOCKET;
        struct addrinfo *result = NULL,
            *ptr = NULL,
            hints;
        const char* sendbuf = "this is a test";
        char recvbuf[DEFAULT_BUFLEN];
        int iResult;
        int recvbuflen = DEFAULT_BUFLEN;
    
        // Validate the parameters
        if (argc != 2) {
            printf("usage: %s server-name\n", argv[0]);
            return 1;
        }
    
        // Initialize Winsock
        iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (iResult != 0) {
            printf("WSAStartup failed with error: %d\n", iResult);
            return 1;
        }
    
        ZeroMemory(&hints, sizeof(hints));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
    
        // Resolve the server address and port
        iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
        if (iResult != 0) {
            printf("getaddrinfo failed with error: %d\n", iResult);
            WSACleanup();
            return 1;
        }
    
        // Attempt to connect to an address until one succeeds
        for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
    
            // Create a SOCKET for connecting to server
            ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
            if (ConnectSocket == INVALID_SOCKET) {
                printf("socket failed with error: %ld\n", WSAGetLastError());
                WSACleanup();
                return 1;
            }
    
            // put the socket into non-blocking mode just for connect()
            int iMode = 1;
            iResult = ioctlsocket(ConnectSocket, FIONBIO, &iMode);
            if (iResult == SOCKET_ERROR)
                closesocket(ConnectSocket);
                ConnectSocket = INVALID_SOCKET;
                continue;
            }
    
            // Connect to server.
            iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
            if (iResult == SOCKET_ERROR) {
                if (WSAGetLastError() != WSAEWOULDBLOCK) {
                    closesocket(ConnectSocket);
                    ConnectSocket = INVALID_SOCKET;
                    continue;
                }
    
                // wait for the connect to finish
                fd_set fds;
                FD_ZERO(&fds);
                FD_SET(ConnectSocket, &fds);
    
                struct timeval timeout;
                timeout.tv_sec = 10;
                timeout.tv_usec = 0;
    
                iResult = select(0, NULL, &fds, NULL, &timeout);
                if (iResult <= 0) {
                    closesocket(ConnectSocket);
                    ConnectSocket = INVALID_SOCKET;
                    continue;
                }
            }
    
            // put the socket back into blocking mode
            iMode = 0;
            iResult = ioctlsocket(ConnectSocket, FIONBIO, &iMode);
            if (iResult == SOCKET_ERROR) {
                closesocket(ConnectSocket);
                ConnectSocket = INVALID_SOCKET;
                continue;
            }
    
            break;
        }
    
        freeaddrinfo(result);
    
        if (ConnectSocket == INVALID_SOCKET) {
            printf("Unable to connect to server!\n");
            WSACleanup();
            return 1;
        }
    
        struct timeval timeout;
        timeout.tv_sec = 10;
        timeout.tv_usec = 0;
    
        // set a read timeout
        if (setsockopt(ConnectSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) == SOCKET_ERROR) {
            printf("setsockopt failed\n");
        }
    
        // Send an initial buffer
        iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
        if (iResult == SOCKET_ERROR) {
            printf("send failed with error: %d\n", WSAGetLastError());
            closesocket(ConnectSocket);
            WSACleanup();
            return 1;
        }
    
        printf("Bytes Sent: %ld\n", iResult);
    
        // shutdown the connection since no more data will be sent
        iResult = shutdown(ConnectSocket, SD_SEND);
        if (iResult == SOCKET_ERROR) {
            printf("shutdown failed with error: %d\n", WSAGetLastError());
            closesocket(ConnectSocket);
            WSACleanup();
            return 1;
        }
    
        // Receive until the peer closes the connection
        do {
            /*
            fd_set fds;
            FD_ZERO(&fds);
            FD_SET(ConnectSocket, &fds);
    
            struct timeval timeout;
            timeout.tv_sec = 10;
            timeout.tv_usec = 0;
    
            iResult = select(0, &fds, NULL, NULL, &timeout);
            if (iResult <= 0) {
                if (iResult == SOCKET_ERROR)
                    printf("select failed with error: %d\n", WSAGetLastError());      
                else
                    printf("select timed out\n");
                break;
            }
            */
    
            iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
            if (iResult > 0)
                printf("Bytes received: %d\n", iResult);
            else if (iResult == 0)
                printf("Connection closed\n");
            else if (WSAGetLastError() == WSAETIMEDOUT)
                printf("recv timed out\n");
            else
                printf("recv failed with error: %d\n", WSAGetLastError());
    
        } while (iResult > 0);
    
        // cleanup
        closesocket(ConnectSocket);
        WSACleanup();
    
        return 0;
    }
    

    【讨论】:

    • 其实如果你不介意一个简单的问题,为什么你必须在使用 select() 之后将端口设置回阻塞?
    • 默认情况下以阻塞模式创建套接字。您必须明确请求非阻塞模式。如果您愿意,您当然可以让套接字处于非阻塞模式。不过,管理非阻塞套接字需要做更多的工作。在我的示例中,套接字仅在connect() 操作期间处于非阻塞模式,因为这是实现connect() 超时的唯一方法(尽管可以通过多种其他方式处理实际超时 -通过WSAAsyncSelect()WSAEventSelect()、重叠 I/O、IOCP 等)。为简单起见,示例的其余部分坚持使用阻塞模式。
    猜你喜欢
    • 2020-07-16
    • 2019-05-30
    • 2010-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多