【问题标题】:Client socket connect() succeeds after server accept() times out服务器accept()超时后客户端socket connect()成功
【发布时间】:2016-02-14 18:41:51
【问题描述】:

我正在开发一个 c++ 类,它充当 linux 中套接字的高级包装器。在测试它时,我故意让客户端应用程序在调用 connect() 之前休眠几秒钟,从而使服务器的 accept() 调用超时。

但是,在服务器超时后,客户端应用程序仍然能够调用 connect() 并发送数据而不会检测到错误。这显然是个问题,因为服务器没有接收到数据,所以客户端应该知道连接失败了。

这是我的代码。服务器应用调用 Socket::accept_connection(),客户端应用休眠,然后调用 Socket::connect_to()。

// Accept a connection on the server side with a timeout
Socket *Socket::accept_connection(double timeout) {
    Socket *new_connection = NULL;

    socklen_t sin_size;
    struct sockaddr_storage client_address;  // Client's address
    struct sockaddr_in client_port_address;  // Client's port
    char s[INET6_ADDRSTRLEN];
    sin_size = sizeof client_address;

    fd_set rfds;
    struct timeval timeout_structure;

    timeout_structure.tv_sec = (long)(timeout);
    timeout_structure.tv_usec = int((timeout - timeout_structure.tv_sec) * 1e6);
    struct timeval *timeout_ptr = NULL;
    if(timeout > 0)
        timeout_ptr = &timeout_structure;

    // Loop until the timeout has been reached
    while(true) {
        FD_ZERO(&rfds);
        FD_SET(socket_desc, &rfds);
        if(select(socket_desc + 1, &rfds, NULL, NULL, timeout_ptr) > 0) {
            int client_sock = accept(socket_desc, (struct sockaddr *)&client_address, &sin_size);

            if(client_sock == -1) {
                // Failed to connect
                connected = false;
                continue;

            } else {

                // Connected 
                inet_ntop(client_address.ss_family, get_in_addr((struct sockaddr *)&client_address), s, sizeof s);
                getpeername(client_sock, (struct sockaddr*)&client_port_address, &sin_size);
                int client_port = ntohs(client_port_address.sin_port);
                // ...
            }
        } else {
            // Timed out
            connected = false;
            std::cout << "accept() timed out\n";
            break;
        }
    }
    return new_connection;
}

// Connect to the given ip address and port
bool Socket::connect_to(std::string server_ip, int server_port, double timeout) {
    connected = false;
    // Create the socket and allocate memory for reading in data
    struct addrinfo hints, *servinfo, *p;
    int rv;
    char s[INET6_ADDRSTRLEN];

    struct timeval timeout_structure;
    timeout_structure.tv_sec = (long)(timeout);
    timeout_structure.tv_usec = int((timeout - timeout_structure.tv_sec) * 1e6);
    struct timeval *timeout_ptr = NULL;
    if(timeout > 0)
        timeout_ptr = &timeout_structure;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    if ((rv = getaddrinfo(server_ip.c_str(), std::to_string(server_port).c_str(), &hints, &servinfo)) != 0) {
        fprintf(stderr, "Socket error: connect_to, getaddrinfo: %s\n", gai_strerror(rv));
        throw;
    }
    // loop through all the results and connect to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((socket_desc = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            perror("Socket error: connect_to, socket");
            continue;
        }

        int flags = 0, error = 0, ret = 0;
        fd_set  rset, wset;
        socklen_t   len = sizeof(error);

        //clear out descriptor sets for select
        //add socket to the descriptor sets
        FD_ZERO(&rset);
        FD_SET(socket_desc, &rset);
        wset = rset;    //structure assignment ok
        //set socket nonblocking flag
        if((flags = fcntl(socket_desc, F_GETFL, 0)) < 0)
            continue;
        if(fcntl(socket_desc, F_SETFL, flags | O_NONBLOCK) < 0)
            continue;
        //initiate non-blocking connect
        if(ret = connect(socket_desc, p->ai_addr, p->ai_addrlen) == -1) {
            if (errno != EINPROGRESS) {
                close(socket_desc);
                perror("Socket error: connect_to, could not connect");
                continue;
            }
        }
        if(ret != 0) {    // If connect did not succeed right away
            // We are waiting for connect to complete now
            if((ret = select(socket_desc + 1, NULL, &wset, NULL, timeout_ptr)) < 0)
                return false;
            if(ret == 0){   //we had a timeout
                errno = ETIMEDOUT;
                return false;
            }
            //we had a positive return so a descriptor is ready
            if(FD_ISSET(socket_desc, &rset) || FD_ISSET(socket_desc, &wset)){
                if(getsockopt(socket_desc, SOL_SOCKET, SO_ERROR, &error, &len) < 0 || error != 0)
                    return false;
            } else
                return false;

            if(error){  //check if we had a socket error
                errno = error;
                return false;
            }
        }
        //put socket back in blocking mode
        if(fcntl(socket_desc, F_SETFL, flags) < 0)
            return false;
        break;
    }
    if(p == NULL) {
        fprintf(stderr, "Socket error: connect_to, failed to connect\n");
        socket_desc = 0;
        return false;
    }
    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s);
    freeaddrinfo(servinfo); // all done with this structure
    connected = true;

    return connected;
}

【问题讨论】:

    标签: c++ linux sockets


    【解决方案1】:

    这是正常的,不是问题。

    1. TCP 维护一个侦听积压队列,将已由 TCP 堆栈完成但尚未被应用程序接受的连接放置在该队列中。
    2. TCP 为每个套接字维护一个套接字接收缓冲区,从对等端到达但尚未被应用程序读取的数据放入其中。

    客户端应该知道连接失败。

    它没有失败。服务器可以接受它并读取数据。

    【讨论】:

    • accept() 超时时,有没有办法从积压队列中取消/删除连接?
    • @user1389840 没有,没有关闭监听套接字。
    • @user1389840 如果您不再接受任何连接,请关闭套接字。如果是,那么为什么需要取消/删除连接?如果您想接受/拒绝特定连接,请在新连接上调用accept,然后调用close
    • @EJP 和 DavidSchwartz,谢谢,我应该已经意识到,如果我听完了,我可以关闭服务器的套接字,或者如果我没有,我可以再次调用 accept()。那行得通。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-19
    • 2018-06-24
    • 1970-01-01
    • 2018-04-24
    • 2022-07-06
    • 2012-11-14
    • 2017-10-10
    相关资源
    最近更新 更多