TCP sockets, server fails to respond to client, accept: Interrupted system call

17,092

The accept() call "failing" with EINTR is normal. Your child process ended, and the current system call is interrupted. This happens for a few system calls even when you set SA_RESTART in sigaction flags. You just continue the loop, trying to accept a new client, so all is ok.

Your child process ends because it crashes.

char *word1, *word2;
...

sscanf(tcp_recv_buf, "%s %s", word1, word2);

Here you're trying to write to word1 and word2 , which are uninitialized pointers.

Share:
17,092
tony_tiger
Author by

tony_tiger

Updated on June 04, 2022

Comments

  • tony_tiger
    tony_tiger about 2 years

    I am trying to implement a TCP server and client in C, running on Solaris. I am new to sockets and am using Beej's Guide as an example.

    For starters, what I would like is for the client to send a message to the server in the form of word1 word2. Upon receipt, I want the server to extract word2 from the message and send that back to the client.

    The initial client --> server message sending works fine. But the server --> client response is not working. There are several failure symptoms:

    1. The server does not appear to even try to send() anything to the client.
    2. After receiving the client's message, the server prints: accept: Interrupted system call, then returns to the top of the while(1) loop and remains there until I Ctrl-C out of it.
    3. The client's call to recv() returns 0 bytes.

    I found an old thread here, where the last post says this:

    accept is being interrupted by the child process sending a signal back to the parent when it terminates (SIGCHLD, if I remember write). You can either ignore SIGCHLD, or you can code accept() to handle the interrupt better (errno is set to EINTR)

    However, I'm not understanding this. Why is the child process terminating before even attempting the send() portion? What does "handle the interrupt better" mean?

    After searching some more, I found this question on Stack Overflow: How to handle EINTR (interrupted System Call).

    I tried adding the code in the accepted answer, replacing write() with send(), but I still see the same behavior.

    Server code:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <sys/wait.h>
    #include <signal.h>
    
    const char* nodename = "localhost";
    const char* FILESERV1_LISTEN_PORT = "41063";
    const unsigned int MAXDATASIZE = 100;
    
    void sigchld_handler(int s)
    {
    // from http://beej.us/guide/bgnet/examples/server.c
        while(waitpid(-1, NULL, WNOHANG) > 0);
    }
    
    // get sockaddr, IPv4 or IPv6:
    void *get_in_addr(struct sockaddr *sa)
    {
    // from http://beej.us/guide/bgnet/examples/server.c
        if (sa->sa_family == AF_INET) {
            return &(((struct sockaddr_in*)sa)->sin_addr);
        }
    
        return &(((struct sockaddr_in6*)sa)->sin6_addr);
    }
    
    int main(void)
    {
        // from http://beej.us/guide/bgnet/examples/server.c
        int rv;
        const unsigned int BACKLOG = 3; // how many pending connections queue will hold
        int tcp_sockfd, new_tcp_sockfd;  // listen on tcp_sockfd, new connection on new_tcp_sockfd
        struct addrinfo fs_hints, *fileservinfo, *p_fsinfo;
        struct sockaddr_storage client_addr; // connector's address information
        socklen_t sin_size;
        struct sigaction sa;
        char yes='1'; // char for Solaris
        char s[INET6_ADDRSTRLEN];
        int tcp_numbytes, tcp_numbytes_written, size;
        char tcp_recv_buf[MAXDATASIZE];
        char *word1, *word2;
    
        memset(&fs_hints, 0, sizeof fs_hints);
        fs_hints.ai_family = AF_INET; // force IPv4
        fs_hints.ai_socktype = SOCK_STREAM;
        //fs_hints.ai_flags = AI_PASSIVE; // use my IP
    
        if ((rv = getaddrinfo(nodename, FILESERV1_LISTEN_PORT, &fs_hints, &fileservinfo)) != 0) {
            fprintf(stderr, "file_server1: getaddrinfo: %s\n", gai_strerror(rv));
            return 1;
        }
    
        // loop through all the results and bind to the first we can
        for(p_fsinfo = fileservinfo; p_fsinfo != NULL; p_fsinfo = p_fsinfo->ai_next) {
            if ((tcp_sockfd = socket(p_fsinfo->ai_family, p_fsinfo->ai_socktype,
                    p_fsinfo->ai_protocol)) == -1) {
                perror("file_server1: socket");
                continue;
            }
    
            if (setsockopt(tcp_sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                    sizeof(int)) == -1) {
                perror("file_server1: setsockopt");
                exit(1);
            }
    
            if (bind(tcp_sockfd, p_fsinfo->ai_addr, p_fsinfo->ai_addrlen) == -1) {
                close(tcp_sockfd);
                perror("file_server1: bind");
                continue;
            }
    
            break;
        }
    
        if (p_fsinfo == NULL)  {
            fprintf(stderr, "file_server1: failed to bind\n");
            return 2;
        }
    
        freeaddrinfo(fileservinfo); // all done with this structure
    
        if (listen(tcp_sockfd, BACKLOG) == -1) {
            perror("file_server1: listen");
            exit(1);
        }
    
        sa.sa_handler = sigchld_handler; // reap all dead processes
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESTART;
        if (sigaction(SIGCHLD, &sa, NULL) == -1) {
            perror("file_server1: sigaction");
            exit(1);
        }
    
        printf("DEBUG: file_server1: waiting for connections...\n");
    
        //signal(SIGCHLD, SIG_IGN);  /* now I don't have to wait()! */
    
        while(1) {  // main accept() loop
            printf("DEBUG: Top of while loop\n");
            sin_size = sizeof client_addr;
            new_tcp_sockfd = accept(tcp_sockfd, (struct sockaddr *)&client_addr, &sin_size);
            if (new_tcp_sockfd == -1) {
                perror("file_server1: accept");
                continue;
            }
    
            inet_ntop(client_addr.ss_family, get_in_addr((struct sockaddr *)&client_addr), s, sizeof s);
            printf("DEBUG: file_server1: got connection from %s\n", s);
    
            if (!fork()) { // this is the child process
                printf("DEBUG: inside if\n");
                close(tcp_sockfd); // child doesn't need the listener
                if ((tcp_numbytes = recv(new_tcp_sockfd, tcp_recv_buf, MAXDATASIZE-1, 0)) == -1) {
                    perror("file_server1: recv");
                    exit(1);
                }
                tcp_recv_buf[tcp_numbytes] = '\0';
                printf("DEBUG: file_server1: received: %s\n", tcp_recv_buf);
    
                sscanf(tcp_recv_buf, "%s %s", word1, word2);
                printf("DEBUG: file_server received word1: %s and word2: %s.\n", word1, word2);
    
                size = strlen(word2);
                while (size > 0) {
                    printf("DEBUG: top of inner while size\n");
                    tcp_numbytes_written = send(new_tcp_sockfd, word2, strlen(word2), 0);
                    if (tcp_numbytes_written == -1) {
                        if (errno == EINTR) {
                            printf("DEBUG: continuing after EINTR\n");
                            continue;
                        }
                        else {
                            printf("DEBUG: -1 on send(), not EINTR\n");
                            perror("DEBUG: file_server1: send");
                            return -1;
                        }
                    }
                    word2 += tcp_numbytes_written;
                    size -= tcp_numbytes_written;
                    printf("DEBUG: bottom of inner while size\n");
                }
                printf("DEBUG: file_server has sent %s to client.\n", word2);
                close(new_tcp_sockfd);
                //exit(0);
            }
            printf("DEBUG: outside of if\n");
            close(new_tcp_sockfd);  // parent doesn't need this
            printf("DEBUG: Bottom of while loop\n");
        }
    
        return 0;
    }
    

    Client code:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    
    const char* nodename = "localhost";
    const char* FILESERV1_LISTEN_PORT = "41063";
    const unsigned int MAXDATASIZE = 100; // max number of bytes we can get at once
    
    // get sockaddr, IPv4 or IPv6:
    void *get_in_addr(struct sockaddr *sa)
    {
        // http://beej.us/guide/bgnet/examples/client.c
        if (sa->sa_family == AF_INET) {
            return &(((struct sockaddr_in*)sa)->sin_addr);
        }
    
        return &(((struct sockaddr_in6*)sa)->sin6_addr);
    }
    
    int main(void)
    {
        // from http://beej.us/guide/bgnet/examples/client.c
        int tcp_sockfd, tcp_numbytes;
        char buf[MAXDATASIZE];
        struct addrinfo fs_hints, *fileservinfo, *p_fsinfo;
        struct sockaddr_storage my_addr; // for storing local dynamic port number
        unsigned short int client_tcp_port;
        char client_ipaddr_str[INET_ADDRSTRLEN];
        socklen_t addrlen;
        int rv;
        int getsock_check;
        char fs_ipaddr_str[INET6_ADDRSTRLEN];
    
        char *msg_to_send = "abcdef hijklm"; // word1 and word2
    
        memset(&fs_hints, 0, sizeof fs_hints);
        fs_hints.ai_family = AF_INET; // IPv4
        fs_hints.ai_socktype = SOCK_STREAM;
    
        if ((rv = getaddrinfo(nodename, FILESERV1_LISTEN_PORT, &fs_hints, &fileservinfo)) != 0) {
            fprintf(stderr, "client1: getaddrinfo: %s\n", gai_strerror(rv));
            return 1;
        }
    
        // loop through all the results and connect to the first we can
        for(p_fsinfo = fileservinfo; p_fsinfo != NULL; p_fsinfo = p_fsinfo->ai_next) {
            if ((tcp_sockfd = socket(p_fsinfo->ai_family, p_fsinfo->ai_socktype, p_fsinfo->ai_protocol)) == -1) {
                perror("client1: socket");
                continue;
            }
    
            if (connect(tcp_sockfd, p_fsinfo->ai_addr, p_fsinfo->ai_addrlen) == -1) {
                close(tcp_sockfd);
                perror("client1: connect");
                continue;
            }
    
            break;
        }
        printf("DEBUG: client1: socket and connect successful\n");
        if (p_fsinfo == NULL) {
            fprintf(stderr, "client1: failed to connect\n");
            return 2;
        }
    
        addrlen = sizeof my_addr;
        if ((getsock_check=getsockname(tcp_sockfd, (struct sockaddr *)&my_addr, (socklen_t *)&addrlen)) == -1) { 
            perror("client1: getsockname");
            exit(1);
        }
        printf("DEBUG: client1: getsockname successful\n");
        client_tcp_port = ntohs(((struct sockaddr_in *)&my_addr)->sin_port);
        inet_ntop(AF_INET, &(((struct sockaddr_in *)&my_addr)->sin_addr), client_ipaddr_str, INET_ADDRSTRLEN);
        printf("DEBUG: client1 has dynamic TCP port number %hu and IP address %s.\n", client_tcp_port, client_ipaddr_str);
    
        inet_ntop(p_fsinfo->ai_family, get_in_addr((struct sockaddr *)p_fsinfo->ai_addr), fs_ipaddr_str, sizeof fs_ipaddr_str);
        printf("DEBUG: client1: connecting to %s\n", fs_ipaddr_str);
        freeaddrinfo(fileservinfo); // all done with this structure
    
        if (send(tcp_sockfd, msg_to_send, strlen(msg_to_send), 0) == -1) {
            perror("client1: send");
            exit(1);
        }
    
        printf("DEBUG: The request from client1 has been sent to the file_server1\n");
    
        if ((tcp_numbytes = recv(tcp_sockfd, buf, MAXDATASIZE-1, 0)) == -1) {
            perror("client1: recv");
            exit(1);
        }
    
        buf[tcp_numbytes] = '\0';
    
        printf("DEBUG: client1: received %d bytes, content '%s'\n", tcp_numbytes, buf);
        close(tcp_sockfd);
    
    
        return 0;
    }
    

    Output from server:

    DEBUG: file_server1: waiting for connections...
    DEBUG: Top of while loop
    DEBUG: file_server1: got connection from 127.0.0.1
    DEBUG: outside of if
    DEBUG: Bottom of while loop
    DEBUG: Top of while loop
    DEBUG: inside if
    DEBUG: file_server1: received: abcdef hijklm
    file_server1: accept: Interrupted system call
    DEBUG: Top of while loop
    

    Output from client:

    DEBUG: client1: socket and connect successful
    DEBUG: client1: getsockname successful
    DEBUG: client1 has dynamic TCP port number 51196 and IP address 127.0.0.1.
    DEBUG: client1: connecting to 127.0.0.1
    DEBUG: The request from client1 has been sent to the file_server1
    DEBUG: client1: received 0 bytes, content ''
    

    Lastly, I eventually want this server to handle the same 2-way transaction with a possible second or third client (for now, not conerned with concurrently serving clients). Do I want to keep the sockfd returned by listen() open in that situation?

  • tony_tiger
    tony_tiger over 10 years
    Thank you. I wonder why the child process on the server did not report a seg fault or bus error.