服务器accept()超时后,客户端套接字connect()成功

时间:2016-02-14 18:41:52

标签: c++ linux sockets

我正在开发一个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;
}

1 个答案:

答案 0 :(得分:1)

这是正常的,不是问题。

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

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

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