Search code examples
ctcp

TCP cannot detect closed socket in client


I have server that just connects to a client and right after that disconnects, while client tries to send an integer to a closed socket (scanf is to ensure server closese it first). I use send with MSG_NOSIGNAL and check for EPIPE but the flag is not set. I think result should have printed value of -1, or 0, but it is equal to 1, because I am writing on already closed socket. Can someone explain that?

Server Code:

#define QUEUE_LENGTH     5
#define PORT_NUM     10002
#define BUFFER_SIZE 512000

int main(int argc, char *argv[]) {
    int sock, msg_sock;
    struct sockaddr_in server_address;
    struct sockaddr_in client_address;
    socklen_t client_address_len;

    sock = socket(PF_INET, SOCK_STREAM, 0); // creating IPv4 TCP socket
    if (sock < 0)
        syserr("socket");

    server_address.sin_family = AF_INET; // IPv4
    server_address.sin_addr.s_addr = htonl(
            INADDR_ANY); // listening on all interfaces
    server_address.sin_port = htons(PORT_NUM); 

    // bind the socket to a concrete address
    if (bind(sock, (struct sockaddr *) &server_address,
             sizeof(server_address)) < 0)
        syserr("bind");

    // switch to listening (passive open)
    if (listen(sock, QUEUE_LENGTH) < 0)
        syserr("listen");

    printf("accepting client connections on port %hu\n",
           ntohs(server_address.sin_port));


    for (;;) {
        client_address_len = sizeof(client_address);
        msg_sock = accept(sock, (struct sockaddr *) &client_address,
                          &client_address_len);
        if (msg_sock < 0)
            syserr("accept");



        printf("ending connection\n");
        if (close(msg_sock) < 0) {
            printf("ErrorClosingSocket\n");
            break;
        }
        continue;
    }
    return 0;
}

Client code:

int sendSomething(void *to_send, int socket, uint32_t length) {

    if (send(socket, to_send, length, MSG_NOSIGNAL) !=
        length) {
        if (errno == EPIPE)  // Sending on closed connection
            return 0;
        return -1;
    }

    return 1;
}

int main(int argc, char *argv[]) {
    int sock;
    struct addrinfo addr_hints;
    struct addrinfo *addr_result;

    int err;

    if (argc != 3)
        fatal("Usage: %s host port\n", argv[0]);

    // 'converting' host/port in string to struct addrinfo
    memset(&addr_hints, 0, sizeof(struct addrinfo));
    addr_hints.ai_family = AF_INET; // IPv4
    addr_hints.ai_socktype = SOCK_STREAM;
    addr_hints.ai_protocol = IPPROTO_TCP;
    // argv[1] is localhost and argv[2] is 10002 
    err = getaddrinfo(argv[1], argv[2], &addr_hints, &addr_result);
    if (err == EAI_SYSTEM) // system error
        syserr("getaddrinfo: %s", gai_strerror(err));
    else if (err != 0) // other error (host not found, etc.)
        fatal("getaddrinfo: %s", gai_strerror(err));


    // initialize socket according to getaddrinfo results
    sock = socket(addr_result->ai_family, addr_result->ai_socktype,
                  addr_result->ai_protocol);
    if (sock < 0)
        syserr("socket");

    // connect socket to the server
    if (connect(sock, addr_result->ai_addr, addr_result->ai_addrlen) < 0)
        syserr("connect");

    freeaddrinfo(addr_result);


    int result;
    scanf("%d", &result);
    uint16_t test;

    test = htons(1);
    result = sendSomething(&test, sock, sizeof(test));
    printf("result:%d\n", result);

    if (close(sock) < 0) {
        printf("ErrorClosingSocket\n");
    }

    return 0;
}

Note: Fatal and Syserr are just for reporting errors


Solution

  • That's the way TCP works. When the server closes the socket, then a FIN is sent to the client. This only signals, that the server will not send any more data. It does not necessarily mean, that it does not want to receive more data.

    Thus, the client can call send() on the socket without the OS reporting an error. If the server indeed closed the whole socket, then it will send a TCP reset packet as a response to incoming data indicating that condition. Now, future operations on the socket (write/close) will indicate an error.

    It is indeed possible for the server (or any peer) to only shutdown the connection half-way (the reading or the writing side) with the syscall shutdown(). If the server shuts down the connection for writing, the same thing happens on the network as if the server closed the whole connection with close(). It is the duty of a higher level protocol to determine, when a connection should be closed for each side.

    If you want to make sure, that all data that you sent was indeed acknowledged by the peer, you can use the SO_LINGER socket option. But a more common way is, to make this sure as a part of the communication protocol, i.e. one part requests to shutdown the connection on a higher level (for example, the smtp QUIT command) and the peer reacts on it by closing the tcp connection.