Search code examples
clinuxloopsfedora-25

When abruptly exiting a C program mid-loop, why do additional loop iterations occur?


Consider the basic client and server programs below (just bare bones / to illustrate my question). The client initiates a connection with the server, prompts the user to enter a message, which is then sent to the server and printed to screen.

If I abruptly quit the client program in the middle of the loop (e.g. by closing the terminal window), sometimes the client will continue to iterate through the loop for a period of time (i.e. the last message sent to the server / currently residing in the write buffer at the time the client is closed, is repeatedly sent to the server, typically until the loop is exhausted). Other times however, the read() call on the server correctly returns 0, and the connection is closed without issue (the behavior seems to be fairly random).

I don't quite understand what's going on here. First off, why do additional loop iterations occur after the program closes? Is there just a lag time between when the terminal window is closed, and when the actual process itself ends? Even if additional loop iterations do occur, shouldn't the call to fgets() block until a message is entered by the user?

I'm using Fedora 25 Workstation with XFCE desktop.

I tried searching for info on this, but didn't have much luck (I'm not sure how to search for this in a succinct way). Any help is much appreciated.

Thanks

CLIENT:

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>

int main(void) {
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(3000);
    inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
    connect(sockfd, (struct sockaddr *)&server, sizeof(server));

    int i;
    for (i = 0; i < 20; i++) {
        char buf[512];
        printf("Send a message: ");
        fgets(buf, 512, stdin);
        write(sockfd, buf, sizeof(buf));
    }

    close(sockfd);
}

SERVER:

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>

int main(void) {
    int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(3000);
    inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
    bind(listenfd, (struct sockaddr *)&server, sizeof(server));

    listen(listenfd, 10);
    printf("Listening...\n");

    struct sockaddr_in client;
    socklen_t client_size = sizeof(client);
    int clientfd = accept(listenfd, (struct sockaddr *)&client, &client_size);

    for (;;) {
        char buf[512];
        int i = read(clientfd, buf, sizeof(buf));

        if (i == 0) {
            close(clientfd);
            printf("Connection Closed\n");
            break;
        } else {
            printf("%s", buf);
        }
    }

    close(listenfd);
}

Solution

  • When your terminal (and thus the remote/master side of the pty device connected to your process's stdin/out/err) is closed, fgets will see end-of-file status on stdin, and will return immediately with either an incomplete line (not ending in \n) or no input at all (null return value); in practice it's going to be the latter. If you checked the result, you would see this and be able to act accordingly.