Skip navigation.
Home
Write anything I want to write...

[Refresher] TCP/IP with Multiple Clients and Concurrent Server

The sample codes here demonstrate the following techniques:

  • Concurrent FTP Server
  • The use of standard I/O with POSIX sockets
  • The handling of Unix signal SIGCHLD to prevent terminated children from becoming zombies
  • The handling of system calls interrupted by caught signals

Codes

Code for the Server:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>


/*----------------------------------------------------------------*/
/* Prevent Terminated Children from Becoming Zombies              */
/*----------------------------------------------------------------*/

/* The method here is rather portable for systems that conform to POSIX.1-1990.
 * In POSIX.1-2001, if
 * - the disposition of SIGCHLD is set to SIG_IGN; or
 * - the SA_NOCLDWAIT flag is set for SIGCHLD,
 * then children that terminate do not become zombies.  A call to wait()
 * or waitpid() will block until all children have terminated, and then
 * fail with errno set to ECHILD.  Linux 2.6 conforms to this behavior, but
 * Linux 2.4 does not.
 */

void handler_sigchld(int signum)
{
  int status;
  // Wait for all terminated children
  // Loop is required as Unix signals are not queued by default.
  while (waitpid(-1, &status, WNOHANG) > 0) {
  }
}


int handle_terminated_children(void)
{
  struct sigaction sigact;
  sigact.sa_handler = handler_sigchld;
  sigemptyset(&sigact.sa_mask);
  sigact.sa_flags = SA_RESTART;  // Make certain system calls restartable across signals
  if (sigaction(SIGCHLD, &sigact, NULL)) {
    return -1;
  }
  return 0;
}


/*----------------------------------------------------------------*/
/* Server                                                         */
/*----------------------------------------------------------------*/

/* Port number of the server. Generally, avoid:
 * - IANA well-known ports         :      0 -  1023
 * - BSD ephemeral ports           :   1024 -  5000
 * - IANA dynamic or private ports :  49152 - 65535
 *
 * To check the empherical port range of Linux, do
 *   \$ cat /proc/sys/net/ipv4/ip_local_port_range 
 *   32768   61000
 * (The default range since Linux 2.4)
 *
 * Refer to
 *   http://www.iana.org/assignments/port-numbers
 * for port number assignments from the IANA.
 */
#define SERVER_PORT     10060
/* The maximum length the queue of pending connections may grow to */
#define LISTEN_BACKLOG  16


