CSCI.4220 Network Programming
Spring, 2006
Server Design, other kinds of sockets

Unix Domain Sockets

The first argument of the socket call is the domain. We have seen two domains so far, the Internet Domain Version 4 (AF-INET), and the Internet Domain Version 6 (AF_INET6). Another domain is the Unix Domain (AF_LOCAL or AF_UNIX). This sets up a socket connection between two processes on the same host. It is similar to a pipe except that it is full duplex and the two two ends of communication do not need to have a common ancestor.

It is also equivalent to using localhost in the client to connect to a server on the same machine, although the performance is often better. The X Windows system uses this.

There is no concept of a port in this domain, the address is a pathname.

struct sockaddr_un {
    sa_family_t sun_family;  /* AF_LOCAL or AF_UNIX */
    char sun_path[104];      /* null terminated pathname */
};
Once the socket has been created and bound, it works like any other socket, so we do not need to spend much time on it. The code for a simple server is identical to other servers that we have seen with these minor differences. Note. Only those lines which are different from those of our standard server are shown. A pathname is passed in as an argument to the program. The pathname can be either relative (starting at the current working directory) or absolute (starting at the root).
#include <sys/un.h>


     struct sockaddr_un server;
     struct sockaddr_un from;  

     sock=socket(AF_LOCAL, SOCK_STREAM, 0); 

     server.sun_family=AF_LOCAL ;

     len = sizeof(server.sun_path); 
     if (strlen(argv[1]) > len) error("Pathname too long");
     strcpy(server.sun_path,argv[1]);
If your argument was ttt, When you do an ls -l you see ./ttt and the permissions look like this

+srwxr-xr-x+

Here is a complete Unix domain server
Here is a complete Unix domain client that you can use to test your server. Pass a string to each of these (use the same string for both).

You can also use Unix domain datagram sockets

Raw sockets

The second argument to the socket call is the socket type. We have seen two types so far, SOCK_STREAM for TCP and SOCK_DGRAM for UDP. A third type is a raw socket (SOCK_RAW). A raw socket is one that skips the Transport layer completely.

The protocol can be specified. If protocol is zero, all packets go to the socket. If protocol is specified, then only packets with that protocol are received.

There is no address structure so there is no binding and no port numbers.

To read and send data, use sendto and recvfrom (The functions for sending and reading datagrams).

Ordinary users are not permitted to use this; only users with superuser privileges are allows to create a raw socket.

You would not use a raw socket for ordinary uses. It is used for network debugging or testing or other unusual uses. You can use it to build your own IP header for example. or you can use it to read or write Internet Message Control Protocl (ICMP) packets

The ping program sends ICMP message requests

Using alarms to cause a read to time out

Recall that an interrupt is an asynchronous event which can happen at any time. When an interrupt occurs, the processor stops executing instructions in the current running process and executes an interrupt handler function in the kernel. Unix systems have a software interrupt mechanism called signals.

The read, recv, and recvfrom system calls are supposed to return with error EINTR if a signal (interrupt) is received. However on some systems such as FreeBSD the default is to disable this; while on other systems such as Solaris, the default is not to disable this. The action of restarting a read without returning if an interrupt occurred is called a restart. You can control restart behavior with the siginterrupt function. Here is part of the man page for this.

#include <signal.h>

int siginterrupt(int sig, int flag);

DESCRIPTION
The siginterrupt() function is used to change the system call restart
behavior when a system call is interrupted by the specified signal.  If
the flag is false (0), then system calls will be restarted if they are
interrupted by the specified signal and no data has been transferred yet.
System call restart has been the default behavior since 4.2BSD, and is
the default behaviour for signal(3) on FreeBSD.

If the flag is true (1), then restarting of system calls is disabled.  If
a system call is interrupted by the specified signal and no data has been
transferred, the system call will return -1 with the global variable
errno set to EINTR.  Interrupted system calls that have started transfer-
ring data will return the amount of data actually transferred.  System
call interrupt is the signal behavior found on 4.1BSD and AT&T System V
UNIX systems.

