
Hello everyone,

Sorry about the delay in getting this week's notes to you...

Here are the tutorial notes for this week. Remember, feel free to contact
me if you have any questions about the material. (You don't have to email
me after your tutorial this week, but if there are any problems please let
me know about them.)

Reminders
=========

 - Eron is away this week and I've asked his students to attend one of the
   other tutorials. As a result, you may each have a few more students
   tonight.


Tutorial 11 Notes -- August 3
=============================

 There are only two tutorials remaining this term, including this tutorial.
 This week we're going to cover client-server communication with sockets, and
 select(). Next week there will be a couple small examples to present, but I'd
 like to reserve most of the time for exam review.

 (1) Assignment 4

     Assignment 4 has been posted and is due Friday, August 12. You should
     probably take a look at the assignment before class so you can answer any
     questions. The most difficult part of the assignment is understanding the
     message passing protocol. Some sample code is given to establish a simple
     client and server (which you'll briefly go over tonight), so most of the
     work is actually deciding what to send between the client and server. If
     you have any questions about the assignment, please feel free to ask me
     about it.

 (2) select()

     One of the problems we have when we want to read from multiple sources is
     that the read operation will block if the sending source isn't ready (at
     least according to the model we've been using). So, if a process has to
     make a choice as to which source to read, it's possible that we could
     choose wrong and the process may be blocked indefinitely.

     One solution to this problem is to use select(). select() monitors a set
     of file descriptors and returns with information about which file
     descriptors are ready to be read (there could be more than 1).

     In this example (which was given as sample code for A4), we demonstrate
     the use of select() with a collection of pipes. Here's the code, slightly
     shortened and with error checking removed. If you want to try out the
     program yourself, grab the "pipeselect.c" file from the A4 specification
     page.

       int i, len, r, pid, maxfd, pipefd[5][2];
       char buf[256];
       fd_set allset, readset;

       for (i = 0 ; i < 5 ; i++) {      /* (A) create pipes and 5 children */
           pipe(pipefd[i]);
           pid = fork();
           if (pid == 0) {              /* child code */
               close(fd[0]);
               for ( ; ; ) {
                   r = rand() % 5 + 1;
                   sleep(r);            /* sleep for a while */

                   len = sprintf(buf, "Child %d, PID %d, slept for %d sec.",
                                 i, getpid(), r);
                   write(pipefd[i][1], buf, len);
               }
           }
       }

       if (pid != 0) {                  /* (B) parent code */
           FD_ZERO(&allset);
           maxfd = pipefd[0][0];

           for (i = 0 ; i < 5 ; i++) {  /* (C) build set of fds */
               close(pipefd[i][1]);
               FD_SET(pipefd[i][0], &allset);

               if (pipefd[i][0] > maxfd)
                   maxfd = pipefd[i][0];
           }

           for ( ; ; ) {                /* (D) wait for fds to become ready */
               readset = allset;
               select(maxfd + 1, &readset, NULL, NULL, NULL);

               for (i = 0 ; i < 5 ; i++) {
                   if (FD_ISSET(pipefd[i][0], &readset)) {
                       len = read(pipefd[i][0], buf, 256);
                       buf[len] = '\0';
                       printf("Parent read from pipe %d: \"%s\"\n", num, buf);
                   }
               }
           }
       }

     Running this program will produce output of the form:

         Parent read from pipe 0: "Child 0, PID 3593, slept for 3 sec."
         Parent read from pipe 1: "Child 1, PID 3594, slept for 3 sec."
         Parent read from pipe 3: "Child 3, PID 3596, slept for 3 sec."
         Parent read from pipe 3: "Child 3, PID 3596, slept for 1 sec."
         Parent read from pipe 2: "Child 2, PID 3595, slept for 5 sec."
         Parent read from pipe 4: "Child 4, PID 3597, slept for 5 sec."
         Parent read from pipe 1: "Child 1, PID 3594, slept for 2 sec."
         Parent read from pipe 3: "Child 3, PID 3596, slept for 1 sec."
         Parent read from pipe 3: "Child 3, PID 3596, slept for 1 sec."
         ...the program keeps running until stopped...

     Here are some points you can make:

       - In part (A), 5 child processes are created. A pipe is created for
         communication between the parent and each child.
       - Once a child has been created it enters a loop. The child sleeps for
         a random length of time, then writes a message to its pipe
         that identifies the child and the length of time it slept.
       - Once the parent has created all the pipes and child processes, it
         executes the code at (B). The code at (C) is used to create the set
         of file descriptors that will be monitored by select(). A number of
         operations are defined to manipulate the file descriptor sets
         (fd_set):

             FD_ZERO(&set);       /* create empty set */
             FD_SET(fd, &set);    /* add fd to set */
             FD_ISSET(fd, &set);  /* test if fd is in set */

         The code at (C) also keeps track of the value of the maximum file
         descriptor, also needed by select().
       - At (D), the parent enters a loop. The select() call is made using the
         complete set of file descriptors. select() will block until at least
         one file descriptor in this set becomes ready (i.e., there is
         information to read on a pipe).
       - select() modifies the set of file descriptors passed to it (readset)
         so that when select() returns, those file descriptors that are
         ready will be left in the set.
       - At this point the program simply checks to test and see if a file
         descriptor is in the set (using FD_ISSET). If it is, the program
         reads from the appropriate pipe and displays the message that was
         read. Since more than one file descriptor could be ready, we need to
         test each file descriptor. It isn't good enough to process the first
         ready file descriptor and quit. (Note: the return value of select()
         is the number of file descriptors that are ready so we could write
         more efficient code, i.e., if it returns N then after we have
         processed N pipes we could quit.)
       - The program then beings the next loop by restoring the set of file
         descriptors to be monitored, calling select(), and processing the
         ready file descriptors again.
       - In general, select() can be used with any file descriptors. (For
         instance, A4 requires sockets, not pipes.)

 (3) Client-server communication with sockets

     We've been talking about client-server communication using sockets for
     the last couple of weeks. The sample code "client.c" and "server.c"
     demonstrates a very simple client and server that exchange messages with
     each other. You probably won't have time to present these programs, but
     instead you can just go over the basics.

     On the server end, the server has to make 4 function calls to establish a
     connection with a client:

         socket() - create the socket
         bind() - bind address/port information to socket
         listen() - setup queue for connecting clients
         accept() - wait for a connection from a client

     On the client side there are only two steps:

         socket() - create the socket
         connect() - connect to the server (using approprirate address info.)

     (The basic client-server code that establishes a communication channel is
     pretty much always the same as it is in client.c and server.c, so for A4
     it'll basically be cut-and-paste.)

     The first thing to note on the server side is that when accept()
     succeeds, it returns a file descriptor that can be used for communication
     with the connected client. At this point you can use this file descriptor
     as you would any other file descriptor, i.e., with read(), write(),
     select(), etc.

     The other important thing to note on the server side is that accept()
     blocks. As a result, a server that wants to be able to accept new
     connections, as well as communicate with existing clients that are
     already connected, should use select() with the file descriptor of its
     socket. If select() returns that this file descriptor is ready, the
     server should then accept() a connection (to actually complete the
     communication channel with the client).

     Note: in terms of A4, they have to figure out how to set up select() so
     that it works correctly with accept(). When a new client connects, the
     file descriptor of the new client needs to be added to the set of file
     descriptors that will be monitored on the next select() call.

     On the client side, provided connect() succeeds, the client will be able
     to communicate with the server using the file descriptor of its socket.
     Again, at this point the file descriptor is no different than an ordinary
     file descriptor or a pipe's file descriptor.

     Note: you may not have time to cover everything this week, and that's
	okay. I've had some questions about select() so it's probably
	important to get through that part, but it's okay if you don't have
	time for client-server communication.
