tony_tiger
tony_tiger

Reputation: 799

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

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?

Upvotes: 2

Views: 19063

Answers (1)

nos
nos

Reputation: 229234

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.

Upvotes: 6

Related Questions