THE SOCKET INTERFACE - CONNECTION MODE SERVICE

 
        o Server routines:
 
            -- socket( )
                  o Creates a transport endpoint
                  o Specify datagram or virtual circuit
 
            -- bind( )
                  o Allows server to attach itself to an address
 
            -- listen( )
                  o Marks a transport endpoint as ``listen state''
 
            -- accept( )
                  o Returns an accepted connection
 
            -- read( )
                  o Reads data from transport endpoint
 
            -- write( )
                  o Writes data to transport endpoint
         
 
        o Client routines:
 
            -- socket( )
                  o Creates a transport endpoint
                  o Specify datagram or virtual circuit
 
            -- connect( )
                  o Binds to a return port
                  o Issues a connection request to server
 
            -- read( )
                  o Reads data from transport endpoint
 
            -- write( )
                  o Writes data to transport endpoint

GENERAL FLOW

 
 
                   Server
 
                  socket( )
                     |
                     |
                     |
                   bind( )
                     |                                 Client
                     |
                     |
                  listen( )                            socket( )
                     |                                     |
                     |                                     |
                     |                                     |
                  accept( )                            connect( )
                     |
                     |                                     |
                                                           |
                      <----------------------------------  |
                     |
                     |                                     |
                     |
 
                      <------------------------------  write( )
                   read( )                                 |
                     |                                     |
                     |                                     |
                     |                                     |
                     |                                     |
                                                           |
                                                           |
                   write( ) --------------------------->
                                                        read( )
 
         

SOCKET APPLICATION

 
        o We will implement the query protocol using the socket
          interface
        o The connection-oriented version uses TCP/IP
        o The connectionless version uses UDP/IP
        o Parts of the code (i.e., name lookup routines) will be
          different for other transport providers
 

SERVER CODE

 
          #include <stdio.h>
          #include <sys/types.h>
          #include <sys/socket.h>
          #include <netinet/in.h>
 
          #define SERV_PORT  5134
          #define MAXNAME    1024
 
          main()
          {
               int socket_fd;       /* file descriptor into transport */
               int recfd;           /* file descriptor to accept on   */
               int length;          /* length of address structure    */
               struct sockaddr_in myaddr; /* address of this service  */
               struct sockaddr_in client_addr; /* address of client   */
               signal(SIGCHLD, SIG_IGN);
 
               /* Get a socket into TCP/IP */
               if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
                    perror("socket failed");
                    exit(1);
               }
 
               /*
                *  Set up our address
                */
 
               bzero((char *)&myaddr, sizeof(myaddr));
               myaddr.sin_family = AF_INET;
               myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
               myaddr.sin_port = htons(SERV_PORT);

NOTES

 
        o The sockaddr structure is defined as follows:
 
          struct sockaddr {
              u_short sa_family;
              char    sa_data[14];
          }
 
        o sa_family specifies the address family
        o sa_data holds the address
        o Applications cast transport specific addresses into this
          structure
 
        o The sockaddr_in structure holds internet addresses, and is
          defined as follows:
 
          struct sockaddr_in {
              short   sin_family;
              u_short sin_port;
              struct  in_addr sin_addr;
              char    sin_zero[8];
          };
 
        o sin_port is the port number
        o sin_addr is the host IP address (32 bits)
        o sin_zeros fills in the remaining 8 bytes of the address
          allocated in the sockaddr structure

 