int server(void)
{
  /* Obtain a socket descriptor */
  int listen_fd;
  listen_fd = socket(AF_INET, SOCK_STREAM, 0);  // TCP
  if (listen_fd < 0) {
    // errno is set appropriately
    return -1;
  }

  /* Assign a local address to the socket */
  struct sockaddr_in server_addr;  // IPv4 socket address structure
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // Wildcard address; Network byte order
  server_addr.sin_port = htons(SERVER_PORT);
  if (bind(listen_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
    // errno is set appropriately
    return -2;
  }

  /* Convert the socket into a passive listening socket */
  if (listen(listen_fd, LISTEN_BACKLOG) < 0) {  
    // errno is set appropriately
    return -3;
  }

  /* Arrange to handle terminated children */
  if (handle_terminated_children() < 0) {
    return -4;
  }

  for (;;) {

    /* Get the descriptor of the next completed connection for the accepted socket */
    int connection_fd;
    struct sockaddr_in client_addr;
    socklen_t client_addr_len;
    client_addr_len = sizeof(client_addr);
    connection_fd = accept(listen_fd, (struct sockaddr *) &client_addr, &client_addr_len);
    if (connection_fd < 0) {
      // errno is set appropriately
      switch (errno) {
      case EINTR:
        // Interrupted by a signal. Restart the connection.
        continue;

      case EAGAIN:
      case ENETDOWN:
      case EPROTO:
      case ENOPROTOOPT:
      case EHOSTDOWN:
      case ENONET:
      case EHOSTUNREACH:
      case EOPNOTSUPP:
      case ENETUNREACH:
        // Linux accept() passes already-pending network errors on the new
        // socket as an error code from accept().  The application should
        // detect these errors in the case of TCP/IP and retry again for
        // reliable operation.
        continue;

      default:
        return -5;
      }
    }

    pid_t child_pid;
    child_pid = fork();
    if (child_pid == 0) {
      // Child process
      close(listen_fd);  // Close the listening socket

      /* Interact with the client */
#define STR_MAX 100
      FILE *fp;
      char str[STR_MAX];
      fp = fdopen(connection_fd, "r+");
      if (!fp) {
        // errno is set appropriately
        return -6;
      }
      // Echo the client once
      if (fgets(str, STR_MAX, fp)) {
        fseek(fp, 0L, SEEK_CUR);  // Synchronization; See man fdopen(3)
        if (fputs(str, fp) == EOF) {
          return -7;
        }
      }
      if (fclose(fp) == EOF) {
        // errno is set appropriately
        return -8;
      }

      exit(0);
    }
    else if (child_pid > 0) {
      // Parent process
      close(connection_fd);  // Close the connection socket
    }
    else {  // child_pid < 0
      // errno is set appropriately
      return -9;
    }
  }

  return 0;
}


int main(int argc, char *argv[])
{
  int retval;
  retval = server();

  printf("retval => %d (%s)\n", retval, strerror(errno));

  return 0;
}

Code for the Client:

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


/*----------------------------------------------------------------*/
/* Useful Wrappers                                                */
/*----------------------------------------------------------------*/

/* A wrapper for write() that
   - tries again for if write() was interrupted by a caught signal
   - invokes write() again for incomplete write() due to buffer limit
*/
ssize_t write_safe(int fd, const void *p, size_t n)
{
  ssize_t num_written;
  while (n > 0) {
    num_written = write(fd, p, n);
    if (num_written <= 0) {
      if (errno == EINTR)
        num_written = 0;  // Try again
      else
        return -1;
    }
    n -= num_written;
    p += num_written;
  }
  return num_written;
}


/* A wrapper for read() that
   - tries again for if read() was interrupted by a caught singal
   - invokes read() again for incomplete read() due to buffer limit
     (NOTE: Sometimes you may not want this behavior as you will be
            blocked here until the buffer is full or EOF is reached.)
*/
ssize_t read_safe(int fd, void *p, size_t n)
{
  size_t num_left;
  ssize_t num_read;
  num_left = n;
  while (num_left > 0) {
    num_read = read(fd, p, num_left);
    if (num_read < 0) {
      if (errno == EINTR)
        num_read = 0;  // Try again
      else
        return -1;
    }
    else if (num_read == 0) {  // EOF
      break;
    }
    num_left -= num_read;
    p += num_read;
  }
  return n - num_left;
}


/*----------------------------------------------------------------*/
/* Client                                                         */
/*----------------------------------------------------------------*/

int client(const char *server_ip, int server_port)
{
  /* Obtain a socket descriptor */
  int socket_fd;
  socket_fd = socket(AF_INET, SOCK_STREAM, 0);  // TCP
  if (socket_fd < 0) {
    // errno is set appropriately
    return -1;
  }

  /* Specify the address of the server */
  struct sockaddr_in server_addr;  // IPv4 socket address structure
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  int status;
  if ((status = inet_pton(AF_INET, server_ip, &server_addr.sin_addr)) < 0) {
    // status  < 0:  Does not contain a valid address family
    // status == 0:  Source address is invalid in the specified address family
    return -2;
  }
  server_addr.sin_port = htons(server_port);

  /* Initiate a connection on the socket */
  if (connect(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
    // errno is set appropriately
    return -3;
  }

  /* Interact with the server */
#define STR_MAX 100
  // Get a string
  char str[STR_MAX];
  printf("Enter a few words: ");
  if (fgets(str, STR_MAX, stdin)) {
    // Send it to the server
    if (write_safe(socket_fd, str, strlen(str)) < 0) {
      // errno is set appropriately
      return -4;
    }
  }
  // Read from the server
  ssize_t num_read;
  if ((num_read = read_safe(socket_fd, str, STR_MAX)) < 0) {
    // errno is set appropriately
    return -5;
  }
  // Display the result returned
  printf("Echo: [%*s]\n", num_read, str);

  return 0;
}


#define SERVER_PORT     10060

int main(int argc, char *argv[])
{
  int retval;
  retval = client("127.0.0.1", SERVER_PORT);

  printf("retval => %d (%s)\n", retval, strerror(errno));

  return 0;
}

Miscellaneous

  • kill()
    • Send signal to a process
    • Perform error checking (EPERM: No permission to send; ESRCH: The process or process group does not exist.) without sending any signal.

hi , we have a problem how

hi , we have a problem how does it work ?

The codes here only serve to

The codes here only serve to demonstrate the essential part of using the Unix system calls and networking APIs to implement such clients or servers from scratch. You should be able to compile them using the GCC compiler. Note that the codes are NOT for production use!!