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
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.
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.
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