If you would like read or recv to return after a period of time rather than be blocked indefinitely if there is nothing to read, and you were too lazy to track all of the complexities of the select function, you can use this feature by setting an alarm.

The alarm(int t) function causes a SIGALRM signal to be sent after t seconds. The default signal handler for SIGALRM is to terminate the program, so you have to write an alternative signal handler, which includes whatever action you want to happen if the alarm goes off.

To set a signal handler for a signal, use the signal function. This takes two arguments, the signal and the name of the function that will be the signal handler.

Here is a UDP server that waits for datagram. If it has not received a datagram within five seconds of starting, it gives up. The interesting lines have this /****/; the other lines are the standard udp server stuff.

/* This program creates a datagram server process 
   in the internet domain on Unix.  It times out
   if no datagram is received within 5 seconds
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <strings.h>
#include <arpa/inet.h> 
#include <stdlib.h> /* for atoi */
#include <signal.h>
#include <unistd.h> /* for alarm */
#include <errno.h>  /* for EINTR */
#define BUFSIZE 1024

void sig_alm(int signo)  /*****/
{
  printf("The alarm went off\n"); /*****/
}
extern int errno;

void error(char *);

int main(int argc, char *argv[])
{
   int sock, length, fromlen, n;
   struct sockaddr_in server;
   struct sockaddr_in from;
   char buf[BUFSIZE];
   char *returnmsg = "I got your message";
      
   if (argc < 2) {
     fprintf(stderr,"usage %s port\n",argv[0]);
     exit(0);
   }

   signal(SIGALRM, sig_alm); /*****/
   siginterrupt(SIGALRM,1);  /*****/  /* turns off restart */
   sock=socket(AF_INET, SOCK_DGRAM, 0);
   if (sock < 0)
       error("Opening socket");
   length = sizeof(server);
   memset(&server,0,length);
   server.sin_family=AF_INET;
   server.sin_addr.s_addr=INADDR_ANY;
   server.sin_port=htons((unsigned short)atoi(argv[1]));
   if (bind(sock,(struct sockaddr *)&server,length)<0) 
       error("binding"); 
   while (1) {
       fromlen = sizeof(from);
       alarm(5);  /*****/
       n = recvfrom(sock,buf,BUFSIZE,0,(struct sockaddr *)&from,&fromlen);
       if (n < 0) {
          if (errno == EINTR) {   /****/
              printf("recvfrom timed out; good bye\n");
              exit(0);              
          }
          else  error("recvfrom");
       }
       buf[n]='\0';
       printf("The message from %s is %s\n",
            inet_ntoa((struct in_addr)from.sin_addr), buf);
       n = sendto(sock,returnmsg,strlen(returnmsg),0,(struct sockaddr *)&from,fromlen);
       if (n < 0) error("sendto");
   }
   return 0;
 }

void error(char *msg)
{
    perror(msg);
    exit(0);
}

Daemon Processes and the inetd superserver

A daemon is a process that runs in background and is not associated with a controlling terminal. Typical Unix systems have 20 to 50 daemons running in background doing various administrative tasks.

The windows equivalent is a service.

Most daemons are started at system initialization. There is a system initialization script that does things like this.

They generally have superuser privileges

One of these is the cron daemon, which keeps a table of events in a file such as /etc/crontab. It wakes up once a minute and sees if anything needs to be run.

If a daemon has to output a message, it can't do it directly because it has closed stdin, stdout and stderr. Therefore, messages that would normally be written to standard output or standard error are written to the system log. There is a syslogd daemon which daemons can use for this.

Here is the function prototype

void syslog(int priority, const char *message, ... )
Priority ranges from 0 (emergency LOG_EMERG) thru 7 LOG_DEBUG. The second argument works like printf, so there can be multiple args.

Here is some skeleton code for creating a daemon (modified from Unix Network Programming: The Sockets Networking API Vol 1, third edition,by W. R. Stevens, B Fenner, and A. M Rudoff, Addison Wesley, 2004)