SERVER CODE (cont.)

 
               /*
                *  Bind to the address the service will be offered
                *  over.
                */
 
               if (bind(socket_fd, (struct sockaddr *)&myaddr,
                                      sizeof(myaddr)) < 0) {
                    perror("bind failed");
                    exit(1);
               }
 
               /*
                *  Set the socket up for listening, with a queue
                *  length of 1
                */
 
               if (listen(socket_fd, 1) < 0) {
                    perror("listen failed");
                    exit(1);
               }
 
               /*
                *  Loop continuously, waiting for connection requests
                *  and performing the service.
                */
 
               length = sizeof(client_addr);
               while (1) {
                    if ((recfd = accept(socket_fd,
                        (struct sockaddr_in *)&client_addr, &length)) < 0) {
                         perror("could not accept call");
                         exit(1);
                    }
                    (void) run_server(socket_fd, recfd);
               }
          }
 
          int
          run_server(socket_fd, resfd)
          int socket_fd;
          int resfd;
          {
               switch (fork()) {
                  case -1:
                    perror("fork failed!\n");
                    exit(1);
                  default: /* parent */
                    close(resfd);
                    return;
                  case 0:  /* child */
                    close(socket_fd);
                    perform_actions(resfd);
                    exit(0);
               }
          }
 
          int
          perform_actions(conn_fd)
          int conn_fd;
          {
               int done = 0;    /* indicates all data is read        */
               int where = 0;   /* points to where we are in buffer  */
               int nbytes;      /* the number of bytes read          */
               char reply;      /* a 'y' or a 'n' to reply to client */
               char buf[BUFSIZ];/* buffer to hold the name           */
               char cmd[BUFSIZ];     /* used to figure out if the  */
               char junkbuf[BUFSIZ]; /* specified user is logged   */
               FILE *fp;             /* onto the system            */
 
               /* Read the user name from the client.  */
               do {
                    if ((nbytes = read(conn_fd, &buf[where],
                                                    BUFSIZ-where)) < 0) {
                         perror("read of data failed!");
                         exit(1);
                    }
                    where += nbytes;
                    if (buf[where - 1] == '\0') {
                         done = 1;
                    }
               } while (!done);
 
               /*
                *  Determine if the user is logged on...
                */
 
               sprintf(cmd, "who | grep '^%s '", buf);
               if ((fp = popen(cmd, "r")) == NULL
                            || fgets(junkbuf, BUFSIZ, fp) == NULL) {
                    reply = 'n';
               } else {
                    reply = 'y';
               }
 
               /*
                *  Send a "y" of a "n" to the client.
                */
               if (write(conn_fd, &reply, 1) < 0) {
                    perror("write failed!");
                    exit(1);
               }
 
               (void)read(conn_fd, &buf[0], 1);
          }
 

Notes

 
 
        o The normal way to terminate a connection is with the close( )
          system call
 
        o The close( ) system call attempts to send any undelivered data
 
        o You can also use the shutdown( ) routine
            -- Provides more control
            -- Can stop both incoming and outgoing data
 
        o Although not necessary when you use TCP, it's a good idea
          for the peer that did the last write to wait for the other
          side to consume the data.
            -- We do that by issueing a read( ) call after we send the
               last piece of data
            -- The read( ) call will block until the peer shuts down the
               connection
            -- That way, we know the peer read the last piece of data
            -- In some transport providers (other than TCP), it is
               requires that you do that, because if you close the
               connection immediately after the last write, the last
               piece of data may be lost.

Zombies and Child Cleanup

 
        o We create a child process in the server part of the application
 
        o When a child process exits, the kernel keeps the child's exit
          status around until the parent collects it
 
        o If the parent does not collect the information, the child
          remains a zombie process
 
        o When a child exits, it sends a SIGCHLD signal to the parent,
          and the parent is expected to issue the wait3 call to
          get the exit status of the child.
            -- That cleans up the zombie
 
        o So, you must set up a signal handler to catch SIGCHLD and
          clean up the zombie
 
        o But, you don't want to be interrupted in certain system
          calls (like fork), because that would cause the
          system call to fail.
 
        o So we will use sigblock and sigsetmask to block
          critical sections
            -- However, when we are blocked, we may have several
               children die, and only one signal is posted
            -- So, we must loop in the signal handler to clean up
               all exited children
 
        o In Solaris, if you issue sigset(SIGCHLD, SIG_IGN)
          then the OS will automatically clean up all zombies for you
            -- You don't have to have a specific handler to do it.
 
        o But other versions of UNIX still require a handler.
 
The following code illustrates what you have to do.
 
I've used a compile variable called OLD_SIGHANDLER to use both
the solaris way and the non-solaris way.  When you compile on a non-Solaris
machine, you should use the -DOLD_SIGHANDLER flag to the
cc command line.
 
 
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <sys/errno.h>
#include <sys/signal.h>
#include <string.h>
#include <values.h>
 
extern int errno;
 
