Chapter 3: Networking

Most system programs will require some sort of networking support. In order to narrow down the discussion, and in order to structure the information, a few networking concepts are described below.

Open Systems describes the networking protocol using the seven layers of the Open Systems Interconnection basic reference model, or the OSI model. Each layer has its specific properties:

  1. The first level is the physical layer, such as a twisted pair cable, an RS232 port, or a 802.11g wireless receiver.

  2. The next level is the data link, such as Ethernet or PPP (Point to Point Protocol).

  3. The next layer is the network layer, e.g. ICMP (the 'ping' protocol) and IP (the Internet protocol).

  4. The layer above that is the transport layer, such as TCP (Transmission Control Protocol) or UDP (User Datagram Protocol). The difference between the two is that TCP:

    In the remainder of this document we will only focus on TCP. This transport protocol is most widely used.

    UDP is non-guaranteed, and might be delivered out of order (first sent packets may arrive last); but it has less overhead than TCP and is therefore faster. UDP has its uses, of which we name three:

    (If you want a look at a UDP client and server, you may want to look at the package logfwd on http://public.e-tunity.com.)

  5. The fifth layer is the session layer, which denotes an active network connection. In our discussion this is the layer where 'sockets' occur. A program opens a network socket and reads from it, or writes to it, like from a file. Throughout this time the socket is open and represents one network session.

  6. The layer above that is the presentation layer, where standards are defined to represent data. Examples are MIME (mail encapsulation), XDR (external data representation, which e.g. ensures that a double value is sent correctly, despite of Endianness), and SSL (which has phases like key negotiation and exchange, encrypted data transmission, end-of-session warnings).

  7. The final seventh layer is the application layer, where application-specific formats are defined. Examples are SMTP (mail transfer protocol) and HTTP (hypertext transfer protocol).

The advantage of separate OSI layers is that one can substitute alternative mechanisms for one or more layers, without affecting standards of other layers. E.g., UMTS (wireless phone transfer) replaces the first three layers. IP doesn't exist under UMTS. However, above that, all protocols still work, up until HTTP which brings your favorite site to your mobile phone.

Another example is SSL in layer 6. Whether enabled or not, the above application protocol remains the same. If you have a mail reader that doesn't know how to talk to secure POP3 mailboxes (spop3), then you can simply use a program like stunnel to set up an SSL link to your spop3 provider. Next you have your mail agent talk to the non-SSL part of the tunnel and you're up and running.

In the following sections we will talk about layer 5 (the session layer). Other layers will go largely unnoticed. Regarding sockets, we will handle server side and client side separately -- preparations and actions on these are quite different depending on your point of view.

In the sections on client-side sockets we'll discuss all that is necessary to behave as a client that connects to some server. In the sections on server-side sockets we'll discuss all that is necessary to behhave as a server that presents a networked service.

3.1: Client-side Socket Handling

Obtaining a working client socket which is connected with some server consists of a few actions.

3.1.1: How to handle connection timeouts

In order to neatly prevent endless connection waits, one should set up an acceptable timeout period that is an acceptable wait time. When exceeded, the program would stop.

Waiting for a timeout of the connect() call in most cases consists of the following steps:

  1. Before a potentially blocking action, we issue an alarm call in say 5 seconds.
  2. We define a signal handler for the SIGALRM event. This signal handler 'does nothing' except raise a flag, called e.g. timed_out.
  3. Next, we attempt the connect() system call.
  4. If connect() returns a non-zero value, then something's afoot. The call may have been interrupted due to a signal; which might actually be our alarm call. We check the flag variable timed_out to see whether a timeout condition has occurred.

3.1.2: Putting it all together

So, having sorted out the timeout problem, here's a demo client connector. You can run it as ./client smtp.gmail.com 25. It will connect to smtp.gmail.com, port 25, and show the first transmission (up to 255 characters) what the server is sending. In this case this would be the greeting line of a mail server. You can also try out ./client smtp.gmail.com 26 to see the timeout code in action. It should be pointed out that the timeout applies only to the connecting of a socket; not to the reading of data. If you direct the client at e.g. www.google.com port 80, then the client will sit there indefinitely, waiting for the server's message. A webserver however doesn't start the chat; it waits for the client to request something, which is not what this client does.

The relevant part of creating a client socket and connecting it, is coded in a function client_socket(). This function uses a hardcoded timeout value of 5 seconds, and exits the program upon failure. It may however be a starting point for your own code.

   1  /* Sample program: client.c */
   2  
   3  #include <errno.h>
   4  #include <netdb.h>
   5  #include <stdio.h>
   6  #include <stdlib.h>
   7  #include <string.h>
   8  #include <unistd.h>
   9  #include <sys/types.h>
  10  #include <sys/socket.h>
  11  
  12  static int client_socket (char const *serverint port);
  13  static void timeout_handler (int sig);
  14  
  15  static int timed_out = 0;
  16  
  17  int main (int argcchar const **argv) {
  18      int fd;
  19      char buf[256];
  20      ssize_t nread;
  21  
  22      if (argc != 3) {
  23          fprintf (stderr,
  24                   "Usage: client server port\n"
  25                   "Reads the first block of data that the server sends,\n"
  26                   "up to 255 bytes, and shows it.\n");
  27          exit (1);
  28      }
  29  
  30      fd = client_socket (argv[1], atoi(argv[2]));
  31      if ( (nread = read (fdbuf, 255)) < 0 )
  32          printf ("Read error\n");
  33      else if (!nread)
  34          printf ("No data\n");
  35      else {
  36          buf[nread] = 0;
  37          printf ("%s:%d says:\n%s\n"argv[1], atoi(argv[2]), buf);
  38      }
  39      close (fd);
  40      return (0);
  41  }
  42  
  43  static int client_socket (char const *serverint port) {
  44      int sock;
  45      struct hostent *hent;
  46      struct sockaddr_in sname;
  47  
  48      /* Create the socket. */
  49      if ( (sock = socket (PF_INETSOCK_STREAM, 0)) < 0 ) {
  50          fprintf (stderr"Failed to create socket: %s\n"strerror (errno));
  51          exit (1);
  52      }
  53  
  54      /* Look up the host. */
  55      if (! (hent = gethostbyname (server)) ) {
  56          fprintf (stderr"Failed to resolve host %s\n"server);
  57          exit (2);
  58      }
  59  
  60      /* Prepare the server name binding. */
  61      sname.sin_family = AF_INET;
  62      sname.sin_port = htons (port);
  63      sname.sin_addr = *(struct in_addr *)hent->h_addr;
  64  
  65      /* Set up the timeout mechanism. We allow 5 seconds for trying. */
  66      signal (SIGALRMtimeout_handler);
  67      alarm (5);
  68  
  69      /* Connect. */
  70      if (connect (sock, (struct sockaddr *) &snamesizeof(sname))) {
  71          close (sock);
  72          alarm (0);
  73          signal (SIGALRMSIG_DFL);
  74          if (timed_out)
  75              fprintf (stderr"Cannot connect to %s:%d: timed out\n",
  76                       serverport);
  77          else
  78              fprintf (stderr"Cannot connect to %s:%d: %s\n",
  79                       serverportstrerror(errno));
  80          exit (3);
  81      }
  82      /* Connect call succeeded */
  83      alarm (0);
  84      signal (SIGALRMSIG_DFL);
  85      return (sock);
  86  }
  87  
  88  void timeout_handler (int sig) {
  89      timed_out++;
  90  }
  91  

3.1.3: Handling connect() timeouts in multithreaded programs

The approach shown above in section 3.1.1 is applicable only to non-threaded programs; ie., in the shown sample client program, or in forked daemons. The approach is not thread-safe:

The approach for multi-threaded daemons is somewhat different. It will not be fully described here, but the outline is shown below. A full example can be found in section 1.

  1. Just as is the case with the 'standard' approach, a socket is created, the remote host is looked up, and a binding structure sockaddr_in is filled.

  2. Next, the socket is put in non-blocking mode. This can be done with fcntl().

  3. Now the connect() is attempted. Since the socket is non-blocking, the connect() won't block either, but will almost immediately return a non-zero value. The variable errno will be EINPROGRESS.

    As a global variable errno is used, this piece of code must be protected by mutex locks.

  4. Following the connect(), the socket can be placed back in blocking mode.

  5. After this, a select() call is executed, with the socket in the writable set. The required timeout is stated. The select() call will try to wait until a next write() to the socket is possible; which might time out.

  6. However, the select() call will apparently succeed in situations where a connection was refused. In such cases, the next read() or write() will yield errno ECONNREFUSED.

3.2: Socket IO and Timeouts

As shown in the previous text, a network socket is no more than a file descriptor: the most basic stream handle that the Unix kernel knows. IO (input/output) on sockets may not be quite what you're used to:

3.2.1: Looped socket IO

The following sample code shows how to write() to a socket. The sample program just uses file descriptor 0 (stdout), but the same methodology applies to network sockets. The trick is in function fd_write(): it loops until the total number of written bytes equals the total number of bytes we want to write. Inside the loop atomic write()'s send the information. The return value of each write() is checked to verify that at least some information could be sent.

   1  /* Sample program: socketwrite.c */
   2  
   3  #include <stdio.h>
   4  #include <stdlib.h>
   5  #include <string.h>
   6  #include <unistd.h>
   7  
   8  static int fd_write (int fdchar const *bufint len);
   9  
  10  int main () {
  11      char *msg = "Hello World!\n";
  12      int  len  = strlen (msg);
  13      int  res  = fd_write (0, msglen); 
  14  
  15      if (res != len) {
  16          fprintf (stderr"Only %d bytes written out of %d\n"reslen);
  17          exit (1);
  18      }
  19      
  20      return (0);
  21  }
  22  
  23  static int fd_write (int fdchar const *bufint len) {
  24      int totwritten = 0, nwritten;
  25  
  26      while (totwritten < len) {
  27          nwritten = write (fdbuf + totwrittenlen - totwritten);
  28          if (nwritten < 1)
  29              return (totwritten);
  30          totwritten += nwritten;
  31      }
  32      return (totwritten);
  33  }
  34  

The same mechanism applies to the reading from a socket. If you expect to read e.g. 1000 bytes, then you may have to issue several read()'s before all information is collected. Each read() provides a signal for the next course:

3.2.2: Handling read- and write timeouts

We've seen in section 3.1 that connecting to a network server may result in a timeout, and how to handle it. However, reading from a network socket or writing to one, may cause timeouts too.

The basic principle here is that the system calls read() and write() are blocking calls: they will stop execution of the program until they can do something. In normal situations that is just what you want. For example, tail -f /var/log/messages will monitor /var/log/messages and show text as it is appended to that file. In this case, you'd simply use a blocking read(), and once it signals that information has arrived, display that information.

In contrast, you'll most often want to use some timeout value when handling network sockets -- especially if you're writing a daemon program. The trick here is to see whether a socket can be read or written before the actual read() or write(). The system call select() allows you to do exactly that: peek at a file descriptor to see if a read() or write() would block.

The select() call works as follows:

  1. First you need a timeout specifer, struct timeval. This specifier is set to the timeout length (in seconds and microseconds).

  2. Next, you need two fd_set variables. Each fd_set is cleared, and then the file descriptor you want to watch is added. One of the fd_set's is used as either the read or write watcher; the other fd_set is used as a watcher for exeptions (errors).

  3. After this, the select() call is issued, with the timeout specifier and the file descriptor sets. select() will return the number of available descriptors, or 0 when a timeout occurred.

  4. If there are file descriptors that are ready for reading or writing, then the read() or write() can take place.

The above setup is illustrated in the below listing. The program is identical to the client socket initializer of section 3.1.2, except that main()'s read is replaced by a function socketread(). This function is implements the above mechanism. Note also the checking of exceptions on the socket.

In order to test this yourself, try reading from e.g. an HTTP server. The server won't initiate chatting and will patiently wait for the client. This client will time out, while attempting to read from the server.

   1  /* Sample program: readtimeout.c */
   2  
   3  #include <errno.h>
   4  #include <netdb.h>
   5  #include <stdio.h>
   6  #include <stdlib.h>
   7  #include <string.h>
   8  #include <unistd.h>
   9  #include <sys/types.h>
  10  #include <sys/select.h>
  11  #include <sys/socket.h>
  12  #include <sys/time.h>
  13  
  14  static int timed_out;
  15  static int client_socket (char const *serverint port);
  16  static void timeout_handler (int sig);
  17  static int socketread (int sockchar *bufunsigned bufsz);
  18  
  19  int main (int argcchar const **argv) {
  20      int fd;
  21      char buf[256];
  22      ssize_t nread;
  23  
  24      if (argc != 3) {
  25          fprintf (stderr,
  26                   "Usage: readtimeout server port\n"
  27                   "Reads the first block of data that the server sends,\n"
  28                   "up to 255 bytes, and shows it.\n");
  29          exit (1);
  30      }
  31  
  32      fd = client_socket (argv[1], atoi(argv[2]));
  33      nread = socketread (fdbuf, 255);
  34      buf[nread] = 0;
  35      printf ("%s:%d says:\n%s\n"argv[1], atoi(argv[2]), buf);
  36      close (fd);
  37      return (0);
  38  }
  39  
  40  static int socketread (int sockchar *bufunsigned bufsz) {
  41      struct timeval tv;
  42      fd_set readsetexceptset;
  43      int res;
  44  
  45      /* Prepare the timeout (10 sec) */
  46      tv.tv_sec = 10;
  47      tv.tv_usec = 0;
  48  
  49      /* Prepare the file descriptors sets */
  50      FD_ZERO (&readset);
  51      FD_SET (sock, &readset);
  52      FD_ZERO (&exceptset);
  53      FD_SET (sock, &exceptset);
  54  
  55      /* Block until something happens. The arguments are:
  56       * - nr of file descriptors, FD_SETSIZE is the system value
  57       * - ptr to the set with read-fd's (or 0 if none to watch)
  58       * - ptr to the set with write-fd's (or 0 if none to watch)
  59       * - ptr to the set with exception-fd's (or 0 if none to watch)
  60       * - ptr to the timeout specifier */
  61      if ( (res = select(FD_SETSIZE, &readset, 0, &exceptset, &tv)) < 0 ) {
  62          /* select() returns -1: error */
  63          fprintf (stderr"Select failed: %s\n"strerror(errno));
  64          exit (1);
  65      } else if (! res) {
  66          /* select returns 0: no file descriptor ready. Must be a timeout. */
  67          fprintf (stderr"Timeout occurred\n");
  68          exit (2);
  69      }
  70  
  71      /* Now our socket is either in readset or in exceptset. */
  72      if (FD_ISSET (sock, &exceptset)) {
  73          fprintf (stderr"Exception on socket\n");
  74          exit (3);
  75      }
  76  
  77      /* Our socket must be in the readset. */
  78      return (read (sockbufbufsz));
  79  }
  80  
  81  static int client_socket (char const *serverint port) {
  82      int sock;
  83      struct hostent *hent;
  84      struct sockaddr_in sname;
  85  
  86      /* Create the socket. */
  87      if ( (sock = socket (PF_INETSOCK_STREAM, 0)) < 0 ) {
  88          fprintf (stderr"Failed to create socket: %s\n"strerror (errno));
  89          exit (1);
  90      }
  91  
  92      /* Look up the host. */
  93      if (! (hent = gethostbyname (server)) ) {
  94          fprintf (stderr"Failed to resolve host %s\n"server);
  95          exit (2);
  96      }
  97  
  98      /* Prepare the server name binding. */
  99      sname.sin_family = AF_INET;
 100      sname.sin_port = htons (port);
 101      sname.sin_addr = *(struct in_addr *)hent->h_addr;
 102  
 103      /* Set up the timeout mechanism. We allow 5 seconds for trying. */
 104      signal (SIGALRMtimeout_handler);
 105      alarm (5);
 106  
 107      /* Connect. */
 108      if (connect (sock, (struct sockaddr *) &snamesizeof(sname))) {
 109          close (sock);
 110          alarm (0);
 111          signal (SIGALRMSIG_DFL);
 112          if (timed_out)
 113              fprintf (stderr"Cannot connect to %s:%d: timed out\n",
 114                       serverport);
 115          else
 116              fprintf (stderr"Cannot connect to %s:%d: %s\n",
 117                       serverportstrerror(errno));
 118          exit (3);
 119      }
 120      /* Connect call succeeded */
 121      alarm (0);
 122      signal (SIGALRMSIG_DFL);
 123      return (sock);
 124  }
 125  
 126  void timeout_handler (int sig) {
 127      timed_out++;
 128  }
 129  

3.3: Server-side Socket Handling

Server-side socket handling is in many facets similar to client-side handling, but there are a few specific steps. The first series of actions creates the socket:

Having established a server network socket, the program waits for client activity. The server enters an endless loop to serve clients. The loop usually consists of the following:

Without further ado, let's dive into some code. The following program is started as ./server {port}, where port is a numeric TCP port. The server listens to all network interfaces; it doesn't bind to a specific server address. Once the server establishes its service, it says hello to the client, also stating the client's IP address, and then hangs up.

The following is a rather lengthy listing, which is however self-explanatory in its comments. You can try out the program with the following steps:

  1. In one terminal window, run tail -f /var/log/messages (or whatever your syslogd output file is);

  2. In a next terminal window, start the server using ./server 10000. Unix will allow a user-space process to start a networked process on an unprivileged port (greater than 1024). If you want to run the server on say port 80, then you have to start it as root.

  3. Test the server using the client program from section 3.1.2, as in ./client 10000.

  4. When you're done testing, stop the server using killall server.

   1  /* Sample program: server.c */
   2  
   3  #include <errno.h>
   4  #include <fcntl.h>
   5  #include <signal.h>
   6  #include <stdio.h>
   7  #include <stdlib.h>
   8  #include <string.h>
   9  #include <syslog.h>
  10  #include <unistd.h>
  11  #include <arpa/inet.h>
  12  #include <sys/socket.h>
  13  #include <sys/types.h>
  14  
  15  static int daemonize (void);
  16  static void fd_writestr (int sockchar const *msg);
  17  static int prepare_service (int port);
  18  static void run_service (int serversock);
  19  static void grim_reaper (int sig);
  20  
  21  int main (int argcchar **argv) {
  22      pid_t pid;
  23      int sockport;
  24      
  25      /* Arguments check, scan the port number */
  26      if (argc != 2 || sscanf (argv[1], "%d", &port) < 1) {
  27          fprintf (stderr,
  28                   "Usage: server PORT\n"
  29                   "The port must be a numerical TCP port number.\n");
  30          exit (1);
  31      }
  32  
  33      /* At the parent process level, we create a network socket. */
  34      sock = prepare_service (port);
  35  
  36      /* This will be our reporter on child processes. */
  37      signal (SIGCHLDgrim_reaper);
  38  
  39      /* Next we can become a daemon. */
  40      if ( (pid = daemonize()) > 0 ) {
  41          /* Parent process. */
  42          printf ("Daemon started as process ID %u.\n"
  43                  "We're open for business at port %d.\n"pidport);
  44          close (sock);
  45      } else {
  46          /* Child process. We're a true daemon now, so no more
  47           * fprintf(). We have to use syslog() for messaging. */
  48          openlog ("demo-server"LOG_PIDLOG_DAEMON);
  49          syslog (LOG_NOTICE"Server started!");
  50          run_service (sock);
  51      }
  52  
  53      return (0);
  54  }
  55  
  56  static int prepare_service (int port) {
  57      int sockval = 1;
  58      struct sockaddr_in servername;
  59  
  60      /* Create the socket. */
  61      if ( (sock = socket (PF_INETSOCK_STREAM, 0)) < 0 ) {
  62          fprintf (stderr"Failed to create socket: %s\n"strerror (errno));
  63          exit (2);
  64      }
  65  
  66      /* Make sure it's re-usable upon restarts. */
  67      if (setsockopt (sockSOL_SOCKETSO_REUSEADDR, &valsizeof(val))) {
  68          fprintf (stderr"Failed to set socket options: %s\n",
  69                   strerror(errno));
  70          close (sock);
  71          exit (3);
  72      }
  73  
  74      /* Prepare the naming of the socket, for all IP addresses of this host. */
  75      servername.sin_family = AF_INET;
  76      servername.sin_port = htons (port);
  77      servername.sin_addr.s_addr = htonl (INADDR_ANY);
  78  
  79      /* Bind the socket to the prepared address. */
  80      if (bind (sock, (struct sockaddr *) &servernamesizeof(servername)) < 0) {
  81          fprintf (stderr"Failed to bind socket to port %d: %s\n",
  82                   portstrerror(errno));
  83          exit (4);
  84      }
  85      
  86      /* Tell the kernel that we're listening to it. */
  87      if (listen (sock, 5) < 0) {
  88          fprintf (stderr"Error while listening to server socket %d: %s",
  89                  sockstrerror(errno));
  90          exit (5);
  91      }
  92      return (sock);
  93  }
  94      
  95  static void grim_reaper (int sig) {
  96      pid_t child;
  97      int status;
  98  
  99      /* Clean up any statuses of child processes. */
 100      while( (child = wait4 (-1, &statusWNOHANG, 0)) > 0 ) {
 101          syslog (LOG_NOTICE"Child %u terminated"child);
 102          if (WIFEXITED(status))
 103              syslog (LOG_NOTICE"Exit status: %d"WEXITSTATUS(status));
 104          if (WIFSIGNALED(status))
 105              syslog (LOG_NOTICE"Child got signal %d"WTERMSIG(status));
 106          if (WIFSIGNALED(status) && WCOREDUMP(status))
 107              syslog (LOG_NOTICE"Child dumped core");
 108          if (WIFSTOPPED(status))
 109              syslog (LOG_NOTICE,"Child stopped due to signal %d",
 110                      WSTOPSIG(status));
 111      }   
 112  }
 113  
 114  int daemonize () {
 115      pid_t pid;
 116  
 117      /* Try to fork. */
 118      if ( (pid = fork()) < 0 ) {
 119          /* Failed */
 120          fprintf (stderr"Fork failure: %s\n"strerror(errno));
 121          exit (1);
 122      } else if (pid > 0) {
 123          /* Parent branch */
 124          return (pid);
 125      }
 126      
 127      /* We now must be the child branch. Go to the root dir. */
 128      if (chdir ("/")) {
 129          fprintf (stderr"Cannot chdir to /: %s\n"strerror(errno));
 130          exit (1);
 131      }
 132  
 133      /* Become session leader of a new process group. */
 134      if (setsid() == -1) {
 135          fprintf (stderr"Failed to create process group. Already crated?\n");
 136          exit (1);
 137      }
 138  
 139      /* Close FD's 0,1,2 and reopen them on /dev/null. Note that we
 140       * cannot report on failures of the open() below -- stderr is
 141       * very likely already gone. */
 142      close (0);
 143      close (1);
 144      close (2);
 145      open ("/dev/null"O_RDONLY);
 146      open ("/dev/null"O_WRONLY);
 147      open ("/dev/null"O_WRONLY);
 148  
 149      /* We're a daemon now! */
 150      return (0);
 151  }
 152  
 153  static void run_service (int serversock) {
 154      int clientsocksizepid;
 155      struct sockaddr_in clientname;
 156      char *client_ip;
 157      fd_set readsetexceptset;
 158  
 159      syslog (LOG_NOTICE"Awaiting activity on server socket %d"serversock);
 160  
 161      /* Note that we're a server now. We never return, and never exit --
 162       * this would stop the service! */
 163      
 164      while (1) {
 165          /* Wait for network activity; basically a select() with an
 166           * indefinite timeout specifier. */
 167          FD_ZERO (&readset);
 168          FD_SET (serversock, &readset);
 169          FD_ZERO (&exceptset);
 170          FD_SET (serversock, &exceptset);
 171  
 172          if (select (FD_SETSIZE, &readset, 0, &exceptset, 0) < 0)
 173              continue;
 174          if (FD_ISSET (serversock, &exceptset)) {
 175              syslog (LOG_WARNING"Exception on network socket");
 176              continue;
 177          }
 178  
 179          /* We got action! Accept the network connection. */
 180          size = sizeof(clientname);
 181          if ( (clientsock = accept (serversock, (struct sockaddr *) &clientname,
 182                              (socklen_t *) &size)) < 0) {
 183              syslog (LOG_WARNING"Failed to accept network connection: %s",
 184                      strerror(errno));
 185              continue;
 186          }
 187          client_ip = inet_ntoa (clientname.sin_addr);
 188          syslog (LOG_NOTICE"Accepted network connection from %s on fd %d",
 189                  client_ipclientsock);
 190  
 191          /* Try to fork off a handler for this client, so that the
 192           * daemon can serve new requests. */
 193          if ((pid = fork()) < 0) {
 194              /* Fork failed */
 195              syslog (LOG_WARNING"Failed to fork: %s"strerror(errno));
 196              close (clientsock);
 197          } else if (pid) {
 198              /* Parent branch of this fork */
 199              syslog (LOG_NOTICE"New connection from %s, handled by pid %d",
 200                      client_ippid);
 201              close (clientsock);
 202          } else {
 203              /* Child branch of this fork, the grandchild overall.
 204               * Serve the request, and don't forget to exit! */
 205              syslog (LOG_NOTICE"Servicing client %s"client_ip);
 206              fd_writestr (clientsock"Hello there, ");
 207              fd_writestr (clientsockclient_ip);
 208              fd_writestr (clientsock"!\n");
 209              close (clientsock);
 210              exit (0);
 211          }
 212      }
 213  }
 214  
 215  static void fd_writestr (int sockchar const *msg) {
 216      fd_set writesetexceptset;
 217      int len = strlen (msg), totwritten = 0, nwrittenres;
 218      struct timeval tv;
 219  
 220      /* Loop until we've written all bytes. */
 221      while (totwritten < len) {
 222          /* Wait until the socket is writable (or time out after 5 secs). */
 223          FD_ZERO (&writeset);
 224          FD_SET (sock, &writeset);
 225          FD_ZERO (&exceptset);
 226          FD_SET (sock, &exceptset);
 227          
 228          tv.tv_sec = 5;
 229          tv.tv_usec = 0;
 230          
 231          if ((res = select (FD_SETSIZE, 0, &writeset, &exceptset, &tv)) < 0) {
 232              syslog (LOG_WARNING"Select failed");
 233              exit (1);
 234          }
 235          if (!res) {
 236              syslog (LOG_NOTICE"Timeout while servicing client");
 237              exit (1);
 238          }
 239          if (FD_ISSET (sock, &exceptset)) {
 240              syslog (LOG_WARNING"Exception on client connection");
 241              exit (2);
 242          }
 243  
 244          /* Send some bytes. At least 1 byte must be sent. */
 245          if ((nwritten = write (sockmsg + totwritten,
 246                                 len - totwritten)) < 1) {
 247              syslog (LOG_WARNING"Write error on network socket");
 248              exit (3);
 249          }
 250          totwritten += nwritten;
 251      }
 252  }
 253