int daemon_init(const char *pname, int facility)
{
   int i;
   pid_t pid;

   pid = fork();

   if (pid < 0) error("forking");

   if (pid > 0) exit(0);  // parent process terminates 

   if (setsid() < 0) error("setsid");  // sets a new session id
          //so that shell cannot send a kill signal

   signal(SIGHUP, SIG_IGN);  // ignore the hangup signal

   pid = fork();
   
   if (pid > 0) exit(0);

   //this guarantees that the child is not a session leader
   //and so it cannot obtain a controlling terminal

   chdir("/"); // change to the root directory

   for(i=0;i < MAXFD;i++) close(i);

   open("/dev/null",O_RDONLY);
   open("/dev/null",O_RDWR);
   open("/dev/null",O_RDWR);

   // This guarantees that anything written to stdout or stderr will
   // not cause a seg fault.

   openlog(pname, gLOG_PID, facility);  
   
The inetd Daemon

On a typical Unix system, there could be many servers in existence, waiting for a request. Before BSD4.3 each had a process associated with it. Each daemon took a slot in the process table, but was asleep most of the time.

Examples include ftp, telnet, rlogin, finger

These all do pretty much the same thing

The solution is inetd, the internet superserver

inetd starts, makes itself a daemon, reads /etc/inetd.conf and creates a socket for all services specified in the file.

Each socket is bound appropriately. Port is determined by calling getservbyname with the service-name and the protocol fields

It listens on each socket

It calls select

Whenever a connection is received on any of the listening sockets, it wakes up, forks off a child and execs the appropriate process to handle the connection.

Design of Servers

Possible Server Designs

When would you want to use each?

Iterative server best if the response from the server is quick because there is minimal overhead.

Concurrent server with fork best if there will be few connections but each connection will do extensive reading and writing over an extended period. You have to deal with zombies.

Concurrent server with threads generally better than a concurrent server with fork, because the overhead of creating a new thread is much less than that for creating a new process.

server with select This design is best for a server which wants to listen on many sockets simultaneously but where connections are relatively rare. The obvious example is the inetd described in the previous section.

Preforking server This is a new concept, but it is worth studying because it is probably the best design for servers which have to handle many requests and response time is important. For example, file servers or web servers usually use a preforking model. These receive many requests and have to respond very quickly. The overhead associated with creating a new process or even a new thread for each request would be prohibitive if the server gets heavy use.

To do this, the server calls socket, bind and listen exactly as we have seen, and then calls fork several times to create a number of identical processes. Each of these processes then enters its infinite loop and each calls accept.

Recall that when a call to fork creates a new child process, all of the file descriptor information is duplicated. This means that process is listening on the same port.

Each process goes to sleep. What happens when a connection occurs is somewhat system dependent; here is how it works on Berkeley Unix. When a connection arrives, all N processes are awakened. This is because all have been put to sleep on the same wait channel. Exactly one of these will accept the connection (accept will return). The others will go back to sleep.

The code should be written such that each connection is handled concurrently. The process that accepted the connection will read the request and supply the response. Meanwhile, if other connections arrive, another process will accept and handle it. When a particular process completes a request, it closes the socket on which it received and sent the data, and goes back to the accept statement again.

One issue is how many processes to create. If there are too few processes for the number of connections, clients may still be forced to wait if all of the processes are busy handling other connections. However, there is some minimal overhead associated with waking up many processes, and so it is inefficient to create too many processes.

This may not work on other Unix implementations. One solution is to put a lock or some other mutual exclusion primitive around the accept statement so that only one process will be able to accept at any given instance.

The apache web server uses preforking with an additional twist. It can change the number of processes based on load. It periodically checks to see how many processes are busy. If most are busy, it creates more processes; if most are idle, it can kill some of the processes. The system administrator can set a minimum and maximum number of child processes.

Prethreaded server This works in much the same way as a preforking server, and has more or less the same advantages and disadvantages. One potential problem with this is that if a fatal exception occurs, such as a segmentation fault, it will kill the entire process including all the threads, while if it happens in a preforked server, it will kill that process, but the other processes can continue. Of course if code is well written, this should never happen.

Required Reading

Here is an article which covers much of the same material.

Read the two short articles referenced on the course home page for this Thursday's quiz.