main()
{
   ....
 
   int mask;                       /* signal mask                    */
   void handler();                 /* child signal handler           */
 
#ifdef OLD_SIGHANDLER
   (void)sigset(SIGCHLD, handler);
#else
   (void)sigset(SIGCHLD, SIG_IGN);
#endif
 
   .......
 
   /*
    *  Loop continuously, waiting for connection requests
    *  and performing the service.
    */
   length = sizeof(client_addr);
   while (1) {
      if ((recfd = accept(socket_fd, (struct sockaddr *)&client_addr,
                          &length)) < 0) {
         /*
          *  If we failed because we were interrupted (that can
          *  happen because we got a SIGCHLD), simply try again.
          */
         if (errno == EINTR) {
             continue;
         }
         perror("could not accept call");
         exit(1);
      }
      (void) run_server(socket_fd, recfd);
   }
   .....
 
}
 
#ifdef OLD_SIGHANDLER
int
run_server(socket_fd, resfd)
int socket_fd;
int resfd;
{
    mask = sigblock(sigmask(SIGCHLD));
    switch (fork()) {
       case -1:
          perror("fork failed!\n");
          exit(1);
 
       default: /* parent */
          (void) close(recfd);
          (void) sigsetmask(mask);
          /*
           * Break out of switch and continue loop.
           */
          break;
 
       case 0:  /* child */
          (void) close(socket_fd);
          (void) sigsetmask(mask);
          perform_actions(recfd);
          exit(0);
    }
}
#else
int
run_server(socket_fd, resfd)
int socket_fd;
int resfd;
{
     switch (fork()) {
          case -1:
               perror("fork failed!\n");
               exit(1);
          default: /* parent */
               close(resfd);
               return;
          case 0:  /* child */
               close(socket_fd);
               perform_actions(resfd);
               exit(0);
     }
}
#endif
 
#ifdef OLD_SIGHANDLER
void
handler()
{
        while (wait3(NULL, WNOHANG, NULL) > 0)
                ;
}
#endif
 
 
Now lets look at the client side of the application.....
 

Client Code

 
          #include <stdio.h>
          #include <netdb.h>
          #include <signal.h>
          #include <sys/types.h>
          #include <sys/socket.h>
          #include <netinet/in.h>
 
          #define SERV_PORT  5134
          #define MAXNAME    1024
 
          main(argc, argv)
          int argc;
          char **argv;
          {
               int fd;                   /* fd into transport provider */
               int length;               /* length of message          */
               char buf[BUFSIZ];         /* holds message from server  */
               struct hostent *hp;       /* holds IP address of server */
               struct sockaddr_in myaddr; /* address that client uses  */
               struct sockaddr_in servaddr; /* the server's full addr  */
 
               /*
                *  Check for proper usage
                */
 
               if (argc != 3) {
                    fprintf(stderr,"Usage: %s user host\n", argv[0]);
                    exit(2);
               }
         
               /*
                *  Get a socket into TCP/IP
                */
               if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
                    perror("socket failed!");
                    exit(1);
               }
 
               /*
                *  Bind to an arbitrary return address
                */
 
               bzero((char *)&myaddr, sizeof(myaddr));
               myaddr.sin_family = AF_INET;
               myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
               myaddr.sin_port = htons(0);
               if (bind(fd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) {
                    perror("bind failed!");
                    exit(1);
               }
         
               /*
                *  Fill in the server's address and the data
                */
 
               bzero((char *)&servaddr, sizeof(servaddr));
               servaddr.sin_family = AF_INET;
               servaddr.sin_port = htons(SERV_PORT);
               hp = gethostbyname(argv[2]);
 
               if (hp == 0) {
                    fprintf(stderr, "could not obtain address of %s\n",
                         argv[2]);
                    exit(1);
               }
 
               bcopy(hp->h_addr_list[0], (caddr_t)&servaddr.sin_addr,
                    hp->h_length);
 
               /*
                *  Connect to the server
                */
 
               if (connect(fd, (struct sockaddr *)&servaddr,
                                    sizeof(servaddr)) < 0) {
                    perror("connect failed!");
                    exit(1);
               }
         
               /*
                *  Write the user name and read the server's response
                */
 
               length = strlen(argv[1]) + 1;
 
               if (write(fd, argv[1], length) != length) {
                    perror("write failed!\n");
                    exit(1);
               }
 
               if (read(fd, buf, 1) == -1) {
                    perror("read failed!\n");
                    exit(1);
               }
 
               if (buf[0] == 'y') {
                    printf("%s is logged on server\n", argv[1]);
               } else {
                    printf("%s is not logged on server\n", argv[1]);
               }
               exit(0);
          }