This chapter covers the following topics:
MessageQ enables applications to exchange information in the form of messages using the following API functions:
Overview
MessageQ provides applications with three distinct ways to send and receive messages using:
This variety of methods for sending and receiving messages enables application developers to choose the type of messaging that best suits the application's present and future needs.
To send or receive messages, an application must be attached to at least one message queue on the message queuing bus. This queue serves as the application's primary queue---the main mailbox in which it receives information. To attach to a queue, the application must successfully execute the pams_attach_q function. Once attached, the application can send a message to a known target queue address using the pams_put_msg function.
MessageQ offers the following functions for receiving messages:
The Basics of Sending and Receiving Messages
When the application is finished sending or receiving messages, it detaches from the message queuing bus using the pams_detach_q function.
Refer to the programming examples distributed as part of the MessageQ media kit to view sample programs for each of these PAMS API functions.
Note:
If you are new to using MessageQ, you should begin by reading the Introduction to Message Queuing. This introduction explains the MessageQ concepts that you need to understand before you can successfully begin developing applications.
Sending and receiving information as message buffers is the easiest way to exchange information using MessageQ. A message buffer is a predefined, static data structure that is identified using a version number. So, for example, when a payroll system sends employee payroll information using version 1 of its payroll data structure, the receiving application can interpret each field of data in the buffer because it knows the definition of the version 1 payroll data structure.
Passing information using a static data structure in the form of a message buffer is the fastest way to exchange information between applications. Because the data structure definition is known to both the sending and receiving applications, no interpretation is required. Therefore, processing of information between both sender and receiver programs is faster.
However, message buffers limit the flexibility of applications to adapt to changing business conditions. To change the data structure, both the sender and receiver programs must be recoded to send and interpret the new message correctly. In addition, all production applications must be shut down and the newer versions started up for the change to take affect. Such large changes to an integrated application environment often result in synchronization problems where some applications have not yet been restarted using the new message format. This leads to processing errors until all applications are using the same version of the message data structure.
Another limitation in using message buffers is that data is passed "as is" from one system to another in the network. So, if a message must be delivered between two computers that use different byte orders, the application must perform the byte order translation to ensure that the data is interpreted properly by the target application. MessageQ does not perform data marshalling between systems with unlike hardware data formats when messages are sent using the message buffer approach.
See the following topics for information on how to:
Sending and Receiving Message Buffers
When programming MessageQ applications, there are four basic functions that are used in the sending messages. The first function called is pams_attach_q. This function is used to connect your MessageQ applications to the MessageQ message queuing bus. Attaching to the message queuing bus provides the application with a queue address for receiving the message and a means to share information with all other MessageQ applications.
The example in Listing 1-1 illustrates how to attach to a queue by name. The queue name must be defined appropriately in your group initialization file.
How to Send MessageQ Messages
Listing 1-1
Example of Attaching to a Queue by Name
#include <stdio.h>
#include <string.h>#include "p_entry.h"
#include "p_return.h"
#include "p_symbol.h" .
.
.
int32 attach_mode;
int32 dmq_status;
int32 q_name_len;
int32 q_type;
char q_name[12];
q_address my_primary_queue; strcpy(q_name,"example_q_1");
attach_mode = PSYM_ATTACH_BY_NAME;
q_type = PSYM_ATTACH_PQ;
q_name_len = (int32)strlen( q_name ); dmq_status = pams_attach_q(
&attach_mode,
&my_primary_queue,
&q_type,
q_name,
&q_name_len,
(int32 *) 0, /* Use default name space */
(int32 *) 0, /* No name space list len */
(int32 *) 0, /* Timeout */
(char *) 0, /* Reserved by MessageQ */
(char *) 0 ); /* Reserved by MessageQ */ if ( dmq_status == PAMS__SUCCESS )
printf( "Attached successfully to queue: \"%s\".\n", q_name );
else
printf( "Error attaching to queue: \"%s\"; status returned
is: %ld\n", q_name, dmq_status ); .
.
.
After attaching to a queue, the application uses the pams_put_msg function to send a message to the queue address of the receiver program. Before the message can be sent, the application needs to provide message data in a message buffer. The data structure of the message buffer is predefined so that both the sending and the receiving application can interpret the message contents.
The example in Listing 1-2 illustrates how to send a number of messages to a queue.
Listing 1-2 Example of Sending Messages to a Queue
int32 attach_mode;
int32 dmq_status;
int32 q_name_len;
int32 q_type;
int32 timeout;
short class;
short type;
short msg_size;
char delivery;
char priority;
char uma;
static char msg_area[18];
static char q_name[12];
q_address my_queue;
struct PSB put_psb;
.
.
.
/*
** Put a message into my own queue
*/
priority = 0;
class = 0;
type = 0;
delivery = PDEL_MODE_NN_MEM;
msg_size = (short) strlen( msg_area );
timeout = 50; /* 5 seconds */
uma = PDEL_UMA_DISCL;
dmq_status = pams_put_msg(
msg_area,
&priority,
&my_queue,
&class,
&type,
&delivery,
&msg_size,
&timeout,
&put_psb,
&uma,
(q_address *) 0,
(char *) 0,
(char *) 0,
(char *) 0 );
if ( dmq_status == PAMS__SUCCESS )
printf( "\n\tPut successfully to queue: \"%s\".\n", q_name );
else
printf( "\nError sending to queue: \"%s\"; status returned
is: %ld\n", q_name, dmq_status );
.
.
.
MessageQ applications use the pams_get_msg function to read messages from a queue. Because both sending and receiving programs use the predefined buffer structure, the receiving application can interpret the message.
When a MessageQ application is finished, the pams_detach_q is called to disconnect the message queuing bus.
Prior to MessageQ Version 4.0, the only way to send a message was to use the predefined message data structure which allowed message to be as large as 32 kilobytes. If either the sending or receiving data structure needed to change the message structure, both sending and receiving applications were programmed to use the new message structure. For this change to take effect, both sending and receiving programs needed to be reloaded.
MessageQ enables applications to send buffer-style and handle-style messages up to 4MB in size. For handle-style messaging no differences in approach are required to send small or large messages. However, use the following procedure when sending and receiving buffer-style messages larger than 32K
To send a large buffer-style message, applications use the pams_put_msg function. Most arguments to this call are specified in the same way for large and small messages. However, the following list describes the arguments that are specified differently for large messages:
These functions return the actual size of the message written to the message buffer in the large_size argument.
A sample program illustrating how to send a large message called x_putbig.c is contained in the programming examples directory of your media kit.
Applications use message handles to exchange information when there is a need to separate the message content from the MessageQ API functions. A message handle identifies the message buffer to process. Instead of passing a message buffer containing the message data, the application passes a message handle to the pams_put_msg and pams_get_msg functions identifying the message buffer to process.
To use message handles, the sender program begins by using the pams_create_handle function to create a message handle that refers to the message contents. Then, it uses the pams_insert_buffer function to insert the message content into the message handle. Alternatively, the sender program can use any of the pams_encode functions to build the message handle.
The message handle is used as an input argument to the pams_put_msg function and is received by the pams_get_msg function. The receiver program then queries the message handle to determine the message contents. The receiver program uses either the pams_extract_buffer or pams_decode functions to interpret the message contents.
The use of message handles can provide flexibility in application development and design because the message handle hides the message structure from the sender and receiver programs. Message handles are required when using self-describing messages, a new type of MessageQ messaging that enables developers to include encoded message contents with information that identifies the content and format of the information for use by the receiver program.
You can create a message handle using the pams_create_handle function. The format is as follows:
Using Message Handles
Creating a Message Handle
Listing 1-3
Example of Creating a Message Handle
/* Creates handle for the empty message. */
pams_handle handle;
pams_create_handle(handle, handle_type);
When sending or receiving message handles, your application needs a way to interpret the message. MessageQ provides a series of API functions for interpreting the message received using a message handle. Sending and receiving applications can insert and extract the handle or they can encode and decode the handle.
The functions designed to insert or extract the entire message structure are as follows:
These API functions work together as follows:
Other MessageQ functions that allow your application to systematically encode or decode parts of the message buffer are as follows:
These functions work as follows:
To delete the message handle, use the pams_delete_handle function. This function frees the resources used to create the handle and prevents memory leaks when too many handles are created.
The following code fragment shows the pams_delete_handle function.
Because message handles can use large amounts of memory, program your application to delete each message handle once your application is finished using it. See the Efficient Use of Message Handles topic for more information.
Following are some guidelines for efficient use of message handles:
Deleting the Message Handle
/* Release the handle and the associated message. */
pams_delete_handle(handle); Efficient Use of Message Handles
When an application sends an outgoing request handle and is expecting to receive a response, it is most efficient to reuse the request handle for the incoming response. In this case, design the application to create, encode, and send the request message and then get the incoming response once it is available.
The example in Listing 1-4 shows the same handle created to send a message can be reused to receive a response.
Reusing Outgoing Request Handle for Incoming Responses
Listing 1-4
Example of Reusing Message Handles
pams_create_handle(handle, NULL);
pams_encode/pams_insert_buffer;
pams_put_msg(handle,...);
pams_get_msgw(handle,...);
pams_decode/pams_extract_buffer;
pams_delete_handle(handle);
When an application needs to send several messages in a sequence, it is most efficient to reuse a single message handle to send all of the messages. In this case, design the application to create the handle prior to entering the loop for sending the messages and then delete the handle upon exiting the loop. The application can use the pams_set_msg_position function to reset the starting point after each message is sent.
The example in Listing 1-5 illustrates how to reuse a single message handle to send a series of messages:
Listing 1-5 Example of Reusing Message Handles to Send a Series of Messages
pams_create_handle(handle, NULL)
while (some_condition) {
pams_encode/pams_insert_buffer
pams_put_msg(handle,...)
pams_set_msg_position(handle,...)
}
pams_delete_handle(handle);
Note the message encoding is reset after every submission using the pams_set_msg_position function.
When an application receives several messages, it can reuse the message handle for each message. To reuse the handle, create the handle outside of a loop. Then, within the loop, reuse the message handle to send each message. When the application exits the loop, make sure it deletes the handle.
The example in Listing 1-6 shows a server receiving several messages. The message handle is created prior to entering the server loop and freed upon exiting the loop.
Listing 1-6 Example of Sending Messages that Use Message Handles
pams_create_handle(handle, NULL)
while (some_condition) {
pams_get_msg(handle,...)
pams_decode/pams_extract_buffer
}
pams_delete_handle(handle);
Note that pams_get_msg resets message decoding for each newly received message.
Applications can send the same message to multiple addresses. To do so, create and encode the message handle, then issue as many pams_put_msg calls as needed to send the messages.
The example in Listing 1-7 shows a client sending the same message to several addresses.
Listing 1-7 Example of a Client Receiving Multiple Messages Using Message Handles
pams_create_handle(handle, NULL);
pams_encode/pams_insert_buffer;
pams_put_msg(handle,...);
pams_put_msg(handle,...);
pams_put_msg(handle,...);
pams_delete_handle(handle);
Applications can reuse the message handle to forward the received message. First, the application receives and decodes the message to determine the whether the message content is to be forwarded. Then, the application forwards the message to the appropriate address.
The example in Listing 1-8 shows how an incoming message can be used in pams_put_msg to forward the received message.
Listing 1-8 Example of Reusing Message Handles to Forward a Message
pams_create_handle(handle, NULL);
pams_get_msg(handle,...);
pams_decode/pams_extract_buffer;
pams_put_msg(handle,...);
pams_delete_handle(handle);
If the received message is amended, you can use pams_set_msg_position on the received message handle (see the Reusing Handles After Augmenting a Sent Message topic).
Applications can reuse a handle after changing the contents of the message handle. Use this method when a change is needed to the message before your application resends it.
The example in Listing 1-9 shows an incoming message being reused or augmented and sent. Depending on the arguments provided to pams_set_msg_position, fields may be overwritten or be appended to the end of the message. If pams_set_msg_position is not used, pams_encode returns PAMS_ _BADSTATE.
Listing 1-9 Example of Reusing a Message Handle After Augmenting a Sent Message
pams_create_handle(handle, NULL);
pams_get_msg(handle,...);
pams_decode_(handle,...);
pams_set_msg_position(handle,...);
pams_encode_(handle,...);
pams_put_msg(handle,...);
pams_delete_handle(handle);
Self-describing messaging (SDM) is a new feature in MessageQ, Version 4.0. Using SDM, applications construct messages containing both the message content and the information needed by the receiver program to understand what is in the message. The receiver program dynamically interprets the contents of the message by "decoding" some or all of the data contained in it. Sender and receiver programs exchange SDM messages using message handles.
Using SDM, applications do not interact with a message structure. Instead, sender programs encode the contents of the message using the appropriate pams_encode function. Each field in the message has a value (the content) and a tag (identifier). When an application retrieves an SDM message, the content is not directly visible. The receiver program must use the pams_decode functions to interpret the contents of the message that are appropriate to its operation.
Because SDM messages contain information about how to interpret the message contents, self-describing messaging provides applications with more flexibility in adding fields to a message or changing the message contents without necessarily needing to recode all of the receiving applications. In addition, SDM performs data marshaling of data formats between computer systems with unlike hardware data formats.
SDM messages, which are accessed by a handle, contain tagged values that are encoded and decoded by specific PAMS API functions. When you code, you build the message using assignments inside the message data structure which you have defined. To encode and decode data, SDM uses the following message structure:
Figure 1-1 SDM Message Format
In the above figure, the message structure contains a tag and value. Each tag is associated with a value and the tag identifies the data type such as integer, string, and float. The value part of the message identifies information such as the field-id, queue address, time, and so on.
There are several advantages to using SDM. These advantages are as follows:
SDM manages data transformation so that an SDM message can be interpreted properly on any platform. Figure 1-1 illustrates how the pams_encode function creates a formatted SDM message that replaces all platform-dependent compiler assignments through an API, which has decoupled and hidden all the machine, operating system, and platform dependencies. It has also properly encoded the message so that it can be safely transported from platform to platform in a heterogeneous manner. Furthermore, it protects you from message structure changes.
For example, suppose you have an application running on an Hewlett-Packard machine and the message data is little endian. When messages are sent to a Digital machine, a conversion from little endian to big endian must take place. This is handled by encoding the little endian format and converting it to a platform independent format. Then, the platform independent format is decoded into the big endian format for the Digital machine.
One performance consideration in using SDM is that it uses a larger message size, which can take significantly longer to pass back and forth between machines. The message size is larger because the message contains both the information and a description of the information, encoded in a platform-independent manner.
For example, consider a message that is 100 Booleans. With a defined message buffer, the message is only 100 bytes using a C message structure. With SDM, the message size is 800 bytes. Each of the original 100 bytes requires 1 byte of data and 4 bytes of metadata. Because each byte of data must be aligned on word boundaries for platform independence, each byte requires three additional padding bytes.
A more efficient way to encode message data is to use an array. You can encode the 100 bytes as an array of 100 bytes. With an array, the padding necessary to accomplish word alignment is not needed and the tag is present only once. Using this approach, the actual size needed is 104 bytes.
You may be able to work around the larger message size disadvantage by sending the larger SDM message only when needed and a message buffer at other times. For more information on this technique, see the Designing Applications to Use a Mixed Messaging Environment topic.
A mixed messaging environment is an environment where you want to exchange buffer messages with SDM messages to the same application. If you are programming an application to use both kinds of messages, We recommends that your application uses two queues--one queue for buffer-style messages and another queue for SDM messages. By designing your application this way, you guarantee that your application does not dequeue an SDM message by mistake.
Note that for performance reasons, it might be better to modify the buffer structure and redistribute all software than to use a mixed messaging environment. This may be the recommended approach when your applications are close geographically and there is a convenient time to update software.
When sending SDM messages, you code in a similar manner as with a message buffer. However, the main difference is that messages are manipulated using message handles rather than using the actual message buffer. The message handle is provided to pams_put_msg as the first argument. To code an SDM message, you must add the following steps to your program logic after attaching to a queue:
Performance Considerations When Using SDM
Designing Applications to Use a Mixed Messaging Environment
How to Send an SDM Message
A sample program called x_sdm.c which illustrates how to send and receive SDM messages is distributed as part of your media kit.
SDM message fields are tagged. Each tag implicitly defines the data type of the information it is associated with. This guarantees that the sender and the receiver of a SDM message have an explicit agreement about the kind of information they exchange. The collection of tags builds a kind of message dictionary.
To define tags, developers must combine a data type symbol and a tag number qualifying the piece of information. The tag definitions have the following format:
#define tag_name (psdm_data_type | tag_number);
For example, if an account number is a 32-bit signed integer, the tag might be defined as follows:
#define TAG_ACCT_NUM (PSDM_INT32 | 123);
Table 1-1 describes the tag data type symbols:
Note: Tag numbers must be in the range 1 to 16773120 (0xFF0000). The range 0xFF0001 to 0xFFFFFF is reserved for MessageQ usage.
To guarantee that both the sending and receiving applications have the same tag definitions, you can place these definitions in an include file. For example:
Listing 1-10 Tag Definitions for Include File
#define BADGE_NUMBER (PSDM_INT32 | 1 );
#define NAME (PSDM_STRING | 2 );
#define ADDRESS (PSDM_STRING | 3 );
#define HOURLY_WAGE (PSDM_FLOAT | 4 );
Note that the tag_number must be uniquely defined in the include file. These numbers identify a specific tag for your application. Otherwise, if the tag_number is not uniquely defined, a tag collision can occur.
The PAMS API provides functions to encode tagged values in a SDM message accessed with its handle. A variety of encoding functions is provided to support a large number of different data types (for instance, pams_encode_int32 or pams_encode_string).
Sometimes a message needs to contain multiple instances of a tagged value. For example, someone could ask a banking application server to return all accounts for a given customer. To encode such a list, encode each value as usual. Then, encode the tag PSDM_NULL_TAG with a null value. Alternatively, you can use an array tag modifier.
The example in Listing 1-11 shows a program which builds a message with the queue id and time stamp. The message is then put into a message queue.
Listing 1-11 Example of Using the pams_encode Function
/* Include the tag definitions that both sending and */
/* receiving application will use. */
#include "tags.h";
/* Create an empty message handle */
pams_handle handle;
pams_create_handle (handle, NULL);
/* Encode the message into the handle */
pams_encode_int32 (handle, badge_number, 12345);
pams_encode_string (handle, name, "Sue Jones" 9);
pams_encode_string (handle, address "123 Main St, Boston, MA" 23);
pams_encode_float (handle, hourly_wage, 30.00);
/* Next, put the message to the target queue using */
/* the pams_put_msg function. */
Note that in the above example, the message buffer is opaque. That is to say that your application does not know nor need to know how any data are structured in the message. The PAMS API functions handle this for your application.
Your application can use any of the following functions when sending messages:
After creating the handle and building the message, you can send the message to the target queue. To use a handle when sending a message, the sender program specifies the symbol PSYM_MSG_HANDLE as the msg_size argument to the pams_put_msg function.
The code fragment example in Listing 1-12 sends the SDM message. The previously encoded message is contained in the handle argument.
Listing 1-12 Example of Sending an SDM message
/* Sends the message identified by the handle. The symbol */
/* PSYM_MSG_HANDLE_ in the msg_size argument indicates that */
/* the message is a handle and not a message buffer. */
/* Define any variables needed to the put function here. */
msg_size = PSYM_MSG_HANDLE
.
.
.
dmq_status = pams_put_msg(
handle,
&priority,
&my_queue,
&class,
&type,
&delivery,
&msg_size,
&timeout,
&put_psb,
&uma,
(q_address *) 0,
(char *) 0,
(char *) 0,
(char *) 0, );
If ( dmq_status == PAMS__SUCCESS )
printf ( "Message handle successfully put to the queue");
else
printf ( "Error putting message to queue");
.
.
.
When receiving SDM messages, you code in a similar manner as with a buffer-style message. However, you must add the following steps to your program logic after attaching to a queue:
To read a message from a queue, use the pams_get_msg function after you have included the tag definitions and created a message handle. The code fragment example in Listing 1-13 creates a message handle and gets the message:
Listing 1-13 Example of Reading an SDM Message
/* Include the predefined tag definitions */
#include "tag.h";
/* Read the message identified by the handle. The symbol */
/* PSYM_MSG_HANDLE in the len_data argument indicates that */
/* the message is a handle and not a message buffer. */
/* Define any variables needed for the get function here. */
len_data = PSYM_MSG_HANDLE;
.
.
.
dmq_status = pams_get_msg(
handle,
&priority,
&msg_source,
&class,
&type,
&msg_area_len,
&msg_len,
(int32 *)_sel_filter,
(struct PSB *) 0,
(struct show_buffer *) 0,
(char *) 0,
(char *) 0,
(char *) 0, );
If ( dmq_status == PAMS__SUCCESS )
printf ( "Message handle successfully read");
printf ( "Error reading message");
.
.
.
Your application can use the following API functions to manipulate handles and encode the message.
After your application creates a message handle and gets the message, it can interpret the message. Your application can use one of two SDM decoding styles to interpret the message. The first style is used when the message has variable content. The second style is when the message has specific arguments.
The code fragment example in Listing 1-14 shows the first style of decoding a message.
Listing 1-14 Example of Decoding an SDM Message
/* Start a pams_next_msg_field loop:
pams_next_msg_field(handle, &tag, &len)
switch (tag) {
TAGn:
/* If needed, ensure bufptr is large enough for len
pams_decode_xxx(handle, tag, bufptr, ...)
/* Insert your code for process TAGn
break;
}
The following code fragment shows the second style of decoding a message.
/* Specific pams_decode_xxx functions: */
/* One or more pams_decode_xxx() functions for specific tags */
/* Any specific processing for each tag */
Sometimes a message needs to contain multiple instances of a tagged value. For example, someone could ask a banking application server to return all accounts for a given customer. The encoded message contains information about each customer account. At the end of the message is a PSDM_NULL_TAG. To decode such a list, your application must perform a pams_next_msg_field loop and keep processing values until the current tag is PSDM_NULL_TAG.
A program might process different requests in different routines, but there may be a centralized receiving routine. If that routine handled the whole interaction, it would look something like this code example in Listing 1-15:
Listing 1-15 Example of a Server Receiving SDM Messages
#include "p_entry.h";
#include "tags.h";
.
.
.
pams_handle handle;
pams_attach_q();
/* One or more sequences of the form: */
pams_create_handle(handle, NULL);
pams_get_msg(handle,...);
/* or pams_get_msgw(handle,...); */
/* One or more pams_next_msg_field(); and/or pams_decode() */
/* Process the request */
/* Execute the service */
pams_set_msg_position(handle,...);
/* and one or more pams_encode_xxx(handle,...); to encode fields */
pams_put_msg(handle,...);
pams_delete_handle(handle);
.
.
.
pams_detach_q();
If the server wishes to use a global message handle, the pams_create_handle function can be called at the beginning of the program and pams_create_handle can be called at the end of the program.
If incoming message decoding and outgoing message encoding do not need to be executed in parallel, a single message handle can be used and pams_set_msg_position is called once the decoding is finished as shown here; alternatively, separate message handles can be used for incoming and outgoing messages.
A program might send different requests from different routines. A routine that handles a whole interaction with SDM messages would look something like the code example in Listing 1-16:
Listing 1-16 Example of a Client Sending SDM Messages
#include "p_entry.h";
#include "tags.h";
.
.
.
pams_attach_q();
.
.
.
pams_handle handle;
pams_create_handle(handle,NULL);
/* One or more pams_encode calls to place fields in the message and, */
/* if necessary, pams_locate_q(); */
pams_put_msg(handle,...);
pams_get_msgw(handle,...);
/* One or more pams_next_msg_field and/or pams_decode calls; */
pams_delete_handle(handle);
.
.
.
pams_detach_q()
If the client wishes to use a global message handle, pams_create_handle can be called at the beginning of the program and pams_create_handle can be called at the end of the program.
On occasion, it may be desirable to use message handles concurrently across threads. If this is the case, you will need to program the application with the proper thread synchronization mechanisms to allow the concurrent access to the message handle across threads. Otherwise, MessageQ may prevent your application from using concurrent threads or you could get unpredictable results.
For example, suppose your application uses a message handle that is encoded and decoded by several threads. Without the proper thread synchronization mechanisms to control access to message handles, your application could fail when trying to encode or decode a message placed there by another application. Or your application could pass the message handle and delete it before the other application is finished using the handle. In these situations, the thread synchronization mechanisms would place the proper locks on the message handle until all applications using the message handle were finished with it.