3/18/22, 8:01 PM CMPUT379 – Message Passing
CMPUT 379: Experiments with sending and
receiving formatted messages
1. Introduction
Copyright By PowCoder代写 加微信 powcoder
Exchanging messages is a fundamental task in the operation of any
client-server or peer-to-peer application. The application may use
any form of IPC (inter-process communication) to pass the messages
(e.g., pipes, FIFOs, UDP sockets, TCP sockets, etc.).
Typically, there is a handful of message types that need to be
exchanged. Each type has a name that reflects either the carried
data, or the purpose of messages of this type. The collection of such
message types, and the way they are being used by the application
form an application layer protocol.
To enable the exchange of different types of messages, the developer
may be able to choose between encoding a message’s content as
variable length text lines where each line is terminated with a
newline character, or fixed length binary messages that may carry
text strings and binary data.
Each method has its pros and cons. Among the disadvantages of
converting a message that carries many numeric data fields to a text
string is the effort required to encode and decode such messages, and
the possible loss of accuracy when converting real numbers to
strings. In such cases, a better approach is to preserve the original
structure of the message by sending and receiving it in a binary
form. This lab activity develops this direction further.
2. A Running Example
We use, as a running example, the development of a client-server
application that relies on exchanging 5 types of messages that have
the following names and roles:
STR: each message of this type carries 3 strings
INT: each message of this type carries 3 integers
FLOAT: each message of this type carries 3 floating numbers
ACK: a server ACKs every received message from a client
DONE: a client sends to the server a DONE message before exiting
3. Messages and Frames
It is convenient to carry each message within a frame that has extra information. In our design here, each frame is a pair: [kind, msg] where
kind is an integer that indicates the carried message type, and msg is a struct or union that carries the message’s data. Recall that in the ANSI standard, a struct (or union) can be assigned to, passed to a function, and returned by a function.
The following is a possible program fragment that implements the above ideas. The fragment defines MSG as a union to save space since each message stores either strings, integers, or floats.
webdocs.cs.ualberta.ca/~cmput379/W22/379only/lab-messages.html
3/18/22, 8:01 PM CMPUT379 – Message Passing
#include …
#define MAXLINE
#define MAXWORD
#define NF 3
// stdio.h, stdlib.h, unistd.h, stdarg.h, string.h,
// assert.h, sys/types.h, sys/stat.h, fcntl.h
// number of fields in each message
#define MSG_KINDS 5
typedef enum { STR, INT, FLOAT, DONE, ACK } KIND; // Message kinds
char KINDNAME[][MAXWORD]= { “STR”, “INT”, “FLOAT”, “DONE”, “ACK” };
typedef struct { char d[NF][MAXLINE]; } MSG_STR;
typedef struct { int d[NF]; } MSG_INT;
typedef struct { float d[NF]; } MSG_FLOAT;
typedef union { MSG_STR mStr; MSG_INT mInt; MSG_FLOAT mFloat; } MSG;
typedef struct { KIND kind; MSG msg; } FRAME;
4. Error Handling
We’ll use the WARNING and FATAL functions, due to the authors of the
AWK Programming Language, for reporting warnings and errors.
// ——————————
// The WARNING and FATAL functions are due to the authors of
// the AWK Programming Language.
void FATAL (const char *fmt, … )
va_list ap;
fflush (stdout);
va_start (ap, fmt); vfprintf (stderr, fmt, ap); va_end(ap);
fflush (NULL);
void WARNING (const char *fmt, … )
va_list ap;
fflush (stdout);
va_start (ap, fmt); vfprintf (stderr, fmt, ap); va_end(ap);
// ——————————
5. Sending Frames
It is convenient to be able to send a frame using a simple sequence
msg= composeMessage ( … some data …);
sendFrame (destination, message kind, message’s content);
In our application, however, we need to handle compositions of three
different types of messages. This can be done using three functions,
webdocs.cs.ualberta.ca/~cmput379/W22/379only/lab-messages.html
3/18/22, 8:01 PM CMPUT379 – Message Passing
// ——————————
MSG composeMSTR (const char *a, const char *b, const char *c)
memset( (char *) &msg, 0, sizeof(msg) );
strcpy(msg.mStr.d[0],a);
strcpy(msg.mStr.d[1],b);
strcpy(msg.mStr.d[2],c);
return msg; }
// ——————————
MSG composeMINT (int a, int b, int c)
memset( (char *) &msg, 0, sizeof(msg) );
msg.mInt.d[0]= a; msg.mInt.d[1]= b; msg.mInt.d[2]= c;
return msg;
// ——————————
MSG composeMFLOAT (float a, float b, float c)
memset( (char *) &msg, 0, sizeof(msg) );
msg.mFloat.d[0]= a; msg.mFloat.d[1]= b; msg.mFloat.d[2]= c;
return msg;
// ——————————
Now, sending a frame over a channel connected to a file descriptor fd can be written as:
void sendFrame (int fd, KIND kind, MSG *msg)
FRAME frame;
assert (fd >= 0);
memset( (char *) &frame, 0, sizeof(frame) );
frame.kind= kind;
frame.msg= *msg;
write (fd, (char *) &frame, sizeof(frame));
6. Receiving Frames
Likewise, it is also convenient to be able to receive a frame from a file descriptor fd by calling a suitable function, e.g.,
FRAME rcvFrame (int fd)
int len;
FRAME frame;
assert (fd >= 0);
memset( (char *) &frame, 0, sizeof(frame) );
len= read (fd, (char *) &frame, sizeof(frame));
if (len != sizeof(frame))
WARNING (“Received frame has length= %d (expected= %d)\n”,
webdocs.cs.ualberta.ca/~cmput379/W22/379only/lab-messages.html
3/18/22, 8:01 PM CMPUT379 – Message Passing
len, sizeof(frame));
return frame;
7. Printing Frames
Printing incoming and outgoing frames occurs extensively in client-
server and peer-to-peer applications either because we want to
monitor the progress of each process, or debug the programs during
development or testing. Thus, it saves lots of time and effort to
carefully design a single function that can print any frame type used
in an application.
// ——————————
void printFrame (const char *prefix, FRAME *frame)
MSG msg= frame->msg;
printf (“%s [%s] “, prefix, KINDNAME[frame->kind]);
switch (frame->kind) {
printf (“‘%s’ ‘%s’ ‘%s'”,
msg.mStr.d[0], msg.mStr.d[1], msg.mStr.d[2]);
printf (“%d, %d, %d”,
msg.mInt.d[0], msg.mInt.d[1], msg.mInt.d[2]);
case FLOAT:
printf (“%f, %f, %f”,
msg.mFloat.d[0], msg.mFloat.d[1], msg.mFloat.d[2]);
case ACK: case DONE:
WARNING (“Unknown frame type (%d)\n”, frame->kind);
printf(“\n”);
// ——————————
8. The Client Loop
To test the above framework, one can write a client that sends three different message types, and waits for receiving an ACK before sending a new message. This can be done as follows:
void do_client (int fifoCS, int fifoSC)
FRAME frame;
MSG msg;
msg= composeMSTR (“Edmonton”, “Red Deer”, “Calgary”);
sendFrame (fifoCS, STR, &msg);
frame= rcvFrame(fifoSC); printFrame(“received “, &frame);
msg= composeMINT (10, 20, 30);
sendFrame (fifoCS, INT, &msg);
frame= rcvFrame(fifoSC); printFrame(“received “, &frame);
webdocs.cs.ualberta.ca/~cmput379/W22/379only/lab-messages.html
3/18/22, 8:01 PM CMPUT379 – Message Passing
msg= composeMFLOAT (10.25, 20.50, 30.75);
sendFrame (fifoCS, FLOAT, &msg);
frame= rcvFrame(fifoSC); printFrame(“received “, &frame);
msg= composeMINT(0,0,0);
sendFrame (fifoCS, DONE, &msg);
In the above code, note the line msg= composeMINT(0,0,0) where we use one of the compose functions to (partially) clear a msg union.
9. The Server Loop
A matching server to the above client, may be implemented as follows:
void do_server (int fifoCS, int fifoSC)
MSG msg;
FRAME frame;
while(1) {
frame= rcvFrame(fifoCS);
printFrame (“received “, &frame);
if (frame.kind == DONE) {printf (“Done\n”); return;}
sendFrame(fifoSC, ACK, &msg);
10. Function Main
The main function executes either the server code, or the client code
depending on the command line arguments. Two FIFOs are used for
client to server, and server to client communications.
int main (int argc, char *argv[])
int fifoCS, fifoSC;
if (argc != 2) { printf(“Usage: %s [-c|-s]\n”, argv[0]); exit(0); }
if ( (fifoCS= open(“fifo-cs”, O_RDWR)) < 0)
FATAL ("%s: open '%s' failed \n", argv[0], "fifo-cs");
if ( (fifoSC= open("fifo-sc", O_RDWR)) < 0)
FATAL ("%s: open '%s' failed \n", argv[0], "fifo-sc");
if ( strstr(argv[1], "-c") != NULL) do_client(fifoCS, fifoSC);
if ( strstr(argv[1], "-s") != NULL) do_server(fifoCS, fifoSC);
close(fifoCS); close(fifoSC);
11. Suggested Activity
To test the above ideas use mkfifo to create two FIFOs: fifo-cs, and fifo-sc in a fresh directory.
webdocs.cs.ualberta.ca/~cmput379/W22/379only/lab-messages.html
3/18/22, 8:01 PM CMPUT379 - Message Passing
Assemble the above fragments in a program called, e.g. fifoMsg.c. Compile the program.
Open two terminal windows. Use one window to run the server using fifoMsg -s, and the other window to run the client using fifoMsg -c.
Extend your program by adding an array, say int pktStats[MSG_KINDS], to keep track of sent and received messages of each type.
You may also like to experiment with replacing low level I/O calls
(e.g., open, read, write, close) with stream I/O functions (fopen,
fread, fwrite, fclose, etc.).
12. Acknowledgment
If you find the above ideas useful, please mention this aspect in
your Assignment #2 report.
CMPUT 379: U. of Alberta, Author: E. Elmallah
webdocs.cs.ualberta.ca/~cmput379/W22/379only/lab-messages.html
程序代写 CS代考 加微信: powcoder QQ: 1823890830 Email: powcoder@163.com