A ClientServer Message Queue Example

Table of contents:

At this point we can use what we have learned about message queues to write a pair of programs that establish a clientserver relationship and use message queues for bidirectional interprocess communication. The client process obtains input from the keyboard and sends it via a message queue to the server. The server reads the message from the queue, manipulates the message by converting all alphabetic text in the message to uppercase, and places the message back in the queue for the client to read. By mutual agreement, the client process identifies messages designated for the server by placing the value 1 in the message type member of the message structure. [7] In addition, the client includes its process ID (PID) number in the message. The server uses the PID number of the client to identify messages it has processed and placed back in the queue. Labeling the processed messages in this manner allows the server to handle messages from multiple clients .

[7] This works nicely , as in multiple client situations, because not every client has initial access to the PID of the server.

For example, if the client process with a PID of 17 placed each word in the statement " The anticipation is greater than the realization. " into separate messages, the current state of the message queue would be as depicted in Figure 6.7. As shown, the messages placed in the queue by the client (PID 17) are labeled as a message type of 1 (for the server).

Figure 6.7. Conceptual view of message queue after the client has sent all seven messages

graphics/06fig07.gif

When the server reads the queue, it obtains the first message of type 1. In our example this is the message containing the word The . The server processes the message, changes the message type to that of the client, and puts the message back on the queue. This leaves the message queue in the state shown in Figure 6.8.

Figure 6.8. Conceptual view of message queue after the first client message has been processed.

graphics/06fig08.gif

To accomplish this task, both the client and server programs need to access common include files and data structures. These items are placed in a local header file called local.h , whose contents are shown in Figure 6.9. An examination of this file reveals that the messages placed in the queue consist of a structure with three members . The first member (which must be of type long if things are to work correctly) acts as the message type ( mtype ) member. Here we call this member msg_to , since it contains a value that indicates the process to whom we are addressing the message. We use the value of 1 to designate a message for the server process, and other positive PID values to indicate a message for a client. The second member of the message structure, called msg_fm (which is also a long integer), contains the ID of the process that is sending the message. In the program example, if the message is sent by a client, this value will be the client PID. If the message is sent by the server, this value will be set to 1. The third member of the message structure is an array of a fixed size that will contain the text of the actual message.

Figure 6.9 Local header file for message queue example.

File : local.h
 /*
 Common header file for 2nd Message Queue Example
 */
 #define _GNU_SOURCE
 + #include 
 #include 
 #include 
 #include 
 #include 
 10 #include 
 const char SEED ='M'; // Common seed for ftok
 const long int SERVER=1L; // Message type for server
 typedef struct {
 long int msg_to; // Message in queue for this type
 + long int msg_fm; // Placed in the queue by this type
 char buffer[BUFSIZ]; // The actual message
 }MESSAGE;
 using namespace std;

The client program, shown as Program 6.4, begins by obtaining its PID. This value is used later to mark messages sent to the server, identifying them as coming from a particular client process. The ftok library function is used to produce a key. When the client process is invoked, we want it to create the message queue if one does not already exist. Further, if the server process is not present, we want the client to start it. We will assume that if the message queue is not present, the server process is not present as well. To accomplish this the initial call to msgget , mid=msgget(key, 0) in line 19, is tested to determine if the call has failed. If the message queue is not found (the call fails), the message queue is created by the second call to msgget . If this occurs, the client process fork s a child process and overlays it with a call to exec to run the server process. The server is passed the message queue identifier via the command line. As all command-line arguments are strings, the sprintf string function is used to put the message queue identifier in the correct format.

Once the message queue is created, the client program enters an endless loop, prompting for user input, placing the input in the message queue for the server to process, retrieving the processed input, and displaying the results to standard output. If the user enters a message of 0 bytes (i.e., enters CTRL+D from the keyboard), the client exits its loop and sends the server a special 0-byte-length message (see line 47) indicating it is done.

Program 6.4 The client.

File : client.cxx
 /*
 CLIENT ... sends messages to the server
 */
 #include "local.h"
 + #include 
 using namespace std;
 int
 main( ){
 key_t key;
 10 pid_t cli_pid;
 int mid, n;
 MESSAGE msg;
 static char m_key[10];
 cli_pid = getpid( );
 + if ((key = ftok(".", SEED)) == -1) {

<-- 1

perror("Client: key generation");
 return 1;
 }
 if ((mid=msgget(key, 0 )) == -1 ) {

<-- 2

20 mid = msgget(key,IPC_CREAT 0660);
 switch (fork()) {
 case -1:
 perror("Client: fork");
 return 2;
 + case 0:
 sprintf(m_key, "%d", mid);

<-- 3

execlp("./server", "server", m_key, "&", 0);
 perror("Client: exec");
 return 3;
 30 }
 }
 while (1) {
 msg.msg_to = SERVER;
 msg.msg_fm = cli_pid;

<-- 4

+ write(fileno(stdout), "cmd> ", 6);
 memset(msg.buffer, 0x0, BUFSIZ);
 if ( (n=read(fileno(stdin), msg.buffer, BUFSIZ)) == 0 )
 break;
 n += sizeof(msg.msg_fm);

<-- 5

40 if (msgsnd(mid, &msg, n, 0) == -1 ) {
 perror("Client: msgsend");
 return 4;
 }
 if( (n=msgrcv(mid, &msg, BUFSIZ, cli_pid, 0)) != -1 )
 + write(fileno(stdout), msg.buffer, n);

<-- 6

}
 msgsnd(mid, &msg, 0, 0);
 return 0;
 }

