STORE_MANAGER is the server and 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. It maintains the store table and its associated mutex locks. It satisfies user requests by creating for each request a thread to carry out that request. Threads will execute concurrently with each other and with the main server thread.
RAND_PROC is the client. It has the same structure as in Homework 4, that is, it has two threads, one interacting with the user and the other interacting with the server. The client program RAND_PROC is invoked after STORE_MANAGER displays the port number assigned to its socket. Each client program is invoked from the command line with arguments specifying the host name of the computer that STORE_MANAGER is running on, and the port number displayed by STORE_MANAGER. Each client program may be invoked on the host running STORE_MANAGER or, better, on a different computer.
Given this set up, the programs 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 program). Thus STORE_MANAGER may be run, say on snowhite server, by invoking
% store_manager
at the command line at snowhite server. Thereafter RAND_PROC may be run, say on thunder, by invoking
% rand_proc hostname portnumber
at the command line on nimbus, 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
are the arguments to the program in rand_proc.c. Other copies of
RAND_PROC
may be run on snowhite, thunder, 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.
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_PROC 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 program also creates a datagram socket and binds an
address to it, having the system select a port number for the socket.
STORE_MANAGER gets implicitly 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 (one of its threads)
sends its response to the socket
associated with that address. The client program 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
Following this scheme, RAND_PROC invokes 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 STORE_READ() and STORE_UPDATE() invoked by the client programs. 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.
The STORE_MANAGER has also to keep a count of the number of created threads that are currently active. We need to make sure that we never have more than two threads active. This is a job for a counting semaphore. Here is a possible implementation for such an object.
/* countmonitor.c -- It implements a monitor that protects a variable
* count initialized to N that should never be negative.
* It is essentially a counting semaphore.
*/
#include <pthread.h>
typedef struct {
int count;
pthread_mutex_t mutex;
pthread_cond_t condition;
} countMonitor;
/* Operations on countmonitor */
void initCountMonitor (countMonitor * p, int n ) {
if(pthread_mutex_init(&(p->mutex), NULL) != 0) {
perror("pthread_mutex_init");
exit(1);}
if(pthread_cond_init(&(p->condition), NULL)
!= 0) {
perror("pthread_cond_init");
exit(1);}
p->count = n;
}
void DownCountMonitor (countMonitor * p){
pthread_mutex_lock(&(p->mutex));
while (p->count <= 0)
pthread_cond_wait(&(p->condition), &(p->mutex));
(p->count)--;
pthread_mutex_unlock(&(p->mutex));
}
void UpCountMonitor (countMonitor * p){
pthread_mutex_lock(&(p->mutex));
(p->count)++;
pthread_cond_signal(&(p->condition));
pthread_mutex_unlock(&(p->mutex));
}
Another issue to be aware of is that threads when they exit they do not necessarily return the resources they are currently using. In order that they do so it is advised that once a thread is started, as its first action it marks itself as ready to give up resources upon termination. This is done with the command
pthread_detach(pthread_self());
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.