CIS307: Guidelines for the solution of Homework 6

Organization of the program

We now have three processes - STORE_MANAGER, RAND_PROC1, and RAND_PROC2. These processes are invoked manually from the command line on possibly different computers. RAND_PROC1 and RAND_PROC2 communicate with STORE_MANAGER through sockets. STORE_MANAGER maintains a table of ordered pairs in STORE, and processes serially (an unbounded number of) requests for read and update operations on STORE received through messages from RAND_PROC1 and RAND_PROC2, sending the response to each request in a message addressed to the requestor.

STORE_MANAGER is invoked first from the command line on a computer. It creates a datagram socket and binds it to a port chosen by the system. It then writes on the screen the port number associated with its socket. The client processes RAND_PROC1 and RAND_PROC2 are invoked after STORE_MANAGER displays the port number assigned to its socket. Each client process is invoked from the command line with arguments specifying the host name of the computer that STORE_MANAGER is running on, the port number displayed by STORE_MANAGER, the logical identifier for the client process and the seed for the random number generator. Each client process may be invoked on the host running STORE_MANAGER or on a different computer. RAND_PROC1 and RAND_PROC2 execute identical algorithms but use different seeds for the random number generator.

Given this set up, the program may be organized into two files - store_manager.c and rand_proc.c (additional header files may be used to group together data structures and operations needed in each process). Thus STORE_MANAGER may be run, say on snowhite server, by invoking

store_manager

at the command line at snowhite server. Thereafter RAND_PROC1 may be run, say on astro, by invoking

rand_proc hostname portnumber identifier seed

at the command line on astro, where

hostname : host name of the computer that STORE_MANAGER is running on, in this example, snowhite.cis.temple.edu, portnumber : the port number displayed on the screen by STORE_MANAGER, identifier : the logical identifier for the client process, 1 for RAND_PROC1, and 2 for RAND_PROC2, and seed : seed for the random number generator

are the arguments to the program in rand_proc.c. Likewise RAND_PROC2 may be run on snowhite, astro, or any other machine of your choice by invoking rand_proc as shown above with the appropriate arguments from the command line on that computer. The files store_manager and rand_proc contain the executable code for store_manager.c and rand_proc.c respectively.

Structure of STORE

STORE_MANAGER maintains a table of pairs (SSTORE_ID, SSTORE_ELEM) in STORE, where SSTORE_ID and SSTORE_ELEM are character strings. In order to simplify the transmission of messages through sockets we will define SSTORE_ID and SSTORE_ELEM to be fixed-length strings. For uniformity we shall use the following definitions :

const STRING_LENGTH = 20; SSTORE_SIZE = 10; type STRING_TYPE = array [0..STRING_LENGTH - 1] of char; SSTORE_ENTRY_TYPE = record SSTORE_ID : STRING_TYPE; SSTORE_ELEM : STRING_TYPE; end; SSTORE_TYPE = array [0..SSTORE_SIZE - 1] of SSTORE_ENTRY_TYPE; var STORE : SSTORE_TYPE;

Since STORE is maintained by STORE_MANAGER and RAND_PROC1 and RAND_PROC2 are not supposed to access it directly, these declarations should be local to STORE_MANAGER.

Initialization of STORE

