Assignment 2, CS-701, Fall 1997

Object Broker Project -- Use Case 1

Use Case 1 -- om and ou Exchange Datagrams

Create a new directory for this project, and create an RCS subdirectory under it. You are going to write two programs for this step (ou and om), which may each be built from single source files, ou.cc and om.cc. Unless you decide to have more than one source file for each program, the Makefile for this project may consist of just the following two lines:
      LDLIBS = -lsocket -lnsl
      all : ou om
Make automatically adds the LDLIBS macro to the g++ command any time it uses an implicit rule to invoke the linker (ld), just as it adds the CXXFLAGS that you already know about. The two libraries named in this macro are needed for any programs that use sockets. However, if you build your programs on qcunix1 you have to omit the nsl library because they don't use it and it isn't available there.

If you use the strtok_r() function in either of your programs, you will also have to modify the value of CXXFLAGS in your Makefile:

      CXXFLAGS = -g -Wall -D_REENTRANT
The two programs will communicate by exchanging datagrams. Since you have sample code for stream sockets, but not for datagram sockets, here are the changes you will have to make to use datagrams:
  1. The second argument to socket() has to be SOCK_DGRAM instead of SOCK_STREAM.
  2. The server uses bind() to establish its "well known" port number, but it does not call listen() or accept(). Instead it calls recvfrom() to receive datagrams as they arrive:
          bytesReceived = recvfrom( well_known_socket_fd,
                                    buffer_to_receive_datagram,
                                    sizeof buffer_to_receive_datagram,
                                    0,  // Flags: none need to be set.
                                    &sockaddr_for_sender,
                                    &sockaddr_for_sender_size);
    
    When a datagram arrives, sockaddr_for_sender will be filled in with host address and port number for the datagram sender; and sockaddr_for_sender_size is an int that you must initialize before each call to recvfrom() with the size of a sockaddr_in struct, and which the kernel will modify with the size of the socket address before the call returns. (This is for generality so that datagrams could be sent and received using different formats for the socket addresses on the client and server ends. We don't use this feature, but we have to make our code consistent with it anyway.)
  3. The server can send a datagram back to a client by using the sendto() function:
          bytesSent = sendto( well_known_socket_fd,
                              message_to_send,
                              strlen(message_send),
                              0,  // Flags: none need to be set.
                              &sockaddr_for_sender,
                              sockaddr_for_sender_size);
    
    The sockaddr_for_sender would be the same struct that was used in the matching call to recvfrom(). Note that unlike recvfrom(), the last argument is a value, not a pointer in this function.
  4. The client does not use connect(). Instead it uses sendto() to send a datagram message to the server, and then uses recvfrom() to get the server's reply. Since datagrams are unreliable, it is possible that a message sent from the client won't be received by the server, resulting in no reply! That means that the client might block forever waiting for a reply that isn't coming. Here is some pseudocode to deal with that possibility:
          sendto( ... )
          alarm(1)
          br = recvfrom( ... )
          if (-1 == br && EINTR == errno)
            server did not reply within one second
    
  5. Neither the client nor the server uses read() or write() for sending messages. Everything is done using sendto() and recvfrom().
Use the last four digits of your social security number as the well-known port number for your server. There will be no conflicts with other students using the lab this semester. However you have to have superuser privileges to use port numbers less than 1000, so the one person whose social security number ends with a number less than 1000 will have to add 1000 to it (or treat the last three digits as a hexadecimal value). If for some reason you do need more port numbers, there will be no conflicts if you use numbers between your assigned number and that number plus 60.

om for Use Case 1
The object manager accepts any number of command line arguments. If the first argument is numeric, it is an alternate value for the program's well-known port number. Other than the port number, all command line arguments are the names of classes to be managed by the program. For this use case, the class names may be totally arbitrary strings. Build a linked list of these class names.

Create a datagram socket and bind it to your assigned well-known port number. When a datagram arrives it will be a request formatted as a text string with spaces as delimiters between tokens. The first token in the datagram is a request type for selecting a function to call to process the request. Pass the remainder of the datagram as a single argument to the appropriate processing function, which is returns a character string. Send that string as a reply to the sender of the request. Use a dispatch table as described in class to match request types with processing functions. If a request has an unrecognized request type, send the string "*** unrecognized request ***" to the sender.

For this use case, om recognizes two request types, and processes them as follows:

Request
Type
Argument
List
Returns
listClassesAnythingThe names of all the classes being mangaged, separated by newlines.
exitAnythingNothing. Print the argument list, and exit om.
Write to stdout each datagram received and each reply sent.

ou for Use Case 1
The first command line argument is the name of the host where om is running. If the second command line argument is numeric, it is the port number to use instead of your well-known port number. One additional argument is a string. Send a datagram to the om server containing the command line string. Print the reply that is returned by om, or an appropriate message if no reply is received within a reasonable amount of time, and exit.

Documentation for Use Case 1
Write man pages for ou and om. Add the following rule to your Makefile:
      install_doc : ou.1 om.1
      <tab>cp -f ou.1 om.1 $(HOME)/man/man1
Finishing Use Case 1
Test both your programs carefully. Run ~vickery/bin/om_1 and be sure your ou works correctly with it. My default port number is 0x8000 (32768). Run your om and make sure it works correctly with ~vickery/bin/ou_1. Be sure your programs work correctly when run on different computers. Be sure you can run ou any number of times before telling om to exit. Be sure your om handles invalid requests properly. Be sure your ou gives an appropriate message if the host can't be located or if om doesn't respond. Make sure the first line of each source file, man page, and the Makefile is a comment with the $Id$ keyword in it. Make sure there is a comment line with the $Log$ keyword in it in each of these files.

Add the following rule to your Makefile:

      install : install_doc ou om
      <tab>cp -f ou om $(HOME)/bin
      <tab>chmod 755 $(HOME)/bin/ou $(HOME)/bin/om
Note: Your executable programs must be named ou and om. Your source files may be named whatever you like, but when you pick the names, remember that their names will not change for different use cases. I suggest that you use the names om.cc and ou.cc.

Be sure your source code follows the coding guidelines for this course before proceeding. You should have been formatting and documenting your code as you wrote it. Now is the time to proofread everything to make sure it looks right.

Check everything into rcs and be sure "make install" builds and installs everything correctly. Be sure all the rcs keywords are expanded correctly in all the source files. Revise source files, man pages, and the Makefile and check them in again as necessary.

Double check everything. It is difficult to get make and rcs to recreate a use case properly once you start working on the next one.


Christopher Vickery
Computer Science Department, Queens College of CUNY