Tuesday, March 27, 2007

Use Microsoft Message Queuing in C# for inter-process communication

Takeaway: Zach Smith demonstrates how to use Microsoft Message Queuing (MSMQ) for inter-process communication between applications on the same machine or over the network.

This article is also available as a TechRepublic download, which includes a project file containing all the sample code you need to read or write messages from a queue.

As the name implies, Microsoft Message Queuing (MSMQ) is a way to send messages to a queue for later processing. The messages are sent to the queue by a "Producer" application and retrieved from the queue by a "Consumer" application. These two applications can be on the same machine, across a network, or even on different machines that aren't always connected. MSMQ is considered failsafe in that it will retry sending a message if the first transmission fails. This allows you to be very confident that your application messages will arrive at their destination.

I will be working with a queue called "TechRepublic". When you run the sample application included with the download version of this article, this queue will be automatically created if it doesn't currently exist.

In a previous article, Zach Smith demonstrated how to use IPC channels to communicate between two processes running on the same machine. In this article, he demonstrates how to use Microsoft Message Queuing (MSMQ) for inter-process communication between applications on the same machine or over the network.

Accessing MSMQ

Accessing a queue via .NET is done with the System.Messaging.MessageQueue object. Listing A demonstrates how to access the TechRepublic queue on a computer named "SRV-MESSAGING":

Listing A

MessageQueue queue =

new MessageQueue("SRV-MESSAGING\TechRepublic");

Note: You'll have to add a reference to System.Messaging in your project to use this object.

We now have a MessageQueue object instantiated as a local variable "queue". The MessageQueue object provides you with all of the functionality you will need to interact with the queue.

If the queue does not exist, you can programmatically create the queue by calling the static Create method of the MessageQueue object. The code shown in Listing B demonstrates how to check for the existence of a queue and create the queue or instantiate a reference to the queue:

Listing B

MessageQueue queue = null;

string queueName = "SRV-MESSAGING\TechRepublic";



if (MessageQueue.Exists(queueName))

queue = newMessageQueue(queueName);

else

queue = MessageQueue.Create(queueName, false);

Writing to queues

When writing to the queue, you will use the MessageQueue.Send method. A simple example of sending a message to the TechRepublic queue is shown in Listing C.

Listing C

queue.Send("My message body", "Message Label");

In this case, a message with the body "My message body" is sent to the TechRepublic queue, and has a label of "Message Label" applied to it. Message labels allow you to partition the messages without reading the body of the message. The labels are also visible in the "Queue Messages" section of a message queue when viewed from the Computer Management console.

Reading from queues

There are several ways to read messages from a queue. One of the more common scenarios is to grab all messages from the queue and process them one at a time. This is accomplished by calling the MessageQueue.GetAllMessages method. An example of this method is shown Listing D.

Listing D

System.Messaging.Message[] messages = queue.GetAllMessages();



foreach (System.Messaging.Message message in messages)

{

//Do something with the message.

}

An alternative to this is to use the GetMessageEnumerator2 method. While these two methods work similarly, the GetMessageEnumerator2 method is forward-only. For very large queues, this method should be used instead of the GetAllMessages method. The reason for this is that the GetAllMessages method pulls down all messages into local memory. The GetMessageEnumerator2 method pulls only the current message down locally and pulls the next message down only after MoveNext is called. An example of GetMessageEnumerator2 is shown in Listing E. This code goes through each message in the queue and removes it.

Listing E

MessageEnumerator enumerator = queue.GetMessageEnumerator2();



while (enumerator.MoveNext())

enumerator.RemoveCurrent();

Another point to consider when using GetMessageEnumerator2 is that you will have access to any new messages added to the queue, even if they were added after you called GetMessageEnumerator2. This is assuming the new messages were added to the end of the queue.

If you want to retrieve only the first message in a queue, you should use the MessageQueue.Receive method. This method will pull down the first message in the queue, and in the process, remove it from the queue. Since the message is removed when it is read, you are assured that your process is the only one to receive the message. An example of the Receive method is shown Listing F.

Listing F

System.Messaging.Message message = queue.Receive();

An alternative to the Receive method is the Peek method. This method pulls down the first message in the queue like Receive; however, it leaves a copy of the message on the queue. This allows you to examine the message before removing it from the queue. The syntax of Peek is similar to that of Receive.

Listing G

System.Messaging.Message message = queue.Peek();

Sending/receiving serialized objects

While the ability to send text to the queue is interesting, queues also allow you to send serializable objects. This means that you can create a custom .NET class, instantiate an instance of it, and send it to the queue for other applications to pick up. This works by first using the XML Serializer to serialize the object being sent and then placing the serialized object into the message's body.

For example, assume we want to send the following object (Listing H) to the TechRepublic message queue.

Listing H

[Serializable()]

publicclassMessageContent