STORE_MANAGER initializes STORE before it starts processing requests for read and update operations from RAND_PROC1 and RAND_PROC2. It assigns to the SSTORE_ID component of each element of STORE a unique value that is never modified by subsequent (read or update) operations. The values chosen are fixed-length strings (of STRING_LENGTH characters) and may represent internet host addresses such as "jedi.cis.temple.edu" (padded with enough spaces at the end to make up a total of STRING_LENGTH characters). This set of values should be made available to RAND_PROC1 and RAND_PROC2 (for example, through a header file specifying these values that is attached to rand_proc.c and store_manager.c through #include directives) to enable them to generate read and update requests that can be satisfied. The SSTORE_ELEM component of each element of STORE is assigned the same value - a fixed length string of 0's, consisting of STRING_LENGTH 0's.

Choice of arguments for SSTORE_READ() and SSTORE_UPDATE() operations

As stated above, STORE_MANAGER initializes STORE before it starts accepting requests for read and update operations. The SSTORE_ID component of each element is assigned a fixed value, and all the SSTORE_ELEM components are initialized with a fixed-length string of 0's.

Read and update operations requested by RAND_PROC1 and RAND_PROC2 specify a value for the SSTORE_ID component (through the parameter WHO in the functions SSTORE_READ() and SSTORE_UPDATE()). If an entry with that value for the SSTORE_ID component is found in STORE, the operation is performed on the entry, otherwise a response indicating failure is generated. RAND_PROC1 and RAND_PROC2 select values for the parameter WHO randomly from a list maintained in each process. This list contains ALL values held in the SSTORE_ID components in STORE, and also an additional value that is not present in STORE. Thus the list contains SSTORE_SIZE + 1 values, and the value not contained in STORE is selected, on the average, once every (SSTORE_SIZE + 1) trials, causing the operations requested by the process to fail at the above rate.

Update operations specify (through the parameter WHAT) a value to be assigned to the SSTORE_ELEM component of a specified entry in STORE, obliterating the value currently held in it. We will stipulate that RAND_PROC1 would ALWAYS use a string of 1's (of length STRING_LENGTH) and RAND_PROC2, a string of 2's (also of length STRING_LENGTH), for the parameter WHAT when requesting an update operation. Recall that the SSTORE_ELEM component of each element of STORE is initialized with a string of 0's by STORE_MANAGER. Thus the SSTORE_ELEM component of an entry in STORE can hold one of three values :

This makes it easier to ascertain whether the read operations returned the values last written into the entries in STORE by scanning the logs generated by the processes. Correctness requires that STORE_MANAGER serialize the read and update operations and thus maintain the consistency of STORE.

Establishing sockets for communication

RAND_PROC1 and RAND_PROC2 use sockets to communicate with STORE_MANAGER. Each process creates a datagram socket in the Internet domain and binds an address to it to be able to receive messages through it. The address bound to a socket comprises an internet address (usually that of the host that the socket is created on) and a port number. A processs typically sends messages to a socket by specifying the address associated with the socket.

STORE_MANAGER first creates an Internet domain datagram socket. It then binds a local address to the socket, getting the system to pick a port number for it. It queries the system for the port number assigned to the socket and writes the port number on the screen. Recall that the host name of the computer that STORE_MANAGER is invoked on and the port number displayed by STORE_MANAGER are made available to RAND_PROC1 and RAND_PROC2 through command line arguments. This enables them to construct the address associated with the socket created by STORE_MANAGER. The following is an outline of the steps carried out by STORE_MANAGER in establishing a socket.

  Algorithm SetUpSocketInStoreManager
  begin

   1. Create a datagram socket by invoking socket() as 
        s := socket(AF_INET,SOCK_DGRAM,0);
   
   2. Bind local address to socket as follows :   
        { sm_socket_address is assumed to be declared as a variable of type 
          struct sockaddr_in }

        sm_socket_address.sin_family := AF_INET;
        sm_socket_address.sin_addr.s_addr := INADDR_ANY; 
        { The wildcard INADDR_ANY matches all valid network addresses of  
          the computer. }

        sm_socket_address.sin_port := 0;  { Let system pick port number } 
       
        Now invoke bind() as 
          bind(s,&sm_socket_address,sizeof(sm_socket_address));
   
   3. Find the port number assigned by the system by invoking 
      getsockname() as  
        getsockname(s,&sm_socket_address,&length_of_address);

   4. Print the host's representation of the port number, 
      ntohs(sm_socket_address.sin_port;

  end SetUpSocketInStoreManager
Each client process also creates a datagram socket and binds an address to it, having the system select a port number for the socket. STORE_MANAGER gets the address of the socket in a client when it receives a message (containing a request for a read or update operation) from the client, and sends its response to the socket associated with that address. The client process constructs the address of the socket in STORE_MANAGER from the host name and the port number arguments, and sends its messages requesting read and update operations to this address. Algorithm SetUpSocketInRandProc details the steps outlined above.

Algorithm SetUpSocketInRandProc begin 1. Create a datagram socket by invoking socket() as s := socket(AF_INET,SOCK_DGRAM,0); 2. Bind local address to socket as follows : { rp_socket_address is assumed to be declared as a variable of type struct sockaddr_in } rp_socket_address.sin_family := AF_INET; rp_socket_address.sin_addr.s_addr := INADDR_ANY; { The wildcard INADDR_ANY matches all valid network addresses of the computer. } rp_socket_address.sin_port := 0; { Let system pick port number } Now invoke bind() as bind(s,&rp_socket_address,sizeof(socket_address)); 4. Construct the address of the socket created by STORE_MANAGER by assigning the appropriate values to the components of sm_socket_address. sm_socket_address is assumed to be declared as a variable of type struct sockaddr_in. sm_socket_address.sin_family := AF_INET; Get the IP address of the computer running STORE_MANAGER from the the first command line argument : { assumes the declaration struct hostent *host_info; } host_info := gethostbyname(argv[1]); Assign the IP address by copying individual bytes, invoking bcopy as bcopy(host_info->h_addr,&sm_socket_address.sin_addr, host_info->h_length); Assign the network representation of the port number from the second command line argument : sm_socket_address.sin_port := htons(atoi(argv[2])); { Now sm_socket_address contains the address of the socket created by STORE_MANAGER. } end SetUpSocketInRandProc

Performing I/O through sockets

The client processes RAND_PROC1 and RAND_PROC2 construct the address of the socket created by STORE_MANAGER from the name of the machine STORE_MANAGER runs on and the port number associated with the socket they receive as arguments. They can then send messages to STORE_MANAGER by specifying this address for the destination. However, the addresses of the sockets created by the client processes are not made available to STORE_MANAGER explicitly. This is because STORE_MANAGER being a server does not INITIATE the exchange of messages with the clients but sends a message to a client in response to a request received from it, and with the proper choice of communication primitives the receiver of a message can obtain the address of the socket the message originated from.

Following this scheme, RAND_PROC1 and RAND_PROC2 invoke the function sendto() to send a message to STORE_MANAGER, and STORE_MANAGER invokes recvfrom() to receive messages from the clients. The function recvfrom() returns to STORE_MANAGER the address of the socket associated with the sender (through one of its parameters) and STORE_MANAGER sends its response to the request specifying this address for the destination. The functions sendto() and recvfrom() may be used in the transmission of messages from STORE_MANAGER to the clients as well.

The details of sending and receiving messages should be hidden in the functions SSTORE_READ() and SSTORE_UPDATE() invoked by the client processes. The intent is to avoid burdening the user with the details of the implementation and present an interface that specifies only the semantics of the operation.

Format of messages exchanged through sockets

STORE_MANAGER retrieves messages one at a time from its socket, carries out on STORE the operation requested in the message, and sends its response to the socket associated with the requester. Since the update operation requires one more operand than the read operation, the structure of a message requesting an update operation would be different from that of one asking for a read operation. Since datagram sockets preserve message boundaries in the data transmitted through them, STORE_MANAGER would be able to retrieve messages one at a time while reading from its socket. However, a message can be assigned to a variable of the appropriate type directly upon retrieval only if STORE_MANAGER can predict in advance the type of operation on STORE that would be specified in the message. Since these operations are chosen at random by RAND_PROC1 and RAND_PROC2, STORE_MANAGER would not be able to anticipate them, and would have to adopt a somewhat cumbersome procedure involving an examination of the length of the message before assigning the message to a variable of the appropriate type. We may alleviate the task by arranging to use messages of the same length (incurring some loss in effective bandwidth) while requesting both read and update operations. A message specifying a read operation is padded at the end with enough spaces to make it as long as one asking for an update operation before being sent, and STORE_MANAGER ignores the dummy characters while parsing the message. This makes for a simpler design, accompanied, however, by a slight reduction in communication bandwidth.

The above discussion applies to messages originating from STORE_MANAGER as well. Response messages for all cases except that of an invalid read operation have the same length, but the latter requires fewer characters. All cases may, however, be handled in a uniform manner by getting STORE_MANAGER to put in some padding in the messages containing the response to invalid read operations.

Format of entries in the log files

STORE_MANAGER, RAND_PROC1 and RAND_PROC2 maintain private log files in which they record the messages they receive and send. Since the log files are our primary source of information in determining whether the read and update operations on STORE are performed consistently, we must ensure that all the relevant information about each request and the corresponding response are recorded in the files.

Entries in the file store.log maintained by STORE_MANAGER summarize requests from the clients and the corresponding responses from STORE_MANAGER. Each entry recording a request identifies the client making the request (RAND_PROC1 or RAND_PROC2), the operation requested (read or update), the parameters of the operation, and a timestamp. Each entry recording the response to a request indicates the operation requested, the parameters of the operation, the status returned for the operation, and a timestamp. Each entry in the file may be written on a separate line for clarity.

The following are examples of the suggested format :

 
 
  1  R  jedi.cis.temple.edu   < timestamp >
     R  jedi.cis.temple.edu   00000000000000000000  OK   < timestamp >  
  2  U  jedi.cis.temple.edu   22222222222222222222  < timestamp >
     U  jedi.cis.temple.edu   22222222222222222222  OK   < timestamp >
  2  U  yoda.cis.temple.edu   22222222222222222222  < timestamp >
     U  yoda.cis.temple.edu   22222222222222222222  OK   < timestamp >
  1  R  yoda.cis.temple.edu   < timestamp >
     R  yoda.cis.temple.edu   22222222222222222222  OK   < timestamp >
  1  U  none.cis.temple.edu   11111111111111111111  < timestamp >
     U  none.cis.temple.edu   11111111111111111111  ERR  < timestamp >
  2  R  none.cis.temple.edu   < timestamp >
     R  none.cis.temple.edu   --------------------  ERR  < timestamp >
The last two operations fail because "none.cis.temple.edu" is not a valid SSTORE_ID value in STORE.

The client processes RAND_PROC1 and RAND_PROC2 log the messages they send and receive to the files rand_proc1.xxx and rand_proc2.xxx respectively, where the extension in the file name is the name of the computer the client is run on. The name of the machine that the client is invoked on may be passed as the fifth argument to the client process to generate the appropriate name for its log file. Entries in the files correspond to the requests sent and the responses received by the respective processes. Each entry recording a request identifies the operation requested (read or update), the parameters of the operation, and a timestamp. Each entry recording a response indicates the operation requested, the parameters of the operation, the status returned for the operation by STORE_MANAGER, and a timestamp. Each entry may be written on a separate line for clarity. The log generated by RAND_PROC1 corresponding to the sequence of requests from the example log of STORE_MANAGER shown above :

R jedi.cis.temple.edu < timestamp > R jedi.cis.temple.edu 00000000000000000000 OK < timestamp > R yoda.cis.temple.edu < timestamp > R yoda.cis.temple.edu 22222222222222222222 OK < timestamp > U none.cis.temple.edu 11111111111111111111 < timestamp > U none.cis.temple.edu 11111111111111111111 ERR < timestamp >

The log generated by RAND_PROC2 corresponding to the sequence of requests from the example log of STORE_MANAGER shown above :

U jedi.cis.temple.edu 22222222222222222222 < timestamp > U jedi.cis.temple.edu 22222222222222222222 OK < timestamp > U yoda.cis.temple.edu 22222222222222222222 < timestamp > U yoda.cis.temple.edu 22222222222222222222 OK < timestamp > R none.cis.temple.edu < timestamp > R none.cis.temple.edu -------------------- ERR < timestamp >

Graceful termination

STORE_MANAGER, RAND_PROC1, and RAND_PROC2 are invoked from the command line on possibly different computers. They may be run as background processes and terminated manually by invoking the command kill from the shell and sending the SIGTERM signal (value 15) to each process. Each process catches the signal, closes its socket and its log file, displays a message informing the user of the receipt of the signal and terminates itself invoking exit(). Since the signal catcher is not allowed to take any user defined arguments, the descriptors for the socket and the log file should be made global to it. The processes should also arrange to catch the SIGHUP signal (value 1) that may be delivered to them when a terminal session is disconnected. The actions involved in responding to this signal are the same as those described earlier for handling the SIGTERM signal.

amandala@astro.ocis.temple.edu