Simple TCP Server and Client - Improved

Introduction

This is an improved version of Simple TCP Server and Client. The server and client programs in this article have been improved in the following aspects:

  1. Use inet_ntop() function to handle IPv4 and IPv6 addresses correctly.

  2. Use getaddrinfo() function to handle IPv4 and IPv6 addresses correctly.

  3. Use fork() function to handle multiple client connections at the same time.

  4. Use waitpid() function to handle the exit of child processes correctly.

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 <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

#define PORT "43866"    /* port we're listening on */
#define BACKLOG 10      /* maximum number of pending connections */
#define MAXDATASIZE 100 /* maximum number of bytes we can get at once */

/* singal handler, handle the exit of child processes */
void sigchld_handler(int s)
{
    /* waitpid() might overwrite errno, so we save and restore it: */
    int saved_errno = errno;

    /* waitpid() with WNOHANG to handle the exit of child processes */
    while (waitpid(-1, NULL, WNOHANG) > 0)
        ;

    /* restore errno */
    errno = saved_errno;
}
TCP
    /* check address family */
    if (sa->sa_family == AF_INET)
    {
        /* return IPv4 address */
        return &(((struct sockaddr_in *)sa)->sin_addr);
    }
    /* return IPv6 address */
    return &(((struct sockaddr_in6 *)sa)->sin6_addr);
}

/* get port, IPv4 or IPv6: */
int get_in_port(struct sockaddr *sa)
{
    /* check address family */
    if (sa->sa_family == AF_INET)
    {
        /* return IPv4 port number */
        return (((struct sockaddr_in *)sa)->sin_port);
    }
    /* return IPv6 port number */
    return (((struct sockaddr_in6 *)sa)->sin6_port);
}

int main(int argc, char *argv[])
{
    int sockfd, new_fd;                   /* sockfd: listen on sock_fd, new_fd: new connection on new_fd */
    struct addrinfo hints; *p;            /* hints: to set getaddrinfo() */
    struct addrinfo *servinfo;            /* servinfo: to store the result of getaddrinfo() */
    struct addrinfo *p;                   /* p: to loop servinfo */
    
    struct sockaddr_storage their_addr;   /* connector's address information */
    socklen_t sin_size;                   /* to store the size of their_addr */
    struct sigaction sa;                  /* to set signal handler */
    int yes = 1;                          /* to set SO_REUSEADDR option */
    int rv;                               /* to store the return value of getaddrinfo() */
    char s[INET6_ADDRSTRLEN];             /* to store the client IP address */
    int numbytes_send;                    /* to store the return value of send() */
    int numbytes_recv;                    /* to store the return value of recv() */
    char buf[MAXDATASIZE];                /* to store the received data */

    /* initialize hints */
    memset(&hints, 0, sizeof hints);

    /* set hints */
    hints.ai_family = AF_UNSPEC;          /* use IPv4 or IPv6, whichever */
    hints.ai_socktype = SOCK_STREAM;      /* TCP stream sockets */
    hints.ai_flags = AI_PASSIVE;          /* fill in my IP for me */

    /* get address information */
    if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0)
    {
        /* print error message */
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    /* loop through all the results and bind to the first we can */
    for (p = servinfo; p != NULL; p = p->ai_next)
    {
        /* create socket */
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
        {
            /* print error message */
            perror("server: socket");
            continue;
        }

        /* set SO_REUSEADDR option */
        // SO_REUSEADDR: Allow local address reuse
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
        {
            /* print error message */
            perror("setsockopt");
            exit(1);
        }

        /* bind socket */
        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1)
        {
            /* bind failed, close socket and print error message */
            close(sockfd);
            perror("server: bind");
            continue;
        }

        break;
    }

    /* free the linked list */
    freeaddrinfo(servinfo);

    /* check if bind failed */
    if (p == NULL)
    {
        fprintf(stderr, "server: failed to bind\n");
        exit(1);
    }

    /* listen on socket */
    if (listen(sockfd, BACKLOG) == -1)
    {
        perror("listen");
        exit(1);
    }

    /* set signal handler */
    sa.sa_handler = sigchld_handler; /* reap all dead processes */
    sigemptyset(&sa.sa_mask);        /* block all signals */
    sa.sa_flags = SA_RESTART;        /* restart system calls if possible */
    if (sigaction(SIGCHLD, &sa, NULL) == -1)
    {
        /* print error message */
        perror("sigaction");
        exit(1);
    }

    printf("server: waiting for connections...\n");

    /* main accept() loop */
    while (1)
    {
        sin_size = sizeof their_addr;
        /* accept connection */
        new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
        if (new_fd == -1)
        { /* accept failed */
            perror("accept");
            continue;
        }

        /* convert IP address to string */
        inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof s);
        printf("server: got connection from %s, port %d, socketfd %d\n", s, ntohs(get_in_port((struct sockaddr *)&their_addr)), new_fd);

        /* fork a child process to handle the new connection */
        if (!fork())
        {
            /* child process doesn't need the listener */
            close(sockfd);
            /* child process */
            /* build message */
            char *msg = "Hello world from server!";
            int msg_len = strlen(msg);
            /* send message */
            if ((numbytes_send = send(new_fd, msg, msg_len, 0)) == -1)
                perror("send");
            /* print message */
            printf("server: sent message: %s (%d bytes) to %s, port %d, sockfd %d\n", msg, numbytes_send, s, ntohs(get_in_port((struct sockaddr *)&their_addr)), new_fd);
            /* receive message */
            if ((numbytes_recv = recv(new_fd, buf, MAXDATASIZE - 1, 0)) == -1)
            {
                perror("recv");
                exit(1);
            }
            buf[numbytes_recv] = '\0';
            printf("server: received message: %s (%d bytes) from %s, port %d, sockfd %d\n", buf, numbytes_recv, s, ntohs(get_in_port((struct sockaddr *)&their_addr)), new_fd);
            /* close socket */
            close(new_fd);
            printf("\n");
            /* exit child process */
            exit(0);
        }
        /* parent process doesn't need the new socket */
        close(new_fd);
    }

    return 0;
}

