Chapter 15. Message Queues

Introduction

In all of the networking examples so far, it is necessary for the client and server to be running at the same time in order for communication to be sent between them. Message queuing software facilitates the construction of queues between client and server over an impermanent connection, such that messages are stored until a connection is present. Microsoft Message Queue (MSMQ) is the most applicable system for .NET, but other products such as IBM WebSphere MQ (www.ibm.com/software/ts/mqseries) or TIBCO RendezVous (http://www.tibco.com/software/enterprise_backbone/rendezvous.jsp) are alternatives.

Message queuing is often used as a backup system for whenever a communication link fails between two servers. This improves overall system stability in the event of catastrophic failure. This type of fallback mechanism is vital in systems where out-of-sync data between sites could cause opportunities for malicious users to defraud the system. One could imagine if a person were to withdraw funds from two ATMs simultaneously, during a temporary interbank communications link failure. If the ATMs did not propagate the transactions back to the bank once the link was restored, then the person could run away with double the available balance.

This chapter begins by describing the basics of MSMQ and providing examples of how to send and receive objects via a message queue. This topic is then developed toward the end of the chapter by detailing other features of MSMQ, which help manage how messages are sent through the system.

MSMQ

A common application for MSMQ is where a business may have many different regional outlets and one head office, where stock replacement and auditing takes place. Each outlet may have only a dial-up Internet connection, but a system still needs to be in place to provide good, reliable data consolidation whenever the satellite offices are connected to the head office. The amount of work involved in implementing a custom solution is substantial, especially if the system is expected to scale upward.

MSMQ is included with Windows XP Professional and available as part of the Windows NT4 Option Pack. To install it, click Start→Control Panel→Add or Remove Programs→Windows components, and check Message Queuing, and then Next.

You can administer MSMQ by clicking Start→Control Panel→Administrative Tools→Computer Management→Services and Applications→Message Queuing (Figure 15.1).

Computer Management dialog, MSMQ console.

Figure 15.1. Computer Management dialog, MSMQ console.

Implementing a message queue

To run this example, you will need MSMQ running on your computer. In this example, a message will be passed between two computers with an impermanent link between them. If you are on a LAN, you can simulate the dropout in connectivity by unplugging the Ethernet cable; for readers with only one computer, the effect can be simulated by running the client and server one after the other (not simultaneously).

An application not too dissimilar from this example could be used to perform database replication, where the string being sent via MSMQ could be an SQL statement, and the receiver could execute the statement, rather than simply displaying it. In this way, each action performed on the local database could be mirrored on a remote database whenever they are connected.

The types of data that can be placed on queues are not limited to strings. All objects that can be serialized can be placed on the message queue as XML.

Create a new Visual Studio .NET application as Per usual. Add a reference to System.Messaging.dll with Projects→Add Reference.

This code will first check if a queue is available, and then create it if it is not available. Once that is done, the contents of a textbox will be sent to the queue. Draw a textbox named tbMessage and a button named btnSend, and then click on the button.

C#

private void btnSend_Click(object sender, System.EventArgs e)
{
  string queueName = ".\private$\test";
  MessageQueue mq;
  if (MessageQueue.Exists(queueName))
  {
    mq=new MessageQueue(queueName);
  }
  else
  {
    mq = MessageQueue.Create(queueName);
  }
  mq.Send(tbMessage.Text);
}

VB.NET

Private Sub btnSend_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
  Dim queueName As String = ".private$	est"
  Dim mq As MessageQueue
  If MessageQueue.Exists(queueName) Then
    mq=New MessageQueue(queueName)
  Else
    mq = MessageQueue.Create(queueName)
  End If
  mq.Send(tbMessage.Text)
End Sub

This code first looks at MSMQ to see if a queue of the name private$ test has been created on the local machine. If it has not, then a Message-Queue object (Table 15.1) points to the existing one. The contents of the textbox (tbMessage) are then sent as a message to this queue.

Table 15.1. Significant members of the MessageQueue class.

Formatter

Specifies the formatter used to serialize or deserialize the message body; can be either XmlMessageFormatter, ActiveXMessageFormatter, or BinaryMessageFormatter

Label

Specifies a human-readable queue description

Path

Specifies the location of the queue

Transactional

Specifies whether the queue can accept nontransactional messages

Authenticate

Specifies whether the queue can accept unauthenticated messages

EncryptionRequired

Specifies whether the queue can accept unencrypted messages

Close

Frees all resources used by the handle to the queue

Create

Creates a new queue at the specified path

Delete

Removes a queue from MSMQ, deleting all messages contained therein

GetAllMessages

Returns an array of messages from the specified queue

GetPrivateQueuesByMachine

Returns an array of private message queues from the specified machine

GetPublicQueues

Returns an array of queues on the local network

Receive

Returns a message from the top of the specified queue

Send

Sends a message to the tail of the specified queue

Purge

Deletes all messages from a queue, but does not delete the queue itself

More than one queue can be held on one MSMQ server. They are named < Server name >private$< Queue name >, where < Server name > can be either the computer name of the MSMQ server or “.” for the local server. When the MSMQ server is not on the local intranet, then MSMQ over HTTP may be used. In this case, the server name can be an IP address or domain name. MSMQ HTTP support must be installed from Add/Remove Windows components in the Control Panel (Figure 15.2).

MSMQ HTTP support.

Figure 15.2. MSMQ HTTP support.

HTTP message queues are hosted by IIS and reside in the msmq virtual folder. In this way, queues over HTTP take the form:

http://<domain name>/msmq/<queue name>

You will also require the following namespace:

C#

using System.Messaging;

VB.NET

Imports System.Messaging

To test this application, ensure that you have MSMQ installed on your system, and then run this program from Visual Studio .NET. You should then type some text into the box provided and press Send, as shown in Figure 15.3.

Basic MSMQ application.

Figure 15.3. Basic MSMQ application.

Go to Message Queuing in Computer Management, and click on Private Queues→Test→Queue Messages. Double-click on the envelope icon on the right-hand side, and click on the Body tab in the new window. You should see an XML representation of the text that was sent (Figure 15.4).

Native XML format of a message in MSMQ.

Figure 15.4. Native XML format of a message in MSMQ.

To complete the example, it is also necessary to know how to read in a message from a message queue, as well as how to write to it.

Draw a textbox, tbStatus, and a button, btnListen. Ensure that MultiLine is set to true for the textbox. Click on btnListen and enter the following code:

C#

private void btnListen_Click(object sender, System.EventArgs
e)
{
  Thread thdListener = new Thread(new ThreadStart(QThread));
  thdListener.Start();
}

VB.NET

Private Sub btnListen_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
  Dim thdListener As Thread = New Thread(New _
  ThreadStart(AddressOf QThread))
  thdListener.Start()
End Sub

Because it is possible that there are no messages on the queue, the server will block (hang) at the point of calling the Receive method; therefore, QThread is invoked as a new thread.

C#

 public void QThread()
 {
   string queuePath = ".\private$\test";
   MessageQueue queue = new MessageQueue(queuePath);
   System.Messaging.Message msg;
   ((XmlMessageFormatter)queue.Formatter).TargetTypes =
      new Type[1];
   ((XmlMessageFormatter)queue.Formatter).TargetTypes[0] =
     "".GetType();
   while(true)
   {
     msg= queue.Receive();
     tbStatus.Text += msg.Body + "
";
   }
}

VB.NET

Public Sub QThread()
 Dim queuePath As String = ".private$	est"
 Dim queue As MessageQueue = New MessageQueue(queuePath)
 Dim msg As System.Messaging.Message
 CType(queue.Formatter, XmlMessageFormatter).TargetTypes _
 = New Type(0){}
 CType(queue.Formatter, XmlMessageFormatter).TargetTypes(0) _
 = "".GetType()
 Do
   msg= queue.Receive()
   tbStatus.Text += msg.Body + VbCrLf
 Loop
End Sub

In Figure 15.4, it is clear that messages are stored internally in XML format. This permits the storage of complex types as well as simple strings, but it is necessary to indicate to MSMQ the target type of the object that you want to read back into. In this case, we are reading a string, so the TargetType is set to string (obtained by the "".GetType() construct). It is not necessary to specify the object type when sending to a message queue because .NET can determine this from reflection. The deserialized version of the object is then held in the Message object (Table 15.2).

Table 15.2. Significant members of the Message class.

AcknowledgeType

Specifies the events that require acknowledgment from MSMQ

Acknowledgment

Determines the type of acknowledgment that is flagged by the message

AdministrationQueue

Specifies the queue to which acknowledgment messages are to be sent

AttachSenderId

Includes the ID of the sending machine in the message

Body

Specifies the message payload, which can be any type of object

Label

Includes a human-readable description of the message

MessageType

Indicates that the message is normal, an acknowledgment, or a report

Priority

Determines where in the queue the message is to be placed

Recoverable

Specifies whether the message is stored in memory or disk

SenderId

Indicates the sending machine

TimeToReachQueue

Specifies the maximum length of time it should take for a message to reach a queue

UseDeadLetterQueue

Determines if the time-expired messages should go to the dead-letter queue

UseJournalQueue

Determines if received messages should be archived in the journal

You will need a reference to System.Messaging.dll and the following namespaces:

C#

using System.Threading;
using System.Messaging;

VB.NET

imports System.Threading
imports System.Messaging

To test this application, run them both from their .exe files. Type “hello world” into the client, and press Send. The message will not appear on the server because it is not listening yet. Press the Listen button on the server, and the message will appear in the status box, as shown in Figure 15.5.

Basic MSMQ receiver application.

Figure 15.5. Basic MSMQ receiver application.

Queuing complex objects

It is perfectly valid to use the serialization and deserialization techniques described in Chapter 2 to send complex objects as strings through MSMQ. In the interest of efficiency and simplicity, it is better to use the built-in functionality in MSMQ to perform this task.

In the following example, imagine a situation where a chain of hotels has a central booking agency. This agency takes phone reservations from overseas customers and places each booking in a queue destined for a particular hotel. This hotel would periodically dial in to collect the latest bookings from this queue.

A hotel needs to know the names of the tourists, when they are coming and leaving, and what type of room they are looking for. Furthermore, the reservation system at the hotel is automated, so the message has to be in a well-defined format.

Building on the previous example to send strings to a message queue, include a class that represents a hotel reservation. Add the following code directly at the start of the namespace:

C#

public class booking
{
  public enum RoomType
  {
    BASIC,
    EN_SUITE,
    DELUXE
  }
  public class Room
  {
    public Int16 occupants;
    public RoomType roomType;
  }
  public string name;
  public Room room;
  public DateTime arrival;
  public DateTime departure;
}

VB.NET

Public Class booking
   Public Enum RoomType
    BASIC
    EN_SUITE
    DELUXE
  End Enum

  Public Class Room
    Public occupants As Int16
    Public roomType As RoomType
  End Class
  Public name As String
  Public myRoom As Room
  Public arrival As DateTime
  Public departure As DateTime
End Class

Select the Form Design tab, and remove the textbox (tbMessage) from the form. Now drag on two textboxes named tbName and tbOccupants. If you wish, you can use labels to indicate what each textbox is used for, although this is not essential. Draw on two Date-Picker controls named dtArrival and dtDeparture. A combo box named cbType is also required. You must click on the Items property for the combo box and add three strings: basic, en suite, and deluxe.

Click on the Send button and add the following code:

C#

private void btnSend_Click(object sender, System.EventArgs e)
{
  string queueName = ".\private$\test";
  MessageQueue mq;
  if (MessageQueue.Exists(queueName))
  {
    mq=new MessageQueue(queueName);
  }
  else
  {
     mq = MessageQueue.Create(queueName);
   }
   booking hotelBooking = new booking();
   hotelBooking.name = tbName.Text;
   hotelBooking.departure = DateTime.Parse(dtDeparture.Text);
   hotelBooking.arrival = DateTime.Parse(dtArrival.Text);
   hotelBooking.room = new booking.Room();
   hotelBooking.room.occupants =
   Convert.ToInt16(tbOccupants.Text);
   switch(cbType.SelectedIndex.ToString())
   {
     case "basic":
       hotelBooking.room.roomType = booking.RoomType.BASIC;
       break;
     case "en suite":
       hotelBooking.room.roomType = booking.RoomType.EN_SUITE;
       break;
     case "deluxe":
       hotelBooking.room.roomType = booking.RoomType.DELUXE;
       break;
   }
   mq.Send(hotelBooking);
 }

VB.NET

   Private Sub btnSend_Click(ByVal sender As Object, _
   ByVal e As System.EventArgs)
     Dim queueName As String = ".private$	est"
     Dim mq As MessageQueue
     If MessageQueue.Exists(queueName) Then
       mq=New MessageQueue(queueName)
     Else
       mq = MessageQueue.Create(queueName)
     End If
     Dim hotelBooking As booking = New booking()
     hotelBooking.name = tbName.Text
     hotelBooking.departure = DateTime.Parse(dtDeparture.Text)
     hotelBooking.arrival = DateTime.Parse(dtArrival.Text)
     hotelBooking.myroom = New booking.Room()
     hotelBooking.myroom.occupants = _
       Convert.ToInt16(tbOccupants.Text)
  Select Case cbType.SelectedIndex.ToString()
    Case "basic"
      hotelBooking.myroom.roomType = booking.RoomType.BASIC
      Exit Sub
    Case "en suite"
      hotelBooking.myroom.roomType = _
        booking.RoomType.EN_SUITE
      Exit Sub
    Case "deluxe"
      hotelBooking.myroom.roomType = booking.RoomType.DELUXE
      Exit Sub
  End Select
  mq.Send(hotelBooking)
End Sub

You will need a reference to System.Messaging.dll and the following namespaces:

C#

using System.Threading;
using System.Messaging;

VB.NET

imports System.Threading
imports System.Messaging

To test the application at this stage, you can run it from Visual Studio .NET. Type some reservation details into the boxes provided, and press send (Figure 15.6).

Complex object MSMQ transfer example.

Figure 15.6. Complex object MSMQ transfer example.

If you open the test queue in Computer Management and right-click on Properties→Body for the new message, you will notice a more verbose XML representation of the booking object:

<?xml version="1.0"?>
<booking xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <name>Fiach Reid</name>
 <room>
  <occupants>
   1
  </occupants>
  <roomType>
    BASIC
   </roomType>
  </room>
  <arrival>
   2002-04-28T00:00:00.0000000-00:00
  </arrival>
  <departure>
   2002-05-07T00:00:00.0000000-00:00
  </departure>
 </booking>

Now, to deserialize the object at the receiving end, it is just a matter of altering the TargetType in the queue formatter from string to booking. You will also need to display the new booking, and of course, you still need to include the booking class after the namespace.

Replace the code in the QThread function with the following:

C#

public void QThread()
{
  string queuePath = ".\private$\test";
  MessageQueue queue = new MessageQueue(queuePath);
  System.Messaging.Message msg;
  ((XmlMessageFormatter)queue.Formatter).TargetTypes =
    new Type[1];
  ((XmlMessageFormatter)queue.Formatter).TargetTypes[0] =
     (new booking()).GetType();
   while(true)
   {
      msg= queue.Receive();
      booking hotelBooking = (booking)msg.Body;
      tbStatus.Text += "tourist name:" +
      hotelBooking.name + "
";
      tbStatus.Text += "arrival:" +
      hotelBooking.arrival + "
";
      tbStatus.Text += "departure:" +
      hotelBooking.departure + "
";
      if (hotelBooking.room!=null)
      {
        tbStatus.Text += "room occupants:" +
        hotelBooking.room.occupants + "
";
        tbStatus.Text += "room type:" +
        hotelBooking.room.roomType.ToString() + "
"; }
   }
 }

VB.NET

Public Sub QThread()
  Dim queuePath As String = ".private$	est"
  Dim queue As MessageQueue = New MessageQueue(queuePath)
  Dim msg As System.Messaging.Message
  CType(queue.Formatter, XmlMessageFormatter).TargetTypes = _
     New Type(0) {}
  CType(queue.Formatter, _
     XmlMessageFormatter).TargetTypes(0) = _
     (New booking()).GetType()
   Do
      msg= queue.Receive()
     Dim hotelBooking As booking = CType(msg.Body, booking)
      tbStatus.Text += "tourist name:" + _
      hotelBooking.name + vbcrlf
      tbStatus.Text += "arrival:" + _
      hotelBooking.arrival + vbcrlf
      tbStatus.Text += "departure:" + _
      hotelBooking.departure + vbcrlf
      if not hotelBooking.room is nothing then
       tbStatus.Text += "room occupants:" & _
         hotelBooking.myroom.occupants & vbcrlf _
         tbStatus.Text += "room type:" & _
         hotelBooking.myroom.roomType.ToString() & vbcrlf
    end if
   Loop
End Sub

This code locates an existing queue named private$ est on the local machine. Because the message contains only one type of object, the TargetTypes property is set to an array of one type. The first and only object passed is a booking, and therefore element 0 in the array of target types is set to the booking type.

The thread now enters an infinite loop. Where it encounters the Receive method, the execution blocks until a new message appears in the queue. This message is converted into a booking and then displayed on-screen.

To test this, first check that the top message in the test queue is one that represents a hotel booking. If you are unsure, delete the queue, and then run the preceding program to post a new reservation to the queue. Now run this program from Visual Studio .NET and press Listen. You should see the details of a new booking in the textbox, as shown in Figure 15.7.

Complex object MSMQ receiver example.

Figure 15.7. Complex object MSMQ receiver example.

Transactions

Like databases, MSMQ supports transactions. A transaction is an atomic unit of work that either succeeds or fails as a whole. In a banking system, a transaction might involve debiting a checking account via one message queue and crediting a savings account via another queue. If a system failure were to occurr in the middle of the transaction, the bank would be liable for theft, unless the transaction were rolled back. After the system restarted, the transaction could be carried out again.

The following code attempts to add two messages to a queue. The code has a deliberate division by zero error between the two message sends. If this line is commented out, both operations are carried out; if not, then neither operation is carried out.

Open the client application in the previous example. Click on the Send button, and replace the code with the following:

C#

private void btnSend_Click(object sender, System.EventArgs e)
{
  int zero = 0;
  string queueName = ".\private$\test2";
  MessageQueueTransaction msgTx = new
  MessageQueueTransaction();
  MessageQueue mq;
  if (MessageQueue.Exists(queueName))
  {
    mq=new MessageQueue(queueName);
  }
  else
  {
    mq = MessageQueue.Create(queueName,true);
  }
  msgTx.Begin();
  try
  {
    mq.Send("Message 1",msgTx);
    zero = 5 / zero; // deliberate error
    mq.Send("Message 2",msgTx);
    msgTx.Commit();
  }
  catch
  {
    msgTx.Abort();
  }
  finally
  {
    mq.Close();
  }
}

VB.NET

Private Sub btnSend_Click(ByVal sender As Object, _
     ByVal e As System.EventArgs)
  Dim zero As Integer =  0
  Dim queueName As String = ".private$	est2"
  Dim msgTx As MessageQueueTransaction = New _
     MessageQueueTransaction()
  Dim mq As MessageQueue
  If MessageQueue.Exists(queueName) Then
    mq=New MessageQueue(queueName)
  Else
    mq = MessageQueue.Create(queueName,True)
  End If
  msgTx.Begin()
  Try
    mq.Send("Message 1",msgTx)
    zero = 5 / zero ' deliberate error
    mq.Send("Message 2",msgTx)
    msgTx.Commit()
  Catch
    msgTx.Abort()
  Finally
    mq.Close()
  End Try
End Sub

This code creates a queue as before. The Begin method initiates a transaction. This means that any changes to the queue will not physically take place until the Commit method is called. If the Abort method is called, or the computer crashes, then any statements issued directly after the Begin method are ignored. In this case, an error occurs before the second message is posted to the queue. This error throws an exception, which causes code to be executed that aborts the transaction.

To test this application, run it from Visual Studio .NET with the deliberate error left in the code. Press Send, and then open Computer Management and look at Message Queues. You will notice that a second queue has been created, but neither message has been posted. If you now remove the deliberate error from the code and rerun the application, then press the Send button, you will see both messages appearing in the Queue Messages list.

Acknowledgments

Most of the work done by MSMQ is behind the scenes and completely transparent to the application. If MSMQ fails for some reason, the application—and therefore the user—will not know that today’s data was never transferred. Acknowledgments provide a mechanism for the sending application to verify that the receiving application has read the message and that the message queue is functioning correctly.

This example builds on the code for the first example in this chapter, so open that project in Visual Studio .NET and click on the Send button.

C#

private void btnSend_Click(object sender, System.EventArgs e)
{
  string queueName = ".\private$\test";
  MessageQueue mq;
  if (MessageQueue.Exists(queueName))
  {
    mq=new MessageQueue(queueName);
  }
  else
  {
    mq = MessageQueue.Create(queueName);
  }
  System.Messaging.Message myMessage = new
  System.Messaging.Message();
  myMessage.Body = tbMessage.Text;
  myMessage.AdministrationQueue =
  new MessageQueue(".\private$\test");
 myMessage.AcknowledgeType = AcknowledgeTypes.FullReachQueue
 AcknowledgeTypes.FullReceive;
 mq.Send(myMessage);
}

VB.NET

Private Sub btnSend_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
  Dim queueName As String = ".private$	est"
  Dim mq As MessageQueue
  If MessageQueue.Exists(queueName) Then
    mq=New MessageQueue(queueName)
  Else
    mq = MessageQueue.Create(queueName)
  End If
  Dim myMessage As System.Messaging.Message = New _
  System.Messaging.Message()
  myMessage.Body = tbMessage.Text
  myMessage.AdministrationQueue = New MessageQueue( _
  ".private$	est")
  myMessage.AcknowledgeType = _
  AcknowledgeTypes.FullReachQueue or _
  AcknowledgeTypes.FullReceive
  mq.Send(myMessage)
End Sub

The preceding code checks for a private queue named private$ est. If one is not found, a queue is then created. A message is then created, ready for posting to this queue. This message is set to acknowledge reaching the queue (AcknowledgeTypes.FullReachQueue) and reaching the end-recipient (AcknowledgeTypes.FullReceive). Acknowledgments are set to appear in the same test queue.

To test this piece of code, run it from Visual Studio .NET, type some text into the box provided, and press send. On opening Computer Management→Message Queuing→Private Queues→Test, you will notice acknowledgment messages interspersed throughout the list. Acknowledgment messages have a body size of 0 and carry a green circle on the envelope icon (Figure 15.8). The receiver program can recognize acknowledgment messages when a message has its MessageType set to MessageType.Acknowledgment.

MSMQ acknowledgments.

Figure 15.8. MSMQ acknowledgments.

Note

When each message is received, a second acknowledgment message will appear in the queue, labeled “The message was received.”

Timeouts

“Late data is bad data” is an expression that applies particularly to MSMQ. Imagine a scenario in which MSMQ were used to coordinate last-minute hotel bookings. When a client (a hotel) could not be contacted for more than 24 hours after a booking, it would be imperative that alternative action be taken, such as having an operator call the hotel to confirm the booking manually.

Timeouts provide a mechanism to age messages, such that if they do not reach their destination in time, the message can be deleted or moved to a dead-letter queue so that alternative actions can be taken.

In this example, messages are sent with a five-second timeout. This means they will only appear in the queue for five seconds after being sent, before they are either read by a receiving application or discarded to the dead-letter messages queue. This example builds on the preceding example.

Open the preceding example in Visual Studio .NET, and click on the Send button. Then enter the following code:

C#

private void btnSend_Click(object sender, System.EventArgs e)
{
  string queueName = ".\private$\test";
  MessageQueue mq;
  if (MessageQueue.Exists(queueName))
  {
    mq=new MessageQueue(queueName);
  }
  else
  {
    mq = MessageQueue.Create(queueName);
  }
  System.Messaging.Message myMessage = new
  System.Messaging.Message();
  myMessage.Body = tbMessage.Text;
  myMessage.TimeToBeReceived = new TimeSpan(0,0,0,5);
  myMessage.UseDeadLetterQueue = true;
  mq.Send(myMessage);
}

VB.NET

Private Sub btnSend_Click(ByVal sender As Object, ByVal e As
System.EventArgs)
  Dim queueName As String = ".private$	est"
  Dim mq As MessageQueue
  If MessageQueue.Exists(queueName) Then
    mq=New MessageQueue(queueName)
  Else
    mq = MessageQueue.Create(queueName)
  End If
  Dim myMessage As System.Messaging.Message = _
  New System.Messaging.Message()
  myMessage.Body = tbMessage.Text
  myMessage.TimeToBeReceived = New TimeSpan(0,0,0,5)
  myMessage.UseDeadLetterQueue = True
  mq.Send(myMessage)
End Sub

In this code, the TimeToBeReceived for the message is set to five seconds. A related property TimeToReachQueue can also be used to time-out messages that do not reach the queue in a timely fashion. By setting UseDeadLetterQueue to true, all messages that pass their expiration time are moved into the dead-letter queue for administrative purposes.

To test this piece of code, run it from Visual Studio .NET. Type something into the box provided and press Send. Quickly open Computer Management, and click on the test queue (you may need to right-click and press Refresh). You should see a new message in the list. The messages will disappear again if you refresh the queue after five seconds. Click on System Queues→Dead-letter messages to view expired messages (Figure 15.9).

MSMQ message timeouts.

Figure 15.9. MSMQ message timeouts.

Journal

Journaling is where a record is kept of incoming and outgoing messages to and from remote machines. To specify that the message should be recorded in the journal, the UseJournalQueue method is used.

In the following example, you will need to have the message receiver program described earlier in this chapter close at hand. When sending a message that uses the Journal queue, it will only be transferred to that queue once it has been received. This differs from acknowledgment because the body of the message is stored rather than simply flagging an empty message.

Open the preceding example in Visual Studio .NET, and click on the Send button. Then enter the following code:

C#

private void btnSend_Click(object sender, System.EventArgs e)
{
  string queueName = ".\private$\test";
  MessageQueue mq;
  if (MessageQueue.Exists(queueName))
  {
    mq=new MessageQueue(queueName);
  }
  else
  {
   mq = MessageQueue.Create(queueName);
  }
  System.Messaging.Message myMessage = new
  System.Messaging.Message();
  myMessage.Body = tbMessage.Text;
  myMessage.UseJournalQueue = true;
  mq.Send(myMessage);
}

VB.NET

Private Sub btnSend_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
  Dim queueName As String = ".private$	est"
  Dim mq As MessageQueue
  If MessageQueue.Exists(queueName) Then
    mq=New MessageQueue(queueName)
  Else
    mq = MessageQueue.Create(queueName)
  End If
  Dim myMessage As System.Messaging.Message = _
    New System.Messaging.Message()
  myMessage.Body = tbMessage.Text
  myMessage.UseJournalQueue = True
  mq.Send(myMessage)
End Sub

This piece of code creates a queue as before and posts a string as a message to the queue. Because UseJournalQueue is set, the message will be moved to this system queue after it has been received.

To test this piece of code, run it from Visual Studio .NET. Type something into the box provided and press Send. Open Computer Management and look at the test queue to confirm that the message is in the system. Start the message receiver program, and press Listen. The message should appear in the textbox of the receiver program and be removed from the queue. Clicking on System Queues→Journal messages should show the message once again (Figure 15.10).

MSMQ, Journal messages.

Figure 15.10. MSMQ, Journal messages.

Queued Components

The bulk of the MSMQ code examples to date are very much concerned with the underlying plumbing of sending and receiving messages. You may wish to write code that abstracts away from the underlying MSMQ send & receive mechanisms and concentrate more on business logic.

MSMQ can work in tandem with COM+ component services to provide a means of asynchronous, queued invocation of object methods via Queued Components. In the below example, a component that can perform database updates is created, and a corresponding client is used to call methods on this component. If there were an impermanent connection to this database, then the component may fail during an update, MSMQ handles retries, and queues method calls whenever the component is unavailable.

An example application of the below code is where a database update is required, but is of lower priority than other code which must not be delayed whist waiting for the update to complete.

You may create a queued component by firstly generating a strong name key file by typing sn –k CompPlusServer.snk at the VS.NET command prompt. You can then start a new class library project in Visual Studio .NET, and enter the following code

C#

[assembly: ApplicationName("ComPlusServer")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: AssemblyKeyFile("..\..\ComPlusServer.snk")]
[assembly: ApplicationQueuing(Enabled=true,
QueueListenerEnabled=true)]
namespace ComPlusService
{
   public interface IComPlusServer
   {
      void ExecSQLAsync(string SQL,string strDSN);
   }
   [InterfaceQueuing(Interface="IComPlusServer")]
   public class ComPlusServer : ServicedComponent,
   IComPlusServer
   {
      public void ExecSQLAsync(string SQL,string strDSN)
      {
         OleDbConnection DSN = new
        OleDbConnection(strDSN);
         DSN.Open();
         OleDbCommand oSQL = new OleDbCommand("",DSN);
         oSQL.CommandText = SQL;
         oSQL.ExecuteNonQuery();
         DSN.Close();
      }
   }
}

VB.NET

<assembly: ApplicationName("ComPlusServer")>
<assembly: ApplicationActivation(ActivationOption.Server)>
<assembly: AssemblyKeyFile("....ComPlusServer.snk")>
<assembly: ApplicationQueuing(Enabled := True, _
    QueueListenerEnabled := True)>
Public Interface IComPlusServer
Sub ExecSQLAsync(ByVal SQL As String, ByVal _
    strDSN As String)
End Interface
<InterfaceQueuing([Interface] := "IComPlusServer")> _
Public Class ComPlusServer
    Inherits ServicedComponent
    Implements ServicedComponent, IComPlusServer
    Public Sub ExecSQLAsync(ByVal SQL As String, _
        ByVal strDSN As String)
        Dim DSN As New OleDbConnection(strDSN)
        DSN.Open()
        Dim oSQL As New OleDbCommand("", DSN)
        oSQL.CommandText = SQL
        oSQL.ExecuteNonQuery()
        DSN.Close()
    End Sub
End Class

The above code defines an interface, IComPlusServer, which contains a function prototype for the ExecSQLAsync method. The latter method opens a DSN connection to the specified database, executes an insert, update, or delete, and then closes the connection. A limitation of queued components is that they cannot have return values.

You will require the following namespaces at the head of your code.

C#

using System;
using System.Reflection;
using System.EnterpriseServices;
using System.Data;
using System.Data.OleDb;

VB.NET

Imports System
Imports System.Reflection
Imports System.EnterpriseServices
Imports System.Data
Imports System.Data.OleDb

In order to use this DLL as a queued component, there are some further steps that must be taken.

  1. Import the DLL into the global assembly cache (GAC) by typing gacutil /I:ComPlusService.dll at the command prompt

  2. Import the DLL into component services by typing regsvcs ComPlusService.DLL at the command prompt

  3. Disable authentication on the component by opening Component Services from Administrative Tools, Expand Computers→My Computer→COM+ Applications. Right Click ComPlusServer, select properties→Security. Uncheck Enforce access checks for this application.

  4. Right click ComPlusServer, and click start.

At this point you can now write a client to begin calling methods on this component. Here, we simply create a Windows Forms application in Visual Studio .NET. Add a reference to the ComPlusService DLL created in the previous example, and then draw two textboxes, tbSQL and tbDSN, and a button named btnExecSQL. Double click the button and enter the following code:

C#

private void btnExecSQL_Click(object sender, System.EventArgs
e)
{
   ComPlusService.IComPlusServer ComPlusServer = null;
   ComPlusServer = (IComPlusServer)
   Marshal.BindToMoniker
   ("queue:/new:ComPlusService.ComPlusServer");
   ComPlusServer.ExecSQLAsync
     (this.tbSQL.Text,this.tbDSN.Text);
   Marshal.ReleaseComObject(ComPlusServer);
}

VB.NET

Private Sub btnExecSQL_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles btnExecSQL.Click
  Dim ComPlusServer As ComPlusService.IComPlusServer = _
  Nothing
  ComPlusServer = _
  CType(Marshal.BindToMoniker( _
  "queue:/new:ComPlusService.ComPlusServer"), _
  IComPlusServer)
  ComPlusServer.ExecSQLAsync(Me.tbSQL.Text, Me.tbDSN.Text)
  Marshal.ReleaseComObject(ComPlusServer)
 End Sub

The above code does not directly execute the ExecSQLAsync method on the ComPlusService component. Instead it writes an instruction to the ComPlusService queue in MSMQ, which is then read back by component services, which executes the method on the component.

You will need the following namespaces at the head of the code in your application.

C#

using ComPlusService;
using System.Runtime.InteropServices;

VB.NET

Imports ComPlusService
Imports System.Runtime.InteropServices

To test the application, run the client from Visual Studio .NET, type in a valid DSN and SQL statement, then press the ‘Execute SQL’ button. You will see that the database is updated within a few moments (Figure 15.11). If you temporarily stop the component from component services, and continue to use the client, then the changes will be applied as soon as you restart the component.

Test COM+ Client.

Figure 15.11. Test COM+ Client.

Security

Using MSMQ gives an attacker another point of access to sensitive information. Without encryption and authentication, MSMQ could never be used to handle credit card details or other financial transactions.

To encrypt a message in MSMQ, you set the UseEncryption property to true before sending the message. This prevents the message from being snooped while in transit, but it is decrypted transparently at the receiving end.

The encryption algorithm can be selected using the EncryptionAlgorithm property. This can be set to either RC2 or RC4. The latter is a stream cipher and is thus faster than RC2.

To use authentication in a message in MSMQ, you set the UseAuthentication property to true before sending the message. This will guarantee to the receiver that the message sender is legitimate, but it does not secure against packet snooping.

The hashing algorithm can be selected using the HashAlgorithm property. This can be set to MAC, MD2, MD4, MD5, SHA, or none. The default algorithm is MD5, although MAC (keyed hash) is the most secure.

An authenticated message needs to be accompanied by an external certificate, as contained within the SenderCertificate property. An external certificate must be registered with the directory service of MSMQ. An external certificate contains information about the certification authority, the certificate user, the validity period of the certificate, and the public key of the certificate user, the certification authority’s signature, and so forth.

In cases where message properties usually set by MSMQ are not suited for a particular application, it is possible to tweak low-level security aspects of MSMQ manually. This includes the DestinationSymetricKey property. The latter is simply a byte array used to encrypt and decrypt the message on sending and receipt. The ConnectorType property must be set to a genuinely unique identifier (GUID) to access this property.

Low-level authentication properties that can be altered once Connector-Type is set are AuthenticationProviderName, AuthenticationProvider-Type, and DigitalSignature. These methods specify the name, type, and credentials of authentication applied to the message, defaulting to Microsoft Base Cryptographic Provider, Ver. 1.0, RSA_FULL and a zero-length array, respectively.

Where MSMQ is used over HTTP, it is possible to employ standard Web security systems, such as HTTPS. In this case, the MSMQ server domain name would be prefixed with https://.

As shown in chapters 8 and 9, it is easy to use ultrastrong encryption algorithms on strings (and serialized objects). Coupled with the use of X.509 certificates, issued by an internationally trusted certificate authority, strong authentication could be easily applied to message queues.

To illustrate the example based on the previous hotel booking center analogy, imagine that the booking center also forwarded credit card details to the hotelier via MSMQ. The booking center would need to be absolutely sure that when someone dialed into the MSMQ server, it was in fact the hotelier and not a hacker. Furthermore, it would be a disaster if a technically savvy clerk at the hotel could snoop credit card details from the network installed at the hotel.

First, the hotel would need to acquire an X.509 certificate from a certificate authority such as Verisign or Thawte. The certificate containing the private key would remain at the hotel, but the public keyed certificate would be sent to the booking center.

When a phone order arrived at the booking center, a message would be placed in the queue, which would be encrypted with the public key from the certificate. At this point, a hacker could still receive the message, but could not read it; however, a problem still remains because the hotelier would not know whether there was ever a new message or if it had been stolen.

To avoid this situation, the booking center would require an acknowledgment from the hotel that the booking had been received. The acknowledgment would simply be an acknowledgment reference number encrypted with the private key from the certificate. An attacker would not be able to generate this message, so the message could be reposted awaiting pickup from the correct recipient.

Scalability

When computer systems scale upward, so does the volume of data being sent between them. MSMQ needs to be able to handle larger volumes of data and larger networks, when needed.

Note

When installing an MSMQ server behind a firewall, you will need to ensure that TCP 1801 is open.

MSMQ can consume a lot of disk space; therefore, it may be necessary to ensure that some queues do not grow to a size that they fill the hard disk and prevent other queues from operating. To do this, set the Queue Quota by opening Computer Management, clicking on Message Queuing, and then selecting the queue in question (i.e., Private Queues→test2). Right-click on the queue and select Properties (Figure 15.12). The queue quota is contained in the Limit message storage to (KB): box. The computer quota can be set in the same manner.

MSMQ queue settings dialog.

Figure 15.12. MSMQ queue settings dialog.

Another space-consuming item that is vital to the correct operation of MSMQ is the MQIS database, an internal database that contains queue information and network topology. This is a distributed database, so more than one MSMQ server can hold the data.

In situations where multiple segments in a network are all interconnected with impermanent connections, multiple MSMQ servers can be deployed in each segment. A sample case would be an international chain of shops that centralize their point-of-sale data at the regional office for end-of-day processing and send it once a week to the head office for auditing.

In MSMQ terminology, the entire chain is called an enterprise, each regional office is a site, and every shop is a client. The MSMQ server located in the head office is called the primary enterprise controller (PEC), and the servers at the regional offices are called Primary Site Controllers (PSCs). Three other types of MSMQ servers are available: backup site controllers (BSCs), routing servers, and connector servers.

A BSC requires both a PEC and PSC and stores as a read-only backup of a PSC’s database. This ensures that if the PSC goes offline, clients can still read from the BSC.

A routing server provides a mechanism to forward messages through alternate routes if a network connection goes down. To illustrate this feature, envisage two sites, New York and Toronto, and a head office in Dallas. If the link between Toronto and Dallas is broken, but links between the other cities are still operational, then a routing server could relay messages from New York through Toronto.

A connector server is used as a proxy between MSMQ and third-party messaging systems, such as IBM MQSeries.

The shops can be either dependent clients or independent clients. The difference is that an independent client can store messages locally and forward them to the regional office whenever a connection becomes available. A dependent client requires an always-on connection to the regional office. This may seem disadvantageous, but a dependent client uses less disk space, will run on Windows 95, and becomes one less point of administration

Note

You cannot install an independent client when disconnected to the PSC because it requires access to MQIS data to initialize properly.

Performance issues

MSMQ can operate in a multitude of ways, from running locally as an interprocess communications (IPC) mechanism for applications or as a complex structure of hundreds of machines working in tandem. MSMQ is an effective IPC mechanism when the messages are sent in the Express format, where messages are held in RAM rather than on disk. This does mean that the data will be erased on power failure, but the applications will also be stopped abruptly, so it shouldn’t matter. The only IPC that would outperform MSMQ would be Windows messaging (specifically WM_COPY), but this is not an easy undertaking.

When operating MSMQ over a network, it is common for all messages to be stored on disk to ensure that no data is lost in the event of a system failure. These messages are known as recoverable messages. They come in two flavors: transactional and nontransactional.

Transactions are carried out as a series of in-memory operations and then committed to disk when the operation is complete. They can be coordinated by MSMQ or by the Microsoft Distributed Transaction Coordinator (MSDTC); the former is the more efficient. Nontransactional messages cannot be rolled back, but they are faster than transactional messages.

When many messages need to be written to a queue in one operation, a higher performance can be achieved if a thread pool is used. This only applies to writing messages to a queue; reading from a queue using multiple threads actually decreases performance. When using threads, it is important to make sure the connection to the MSMQ server is not reopened in every thread, but rather, a connection is shared among all threads.

Where network bandwidth is a concern (e.g., over dial-up connections), actions can be taken to reduce the size of the message body by using binary formatters rather than the default XML formatter.

This can be implemented by setting the Formatter property to New BinaryMessageFormatter() before calling the Send method. A new feature in MSMQ 3.0 is the use of multicast from within MSMQ. Where a single message is destined for multiple recipients, multicasting can greatly reduce network traffic. This does require access to the MBONE network and, thus, may not be applicable to all situations.

The most common performance problem with MSMQ is handles to queues being repeatedly opened and closed. This process is extremely wasteful, and it is imperative that a handle to the queue should be maintained for as long as possible. A few bytes from each message can be cut by omitting the system identification (SID), but this is only an option if security features are not being used. Another pitfall could be that the client is requesting too many acknowledgments from the server, which may put an unnecessary strain on both the client and server.

Conclusion

There is little real voodoo behind message queuing, and it would be an easy task to implement a store-and-forward-type proxy server using socket-level programming; however, this chapter is meant to illustrate the advantage of moving to industry-standard techniques by demonstrating the wealth of additional functionality built into MSMQ. After an initial learning curve, MSMQ can easily be seen as a much more scalable solution than any in-house solution developed in the same timeframe.

The next chapter deals with a subject that may not directly impinge on developer’s lives now, but by 2005, it is set to overhaul the entire Internet as we know it, and interoperability with it will become a major selling point with future-proof software products.

Make way for IPv6.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.150.231