Message queuing is a powerful architectural device. Queuing promotes decoupling within a system by enabling components to asynchronously submit requests and receive responses. Queuing also enables scalability and load balancing by allowing multiple request processors, running on different computers, to receive and process messages from a single request queue. In this article, I’ll show you the rudiments of message queuing under .NET: how to create a message queue, how to send messages to it, and how to receive messages from it. I’ll also discuss journaling and acknowledgments.
Message queuing terminology
A message queue is simply a named, ordered repository of messages. A message consists of a header and an optional body. The header is a collection of properties that identifies the message and specifies the destination queue. Header properties may also specify options such as acknowledgment, expiration time, a label, or a response queue. The body is a simple sequence of bytes (up to 4 MB), the format of which must be agreed upon by sender and receiver.
There are several types of message queues. A public queue is published throughout the message queuing network, and potentially all users may send messages to it. A private queue is not published and is available only on the local computer that hosts it, and then only to applications that know the path to the queue. A journal queue stores copies of messages sent to, or removed from, a queue. (On a server, a journal queue is automatically created for each public or private queue. A single, machine-wide journal queue is created on client machines.) A dead-letter queue stores messages that are undeliverable, such as those that expire before an application receives them. The user creates public and private queues, while the system automatically creates journal and dead-letter queues.
Public and private queues can perform various roles. An administrative queue is a public or private queue designated to receive system-generated acknowledgment messages, either positive or negative, when messages are received by either a destination queue or an application. A response queue is a public or private queue designated as the destination for application-generated messages sent in response to a message.
You may refer to a queue in one of three ways: by path, by format name, or by label. A queue path has the basic structure MachineName\QueueName. The "." character may be used to refer to the local machine rather than specifying the machine name. Figure A shows the path structure for specific queue types.
A format name is a string that indicates whether the queue is public or private and specifies the GUID that uniquely identifies the queue, such as PUBLIC=b912a7ab-10d7-4b82-9e8b-341014eed01d. The system automatically assigns the format name for a queue when the queue is created. A queue must be referenced by its format name when a path cannot be resolved, such as when a client laptop machine is temporarily disconnected from the server that hosts the queue. This allows the message queuing system to store messages and forward them to the server when connectivity is re-established.
A queue may also have a user-defined label. A label, however, cannot be guaranteed to uniquely identify a queue.
.NET message queuing
The .NET framework supports message queuing under the System.Messaging namespace in the assembly System.Messaging.dll, primarily through the classes MessageQueue and Message. I’ll demonstrate these classes through a series of example programs.
MessageQueue provides static methods to create and delete queues as well as to test for the existence of a queue. A MessageQueue instance serves as an endpoint to communicate with a queue.
Example CreateQueues.cs, shown in Listing A, creates two queues on the local machine: the public queue DotNet and the private queue DotNetAdmin, which we’ll use for acknowledgments. The helper function CreateQueue tests for the existence of the queue specified by path and either creates a new queue or connects to an existing queue and returns a MessageQueue object reference. In Main, MessageQueue object references are declared within using statements that automatically call the object’s Dispose method at the end of the block. This ensures that resources are released as soon as the message queue objects are no longer needed. The path passed to CreateQueue in each using statement is prefixed by an at character (@) to declare a verbatim string literal in which characters preceded by a backslash are not treated as escape sequences. Within each using block, the message queue is assigned a label. Journaling is enabled for the DotNet queue.
Note the two different contexts in which the using keyword is used. The using directive near the top of the file (e.g., using System.Messaging;) imports types from another namespace into the current namespace. The using statements in Main acquire and then dispose of resources.
Example SendMessage.cs, shown in Listing B, connects to the DotNet queue and sends a message. The message is labeled DotNetMsg, and the body is a string with the current date and time. The message designates DotNetAdmin as its administration queue and specifies that an acknowledgment message be delivered when an application receives the message. The full set of acknowledgment conditions is defined in the AcknowledgeTypes enumeration. Multiple enumeration values can be OR’ed together to generate a sequence of acknowledgments as a message travels from sender to receiver. The message is sent to the DotNet queue with the MessageQueue Send method. A message is automatically assigned a unique identifier as it is sent to a queue. This identifier is available through the read-only Id property.
By default, a message body is formatted as XML by the XmlMessageFormatter. The ActiveXMessageFormatter can be specified instead through the Message.Formatter property for compatibility with the MSMQ ActiveX component. Under Windows 2000, the message body XML can be viewed in the Computer Management application, as shown in Figure B.
|Computer Management showing message body|
Example ReceiveMessage.cs, shown in Listing C, receives from the DotNet queue the message that was sent in SendMessage.cs. The call to MessageQueue.Receive does not return to the caller until a message is available. Overloaded forms of Receive allow a timeout to be specified. Receiving the message removes it from the DotNet queue, places a copy in the DotNet\Journal$ queue and sends an acknowledgment to the DotNetAdmin queue.
The example peeks at the journal and acknowledgment messages using the MessageQueue PeekById and PeekByCorrelationId methods. These calls throw an exception immediately if a matching message does not exist in the queue. Again, overloaded forms accept a timeout value. The correlation ID in an acknowledgment specifies the ID of the original message to which the acknowledgment applies. Peeking returns a copy of the message but, unlike receiving, leaves the message in the queue. To receive or peek, the message queue’s XML message formatter must be told what types to deserialize from the message body. The XML message formatters in the example are configured to deserialize only the string type. An acknowledgment message has no body. The read-only Acknowledgement property indicates the status of the original message.
Variations.NET message queuing also provides functionality to process messages asynchronously and to perform queuing under transaction control. These operations are variations and extensions of the basic operations that have been demonstrated here.