(1) Generate a key for the message queue.

(2) If the message queue is not present, create the queue.

(3) Turn message queue ID into a string to pass to the server via the command line.

(4) Label message as to receiving and originating process.

(5) The message size is the size of the second member (the first is assumed) of the structure plus the number of bytes in the message.

(6) Server will pause, waiting for messages to be added to the message queue. Once a message is retrieved, it is written to standard output.

The server process (shown as Program 6.5) begins by checking the number of command-line arguments. If three command-line arguments are not found, an error message is generated and the server program exits. Otherwise, the contents of argv[1] are converted to an integer value to be used as the message queue identifier. The server then enters into a loop. It first attempts to receive a message of type SERVER (1) from the queue. If the number of bytes returned by msgrcv is 0, the server assumes that the client process is done. In this case, the loop is exited and the server removes the message queue with a msgctl system call (line 35) and exits. However, if a message is successfully retrieved from the message queue, it is processed (in the function process_msg ) and placed back on the queue so the client process can retrieve it.

Program 6.5 The server.

File : server.cxx
 /*
 SERVER-receives messages from clients
 */
 #include "local.h"
 + #include 
 #include 
 #include 
 #include 
 using namespace std;
 10 int
 main(int argc, char *argv[ ]) {
 int mid, n;
 MESSAGE msg;
 void process_msg(char *, int);

<-- 1

+ if (argc != 3) {
 cerr << "Usage: " << argv[0] << " msq_id &" << endl;
 return 1;
 }
 mid = atoi(argv[1]);
 20 while (1) {
 memset( msg.buffer, 0x0, BUFSIZ );

<-- 2

if ((n=msgrcv(mid, &msg, BUFSIZ, SERVER, 0)) == -1 ) {
 perror("Server: msgrcv");
 return 2;
 + } else if (n == 0) break;

<-- 3

process_msg(msg.buffer, strlen(msg.buffer));
 msg.msg_to = msg.msg_fm;
 msg.msg_fm = SERVER;
 n += sizeof(msg.msg_fm);
 30 if (msgsnd(mid, &msg, n, 0) == -1 ) {

<-- 4

perror("Server: msgsnd");
 return 3;
 }
 }
 + msgctl(mid, IPC_RMID, (struct msqid_ds *) 0 );

<-- 5

exit(0);
 }
 /*
 Convert lowercase alphabetics to uppercase
 40 */
 void
 process_msg(char *b, int len){
 for (int i = 0; i < len; ++i)
 if (isalpha(*(b + i)))
 + *(b + i) = toupper(*(b + i));
 }

(1) Check number of command-line arguments.

(2) Retrieve message from queue; wait if no messages are present.

(3) If a zero-length message, exit the loop.

(4) Reassign the to and from fields for the message. Process the message and put it back in the message queue.

(5) Remove the message queue.

Entering the name of the client program on the command line executes the program. The client creates the message queue and invokes the server process (which must reside locally). A prompt is placed on the screen, requesting input. Each time the user enters a string of characters and presses return, the client places the input in the message queue for processing. After the message has been processed, the client retrieves the message from the message queue and displays it to the screen. Entering CTRL+D from the keyboard terminates the client process. As implemented, multiple copies of the client process can run/communicate with the server at the same time. One way to try this is to open multiple windows and run multiple copies of the client. An alternate approach is to place the executable version of the client and server programs in /tmp (be sure to change the permissions so that all users have access to them). Then cd to /tmp and run the client program. Ask another user to do the same (again, remember this is all done on the same machine). Each of you should be able to run the client program and receive processing service. In either scenario, just one message queue will be generated.

EXERCISE

As written, the server program removes the message queue when any client sends a message of length 0. Modify the server program so that it only removes the message queue after all client processes are done with it. One approach might be for the server to keep track of the client processes using the message queue and exit only when the last one sends a message of length 0.

Programs and Processes

Processing Environment

Using Processes

Primitive Communications

Pipes

Message Queues

Semaphores

Shared Memory

Remote Procedure Calls

Sockets

Threads

Appendix A. Using Linux Manual Pages

Appendix B. UNIX Error Messages

Appendix C. RPC Syntax Diagrams

Appendix D. Profiling Programs



Interprocess Communication in Linux
Interprocess Communications in Linux: The Nooks and Crannies
ISBN: 0130460427
EAN: 2147483647
Year: 2001
Pages: 136

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net