Reply to comment

[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.

Reply

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions. (Case-insensitive)