{

privateDateTime _creationDate = DateTime.Now;

privatestring _messageText;



public MessageContent()

{

}



public MessageContent(string messageText)

{

_messageText = messageText;

}



publicstring MessageText

{

get { return _messageText; }

set { _messageText = value; }

}





publicDateTime CreationDate

{

get { return _creationDate; }

set { _creationDate = value; }

}

}

Sending an instance of this object to the queue is simply a matter of calling the MessageQueue.Send method and passing an object instance as a parameter to the method. An example is shown in Listing I.

Listing I

MessageContent message = newMessageContent("Hello world!");

queue.Send(message, "Sample Message");

As you can see, this is similar to the code we used earlier to send the message with a string as its body. Receiving a message that contains a serialized object is a little more difficult. What we need to do is tell the message what kind of object it contains.

To indicate to the message what kind object it contains, we must set up the message's formatter. This is done by assigning a System.Messaging.XmlMessageFormatter object to the message's Formatter property. Since our message contains a MessageContent object, we will want to configure the XmlMessageFormatter for that type.

Listing J



message.Formatter =

new System.Messaging.XmlMessageFormatter(

newType[1] { typeof(MessageContent) }

);

Now that the message has a formatter assigned, we can pull the serialized MessageContent object out of the message. Before doing this, however, we will need to cast the return value of the message.Body property to a MessageContent object:

Listing K

MessageContent content = (MessageContent)message.Body;

At this point, the "content" variable is a deserialized version of the original MessageContent object that we sent to the queue, and all of the original object's properties and values are accessible.

Setting message priority

In a normal situation, messages are accessed in the queue in a first-in, first-out order. This means that if you send MessageA, then MessageB, MessageA will be the first message returned from the queue, followed by MessageB. In most scenarios, this will work fine; however, there are instances where you will want a message to move to the front of the line because it is of higher importance than the other messages. To accomplish this type of functionality, you will need to set message priorities.

The priority of a message is determined by the value of the Message.Priority property. The following are valid values for this property (all from the MessagePriority enum):

  1. Highest
  2. VeryHigh
  3. High
  4. AboveNormal
  5. Normal
  6. Low
  7. VeryLow
  8. Lowest

The position of a message in the queue is determined by its priority--for example, assume we have four messages in the queue, two marked as "Normal" priority and two with "High" priority. Our queue would look like this:

  1. High Priority A--This would be the first message of "High" priority send to the queue.
  2. High Priority B--This would be the second message of "High" priority send to the queue.
  3. Normal Priority A--This would be the first message of "Normal" priority send to the queue.
  4. Normal Priority B--This would be the second message of "Normal" priority send to the queue.

Given this order, if we were to send another message to the queue with a priority of "Highest", it would be placed on the top of the queue.

If the message priority functionality is needed, you will have to modify your code to send the messages. Because the constructor of the Message object doesn't have an overload to specify the message's priority, you must instantiate a Message object and set the associated properties on it before sending it to the queue. The code in Listing L demonstrates how to do this and sends a message with a priority of "Highest" to the queue:

Listing L

//Instantiate the queue

MessageQueue queue = newMessageQueue(queueName);



//Create a XmlSerializer for the object type we're sending.

XmlSerializer serializer = new

XmlSerializer(typeof(MessageContent));



//Instantiate a new message.

System.Messaging.Message queueMessage =

new System.Messaging.Message();



//Set the priority to Highest.

queueMessage.Priority = MessagePriority.Highest;



//Create our MessageContent object.

MessageContent messageContent =

newMessageContent("Hello world - IMPORTANT!");



//Serialize the MessageContent object into the queueMessage.

serializer.Serialize(queueMessage.BodyStream, messageContent);



//Send the message.

queue.Send(queueMessage, "HIGH PRIORITY");

The most obvious difference between this code and the code shown above is the use of the XmlFormatter. This is actually optional, and the code in Listing L could be written as it is in Listing M.

Listing M

//Instantiate a new message.

System.Messaging.Message queueMessage =

new System.Messaging.Message();



//Set the priority to Highest.

queueMessage.Priority = MessagePriority.Highest;



//Create our MessageContent object.

MessageContent messageContent =

newMessageContent("Hello world - IMPORTANT!");



//Set the body as the messageContent object.

queueMessage.Body = messageContent;



//Send the message.

queue.Send(queueMessage, "HIGH PRIORITY");

This code performs the same task as the code shown in Listing L, with fewer lines.

Uses

A simple example of the use of MSMQ functionality would be importing customer requests. The customer would post a request that would get sent to the message queue by a customer-facing application. After the request is sent to the queue, an acknowledgment would be sent to the customer. A separate process would then pick up the message from the queue and run any business logic that is required. After the logic is complete, the processing system would post a response message to another queue. The response would then be picked up from the queue by the customer-facing component and a response would be returned to the customer.

This type of configuration would allow the customer-facing component to be very fast and responsive, while doing the bulk of processing on an internal system. This would also allow the request processing to be spread across multiple internal machines, providing scalability.

Download the sample project

For a working copy of the code shown, download the associated example project. The project contains all the code you need to read or write messages from a queue.



From

No comments:

Post a Comment