Client Code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define PORT "43866"    /* port number that client will be connecting to */
#define MAXDATASIZE 100 /* max number of bytes we can get at once */

/* get sockaddr, IPv4 or IPv6: */
void *get_in_addr(struct sockaddr *sa)
{
    /* check address family */
    if (sa->sa_family == AF_INET)
    {
        /* return IPv4 address */
        return &(((struct sockaddr_in *)sa)->sin_addr);
    }
    /* return IPv6 address */
    return &(((struct sockaddr_in6 *)sa)->sin6_addr);
}

int main(int argc, char *argv[])
{
    int sockfd;                           /* socket file descriptor */
    char buf[MAXDATASIZE];                /* buffer for receiving data */
    struct addrinfo hints;                /* hints: to store parameters for getaddrinfo() function */
    struct addrinfo *servinfo;            /* servinfo: to store the results of getaddrinfo() function */
    struct addrinfo *p;                   /* p: to store the results of getaddrinfo() function */
    int rv;                               /* rv: to store the return value of getaddrinfo() function */
    char s[INET6_ADDRSTRLEN];             /* s: to store the IP address of server */
    int numbytes_recv;                    /* to store the return value of recv() function */
    int numbytes_send;                    /* to store the return value of send() function */

    /* check command line arguments */
    if (argc != 2)
    {
        fprintf(stderr, "usage: ./client hostname\n");
        exit(1);
    }TCP

    /* initialize hints structure */
    memset(&hints, 0, sizeof hints);

    /* set hints structure */
    hints.ai_family = AF_UNSPEC;     /* IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM; /* TCP stream sockets */

    /* get address information */
    if ((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0)
    {
        /* print error message */
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    /* loop through all the results and connect to the first we can */
    for (p = servinfo; p != NULL; p = p->ai_next)
    {
        /* create socket */
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
        {
            /* print error message */
            perror("client: socket");
            continue;
        }

        /* connect to server */
        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
        {
            /* print error message */
            close(sockfd);
            perror("client: connect");
            continue;
        }

        break;
    }

    /* loop through all the results and connect to the first we can */
    if (p == NULL)
    {
        fprintf(stderr, "client: failed to connect to server\n"); /* server is down */
        return 2;
    }

    /* convert IP address to string */
    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s);

    /* print IP address and port number */
    printf("client: connecting to %s port %s\n", s, PORT);

    /* free the linked list */
    freeaddrinfo(servinfo);

    /* receive data from server */
    if ((numbytes_recv = recv(sockfd, buf, MAXDATASIZE - 1, 0)) == -1)
    {
        /* receive data failed */
        perror("recv");
        exit(1);
    }

    /* add '\0' to the end of the string */
    buf[numbytes_recv] = '\0';

    /* print received data */
    printf("client: received '%s' from server.\n", buf);

    /* build message to send to server */
    char *msg = "Hello world from client!";
    int msg_len = strlen(msg);

    /* send data to server */
    if ((numbytes_send = send(sockfd, msg, msg_len, 0)) == -1)
    {
        /* send data failed */
        perror("send");
    }
    printf("client: sent '%s' (%d bytes) to server.\n", msg, numbytes_send);

    /* close socket */
    close(sockfd);

    printf("client: closed socketfd %d.\n", sockfd);
    return 0;
}

/* References: Page 31-33, Beej's Guide to Network Programming Using Internet Sockets */

Compile

gcc server.c -o server
gcc client.c -o client

Output

$ ./server
server: waiting for connections...
server: got connection from 127.0.0.1, port 56836, socketfd 4
server: sent message: Hello world from server! (24 bytes) to 127.0.0.1, port 56836, sockfd 4
server: received message: Hello world from client! (24 bytes) from 127.0.0.1, port 56836, sockfd 4
$ ./client localhost
client: connecting to
client: received 'Hello world from server!' from server.
client: sent 'Hello world from client!' (24 bytes) to server.
client: closed socketfd 3

If you run the client before the server, the following error message will be printed:

$ ./client localhost
client: connect: Connection refused
client: failed to connect to server

Reference

A Simple Stream Server - Beej’s Guide to Network Programming

A Simple Stream Client - Beej’s Guide to Network Programming


Last modified on 2023-01-06


Thank you for your support!