简体   繁体   中英

How to handle TCP client disconnect in C

I am trying to write a basic TCP server that streams serial data to a client. The server would connect to a serial device, read data from said device, and then transmit it as a byte stream to the client. Writing the TCP server is no problem. The issue is that the server will crash when a client disconnects. In other languages, like Python, I can simply wrap the write() statement in a try-catch block. The program will try to write to the socket, but if the client has disconnected then an exception will be thrown. In another project, this code snippet worked for me:

try:
  client_socket.send(bytes(buf, encoding='utf8'))
except Exception as e:
  logger.info("Client disconnected: %s", client_id)

I can handle client disconnects in my C code, but only by first reading from the socket and checking if the read is equal to 0. If it is, then my client has disconnected and I can carry on as usual. The problem with this solution is that my client has to ping back to the server after every write, which is less than ideal.

Does anyone know how to gracefully handle TCP client disconnects in C? My example code is shown below. Thank you!

// Define a TCP socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

// Allow for the backlog of 100 connections to the socket  
int backlog = 100;  

// Supply a port to bind the TCP server to
short port = 9527;

// Set up server attributes
struct sockaddr_in servaddr;  
servaddr.sin_family = AF_INET;  
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
servaddr.sin_port = htons(port);  
    
// Set the socket so that we can bind to the same port when we exit the program
int flag = 1;  
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == -1) {  
    perror("setsockopt fail");  
}  
    
// Bind the socket to the specified port
int res = bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));  
if (res < 0) {
    perror("bind fail");  
    exit(1); 
}  
    
// Listen for incoming connections
if (listen(sockfd, backlog) == -1) {  
    perror("listen fail");  
    exit(1); 
} else {
    printf("Server listening on port\n", port); 
}

for(;;) {
    // Wait for incoming connection  
    struct sockaddr_in cliaddr;  
    socklen_t len = sizeof(cliaddr);  
    int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);  
    if (-1 == connfd) {  
        perror("Could not accept incoming client");
        continue;   
    }  
        
    //Resolving Client Address  
    char buff[INET_ADDRSTRLEN + 1] = {0};  
    inet_ntop(AF_INET, &cliaddr.sin_addr, buff, INET_ADDRSTRLEN);  
    uint16_t cli_port = ntohs(cliaddr.sin_port);  
    printf("connection from %s, port %d\n", buff, cli_port);

    for(;;) {
        // Read from serial device into variable here, then send
        if(send(connfd, "Data...Data...Data\n", 19, 0) < 0) {
            printf("Client disconnected...\n"); 
            break; 
        }
    }
}

Looks like a duplicate of this , this and this .

Long story short you can't detect the disconnection until you perform some write (or read) on that connection. More exactly, even if it seems there is no error returned by send, this is not a guarantee that this operation was really sent and received by the client. The reason is that the socket operations are buffered and the payload of send is just queued so that the kernel will dispatch it later on.

Depending on the context, the requirements and the assumptions you can do something more. For example, if you are under the hypothesys that you will send periodic message at constant frequency, you can use select and a timeout approach to detect an anomaly. In other words if you have not received anything in the last 3 minutes you assume that there is an issue.

As you can easily found, this and this are a good read on the topic. Look at that for a far more detailed explanation and other ideas.

What you call the ping (intended as a message that is sent for every received packet) is more similar to what is usually known as an ACK.

You only need something like that (ACK/NACK) if you also want to be sure that the client received and processed that message.

Thanks to @emmanuaf, this is the solution that fits my project criteria. The thing that I was missing was the MSG_NOSIGNAL flag, referenced here .

I use Mashpoe's C Vector Library to create a new vector, which will hold all of my incoming client connections.

int* client_array = vector_create(); 

I then spawn a pthread that continually reads from a serial device, stores that data in a variable, and then sends it to each client in the client list

void* serve_clients(int *vargp) {

    for(;;) {

        // Perform a microsleep
        sleep(0.1);

        // Read from the Serial device

        // Get the size of the client array vector
        int client_vector_size = vector_size(vargp); 

        for(int i = 0 ; i < client_vector_size ; i++) {
            
            // Make a reference to the socket
            int* conn_fd = &vargp[i]; 

            /*
                In order to properly handle client disconnects, we supply a MSG_NOSIGNAL 
                flag to the send() call. That way, if the client disconnects, we will 
                be able to detect this, and properly remove them from the client list. 

                Referenced from: https://beej.us/guide/bgnet/html//index.html#sendman
            */
            if (send(*conn_fd, "Reply from server\n", 18, MSG_NOSIGNAL) < 0) {
                printf("Client disconnected...\n"); 

                // Close the client connection
                close(*conn_fd); 

                // Remove client socket from the vector
                vector_remove(vargp, i); 

                // Decrement index and client_server_size by 1
                i--; 
                client_vector_size--; 

            }             
        }
    }
}

To spawn the pthread:

// Spawn the thread that serves clients
pthread_t serving_thread; 
pthread_create(&serving_thread, NULL, serve_clients, client_array);

When a new connection comes in, I simply add the new connection to the client vector

while(1) {
    // Wait for incoming connection  
    struct sockaddr_in cliaddr;  
    socklen_t len = sizeof(cliaddr);  
    int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);  
    if (-1 == connfd) {  
        perror("Could not accept incoming client");
        continue;   
    }  
        
    //Resolving Client Address  
    char buff[INET_ADDRSTRLEN + 1] = {0};  
    inet_ntop(AF_INET, &cliaddr.sin_addr, buff, INET_ADDRSTRLEN);  
    uint16_t cli_port = ntohs(cliaddr.sin_port);  
    printf("connection from %s:%d -- Connfd: %d\n", buff, cli_port, connfd); 

    // Add client to vector list
    vector_add(&client_array, connfd);
}

In the end, we have a TCP server that can multiplex data to many clients, and handle when those clients disconnect. 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM