Programming Message Queues

I l @ ve RuBoard

Now that you have an understanding of how Message Queuing 3.0 operates, we'll look at how to use it when you build J# applications. The .NET Framework Class Library provides a comprehensive API for interacting with Message Queuing 3.0 in the System.Messaging namespace.

Posting and Receiving Messages

We'll adapt the by-now familiar example of the CakeSizeServer and CakeSizeClient used in many of the preceding chapters to show the use of message queues. Briefly, the QueuedCakeClient class (which is contained in the file QueuedCakeClient.jsl in the CakeSizeClient project) constructs a request comprising the details of a cake ( size , shape, and filling) and submits it to a public message queue called CakeQueue on the computer running the QueuedCakeSizeServer application. (See the file QueuedCakeSizeServer.jsl in the CakeSizeServer project.)

Tip

The example described below is spread across four projects. The CakeSizeClient solution file (CakeSizeClient.sln) contains all four projects. If you open this solution using Visual Studio.NET, you'll be able to access all four projects together.


You should create the public, nontransactional CakeQueue message queue manually using the Computer Management console before proceeding further. (It is not transactional.)

Note

Although a path is specified for the queue, Active Directory will be used to resolve this path and send the message to the appropriate queue. We'll discuss this more later.


The QueuedCakeSizeServer reads the CakeQueue , validates the request, and then calls the FeedsHowMany method of the CakeInfo class (in CakeUtils.jsl, in the CakeUtils project), which returns a value indicating how many people the specified cake will feed. This value is packaged up into another message and sent back to the client before the server cleans up and terminates. The client tells the server which queue to reply to as part of the data in the original request (a private queue called CakeResponse on the client computer). When the client receives the reply, it unpacks it, displays the result, tidies up, and finishes.

Messages and Serialization

Now for the details. The FeedsHowMany method of the CakeInfo class expects three parameters of type short that indicate the diameter, filling, and shape of the cake. Rather than send this data as three small packages, the client makes use of a new class, CakeRequest (in CakeClasses.jsl, in the CakeClasses project) that acts as a wrapper for this information. This class is in the CakeClasses namespace, along with the CakeFilling and CakeShape classes used in earlier chapters.

The contents of messages posted to a message queue are serialized, either as XML or using a binary format. (The programmer can specify which.) For this reason, the CakeRequest class is kept simple ”it holds public data (properties would have been overkill for this example) so it can be easily serialized as XML, but it is also tagged with SerializeableAttribute to support binary serialization.

Note

The Message Queuing API also supports the sending and receiving of primitive types if you're developing in C# or Visual Basic .NET, but not if you're developing in J#. The reason for this is the way that the primitive types are handled. In C# and Visual Basic .NET, the primitive types are actually aliases for structures defined in the System namespace. C# and Visual Basic .NET support the automatic boxing of structures, which allows structures (value types) to be used like regular objects (reference types). Remember, however, that both J# and Java distinguish between the primitive types and objects, and primitive types cannot be converted automatically to objects or vice-versa. For more details, see Chapter 3.


Attaching to a Queue and Creating a Message

The main method of the QueuedCakeClient class creates a System.Messaging.MessageQueue object that connects to the public queue CakeQueue on the computer called WhiteRabbit (the computer running the QueuedCakeSizeServer program). Notice that the syntax of a public message queue is < machine>\<queue> :

 importSystem.Messaging.*; importCakeClasses.*; publicclassQueuedCakeClient { privatestaticStringserverQueuePath= "WhiteRabbit\CakeQueue"; publicstaticvoidmain(String[]args) { //ConnecttothepublicCakeQueueontheservercomputer MessageQueuecakeQueue=newMessageQueue(serverQueuePath); } } 

Note

You must reference the assembly System.Messaging.dll to use the System.Message namespace.


Note

Instantiating a new MessageQueue object in an application simply creates an object in memory that can be used to access an existing queue ”it does not create a physical message queue. If you want to create a message queue programmatically, you must use the MessageQueue.Create method, which is described later.


The main method creates a CakeRequest object and populates it with some sample data that describes a 12-inch square fruit cake. This object is then packed up into a System.Messaging.Message object. In addition to the object to be serialized, you can optionally supply the Message constructor with an instance of a serializer class. This serializer class instance will be used to serialize the message object into the message body when it is posted to the queue. The System.Messaging namespace supplies two ready-made serializers called BinaryMessageFormatter and XmlMessageFormatter (the default formatter) but you can also define your own by implementing the IMessageFormatter interface if you prefer. Note that if you do define your own serializer, you must make it available to the server process that deserializes the message as well as to the client.

Note

A third formatter is also available: ActiveXMessageFormatter . Previous versions of MSMQ supplied ActiveX components for posting and receiving messages. The format of messages sent using the ActiveX components was different from that emitted by the BinaryMessageFormatter and the XmlMessageFormatter classes. If you need to interoperate with MSMQ systems that employ the MSMQ ActiveX components, you should use the ActiveXMessageFormatter to serialize and deserialize messages:

 //Createanewcakesizerequest: //Howmanypeoplewilla12" squarefruitcakefeed? CakeRequestrequest=newCakeRequest(); request.diameter=12; request.shape=CakeShape.Square; request.filling=CakeFilling.Fruit; //Createamessagecontainingthecakesizerequest //Formatthemessageusingbinaryserialization Messagemsg=newMessage(request,newBinaryMessageFormatter()); 

Tip

Do not confuse the System.Messaging.Message class, which defines a message for posting to a message queue, with classes that implement the System.Runtime.Remoting.Messaging.IMessage interface, which define messages for transmission using .NET Remoting.


Next , the client sets some selected properties of the message. The Recoverable property determines whether the message uses express (in-memory) or recoverable (on-disk) delivery. A value of false indicates express delivery which consumes fewer resources. (This is the default.) The Priority property should be self-explanatory. You can set the priority value to any of the values in the MessagePriority enumeration. The QueuedCakeClient class sets the priority to Low , indicating that this is not the most urgent of messages. The default priority is MessagePriority.Normal , but eight values are available ranging , from MessagePriority.Lowest (priority 0) to MessagePriority.Highest (priority 7):

 //Setthemessageproperties-useexpressdelivery(in-memory), //andsettheprioritytolow msg.set_Recoverable(false); msg.set_Priority(MessagePriority.Low); 
Using a Response Queue

In this example, the client sending a message expects a reply from the server. In theory, the reply could be posted back to the same queue to which the original message was submitted, and this would most likely work if there were only a single client and the client and server coordinated their work with each other. However, as soon as multiple clients started submitting requests and expecting replies, the situation would become untenable ”clients could end up reading requests posted by other clients instead of replies to their own requests. The system would soon degenerate into chaos.

To avoid this situation, a client should create its own private message queue and pass the details of that message queue to the server along with the original message. The server should post its reply to this message queue. (This is analogous to the scheme used by connection-oriented sockets, whereby a server listens for requests on a publicly advertised endpoint, but when a client connects to that endpoint a private port is created for sending any replies back to the client.)

To support this protocol, the Message class provides the ResponseQueue property. You can create a message queue and store a reference to it in this property. The server can extract the value of this property when it reads the message and send the reply back using the specified queue. The QueuedCakeClient class uses the local private queue CakeResponse for this purpose. Notice that the syntax for a private message queue is < machine>\ private$ \<queue> . (You can use "." to refer to the local computer.) The code checks whether the queue already exists using the static MessageQueue.Exists method. If the queue does not exist, it is created using MessageQueue.Create . (Your application must be running with sufficient privileges ” MessageQueuePermissionAccess.Administer ” to perform this operation.) Otherwise, a reference to the existing queue is created:

 privatestaticStringresponseQueuePath= ".\private$\CakeResponse"; //Createalocal,privateresponsequeuethattheserver //cansendthereplyto //Checkthatthequeuedoesnotalreadyexist //Ifitdoesn't,createit MessageQueueresponseQueue; if(!MessageQueue.Exists(responseQueuePath)) { responseQueue=MessageQueue.Create(responseQueuePath); } //Ifthequeuealreadyexists,openit else { responseQueue=newMessageQueue(responseQueuePath); } //SettheResponseQueuepropertyofthemessagetothisqueue msg.set_ResponseQueue(responseQueue); 
Posting a Message

Once the response queue has been created, the message can then be sent to the CakeQueue using the Send method. The parameters to Send include the message itself and an optional label ”a text string that can be used to identify the message, or filter the message using a queue trigger. The contents of the label are entirely up to the programmer (but make it meaningful). The QueuedCakeClient class creates a label comprising the text "Cake Size Request" with the current timestamp appended to the end:

 //Sendthemessage,withthelabel cakeQueue.Send(msg, "CakeSizeRequest: " +DateTime.get_Now()); 

If you set a breakpoint in the QueuedCakeClient class at this point and run the program, you can see the message appearing in the CakeQueue on the server computer after the Send method is executed. Figure 12-3 shows the Computer Management console on the server computer (WhiteRabbit) with the Queue Messages folder of CakeQueue displayed:

Figure 12-3. The Computer Management console showing the contents of CakeQueue

If you double-click on a message, you can display its properties, including the body, as shown in Figure 12-4. If you're concerned about confidentiality, you should encrypt messages (as described later in the chapter).

Figure 12-4. The body of a cake size request message

Receiving a Message

Having submitted the request, the QueuedCakeClient class waits for the reply to be sent back on the response queue. You do this using the Receive method, which will block until a message is available. You can optionally specify a time ­out value. (The QueuedCakeClient class will wait for a maximum of two minutes.) If a timeout occurs, a MessageQueueException will be thrown, which you should be prepared to catch.

 //Waitforthereply-uptotwominutes msg=responseQueue.Receive(newTimeSpan(0,2,0)); 

At this point, it's worth switching our attention to the QueuedCake ­SizeServer class, which handles the request sent by the QueuedCakeClient . The main method of the QueuedCakeSizeServer class connects to the local public CakeQueue (the QueuedCakeSizeServer runs on the computer called WhiteRabbit on my network) and then simply calls Receive . This time, no timeout parameter is specified, so the method will block until the client posts the request message to the queue, whereupon it will be removed from the queue and returned in the Message object ( cakeMessage ) specified as the return value from the Receive method:

 publicclassQueuedCakeSizeServer { privatestaticStringqueuePath= ".\CakeQueue"; publicstaticvoidmain(String[]args) { //ConnecttothelocalpublicCakeQueue MessageQueuecakeQueue=newMessageQueue(queuePath); //Receivethenextrequestfromthequeue //-thiswillblockuntilamessagearrives MessagecakeMessage=cakeQueue.Receive(); } } 

Once the message has been received, the message body containing the CakeRequest object sent by the client must be extracted. Recall that the message body was serialized using a BinaryMessageFormatter object by the client. You should use the same class to deserialize the body. You can do this by setting the Formatter property of the message object and then querying the Body property. (Even though the Formatter property for the message was specified when the message was created, the formatter itself is not actually transmitted with the message, so you must create one when the message is received.)

 //Getthebodyofthemessage //-deserializeusingaBinaryMessageFormatter cakeMessage.set_Formatter(newBinaryMessageFormatter()); System.ObjectcakeData=cakeMessage.get_Body(); 

Theoretically, the body of the message can be almost any type of data, so the Body property returns an Object . You can try to cast this directly into an object of the type you're expecting, but this strategy is not wonderfully safe. Remember that a public message queue is public, so other applications might have posted data to it, possibly containing all sorts of weird and wonderful information. A better approach is to examine the type of the body and cast it only if you can guarantee that it has the appropriate type. In Java, you can use the instanceof operator to verify the type of a Java object; a similar approach will also work with J# and the System.Object class.

 //Verifythatthemessageisinthecorrectformat //Reportanerroranddiscarditifnot if(!(cakeDatainstanceofCakeRequest)) { Console.WriteLine("Badmessage-discarding"); } //OtherwiseextractthedetailsfromtheCakeRequestobject //andcallFeedsHowManytogenerateananswerfortherequest else { CakeRequestcakeRequest=(CakeRequest)cakeData; } 

After retrieving the CakeRequest object from the message body, you can use its contents to invoke the static FeedsHowMany method of the CakeInfo class. Remember that the value returned (a Java short ) will be wrapped up into another message and passed back to the client. Also remember that Java and J# do not support the boxing of primitive types into objects, so a Java short cannot be placed directly into the body of a message (which must be an Object ). The value returned from FeedsHowMany is used to construct a java.lang.Short instead:

 ShortnumEaters=newShort(CakeInfo.FeedsHowMany(cakeRequest.diameter, cakeRequest.shape, cakeRequest.filling)); 

The next task is to create a message containing this Short value and send it back to the client using the response queue specified by the client. You can determine the response queue by querying the ResponseQueue property of the original message:

 //Extracttheresponsequeuefromtheoriginalmessage MessageQueueresponseQueue=cakeMessage.get_ResponseQueue(); 

The body of the message sent back to the client will comprise the return value from FeedsHowMany ”this is held in the Short variable numEaters. Short variables are Object types, but they are sadly not serializable either as XML or using the BinaryMessageFormatter class. One simple solution is to convert the Short into a string using the toString method. (This is the main reason for using a Short rather than a short ”you cannot build a string directly from a Java short.) Strings are serializable.

The Recoverable flag and the formatter used to serialize the string are also set:

 //Constructthereplytobesentbacktotheclient Messagereply=newMessage(numEaters.toString()); reply.set_Recoverable(false); reply.set_Formatter(newBinaryMessageFormatter()); 

Note

Although you cannot build a string directly from a Java short, you can use a little coercion, as in this example:

 shorts=10; 
 StringmyString= "" +s; 

However, this code is not particularly clear or elegant, and it can become a maintenance headache , so document it carefully if you choose to use it.


The message can now be sent to the client. Nothing else needs to be placed on the response queue, so it will be closed after the message is sent. This will release whatever resources the queue object was occupying in memory:

 //Sendthereply responseQueue.Send(reply); responseQueue.Close(); 

Let's switch our attention back to the client now. The client waiting to receive this reply on the response queue will wake up. The message body received should be scrutinized to make sure it is a string. If all is well, the value returned can be displayed before the client closes the CakeQueue and deletes the private response queue using the static Delete method of the MessageQueue class:

 //Waitforthereply-uptotwominutes msg=responseQueue.Receive(newTimeSpan(0,2,0)); msg.set_Formatter(newBinaryMessageFormatter()); System.ObjectreplyData=msg.get_Body(); //ThereplyshouldcontainaStringvaluerepresentingashort //-thenumberofpeoplethecakewillfeed if(!(replyDatainstanceofString)) { Console.WriteLine("Badreply-discarding"); } else { //Displaytheresult StringnumEaters=(String)replyData; Console.WriteLine("Thecakewillfeed " +numEaters); } //Tidyupandfreeresources cakeQueue.Close(); MessageQueue.Delete(responseQueuePath); 

Using Message Queues in Windows Forms and Web Forms Applications

The QueuedCakeClient and QueueCakeSizeServer examples are console applications. If you're building Windows Forms or Web Forms applications (as described in Chapter 16), you can use the features of Visual Studio .NET to automate some of the programming tasks . Server Explorer allows you to drag-and-drop a message queue onto a form, as shown in Figure 12-5.

Figure 12-5. Server Explorer showing the CakeQueue selected, ready to be dragged onto the form

This will create a MessageQueue object and add it to the application, as shown in Figure 12-6.

Figure 12-6. The form showing the MessageQueue object ( messageQueue1 ) underneath

A default name for the MessageQueue object will be generated automatically, but you can use the Properties window to view or change it and any other properties of the message queue at design time.

Handling Messages

The QueuedCakeClient and QueuedCakeServer classes show the essential programming constructs needed to build applications based on message queues, but they also raise a number of questions that we'll address now.

Message Peeking

Perhaps the most important concept to grasp about message queues is that receive operations are destructive. When you execute the Receive method of a message queue, the first available message will be removed from the queue and returned to you. (The operation might block if the queue is empty, depending on any timeout specified.) Sometimes you might want to examine a message before removing it in case it is not for you! In this situation, you should use the Peek method of the message queue, which returns a copy of the message at the head of the queue while leaving the original message still queued:

 MessageQueuecakeQueue=...; MessagecakeMessage=cakeQueue.Peek(); 

The Peek method will block if the queue is empty, but as with the Receive method, you can specify a timeout. A MessageQueueException will be thrown if the timeout expires .

You should also bear in mind that a message queue is a prioritized, first-in-first-out data structure. When you send a message, you have little control over how it flows through the queue, other than being able to specify its priority. You cannot place a message at a particular position in a queue, for example. Likewise, receiving and peeking using the Receive and Peek methods will involve only the message at the head of the queue (although there are ways of accessing messages elsewhere in the queue, as you'll see in the next section).

Enumerating Messages

The MessageQueue class implements message enumeration, which allows you to iterate nondestructively through the messages in a queue. As you access a message, you can modify it or even remove it, although you cannot use an enumerator to insert a message in a given position. To list all the messages in a queue, you can call the GetMessageEnumerator method on the queue. This method returns a System.Message.Messaging.MessageEnumerator object. You can then use this enumerator to access each message in turn .

The following code fragment creates an enumerator over the public CakeQueue and displays the label of each message found in the queue. An enumerator provides access to messages on a message-by-message basis. Chapter 5 described how to use an enumerator, but here's a recap: To access a message, you must advance to it using the MoveNext method. You can read the message using the Current property. You can then move on to the next message by executing MoveNext again. The MoveNext method returns a Boolean value ” true if the operation was successful and another message is available, false if you've reached the end of the enumeration. The Close method discards the enumerator and frees any resources it occupied:

 MessageQueuecakeQueue=newMessageQueue(".\CakeQueue"); MessageEnumeratorenumerator=cakeQueue.GetMessageEnumerator(); Messagemsg=null; while(enumerator.MoveNext()) { msg=enumerator.get_Current(); Console.WriteLine(msg.get_Label()); } enumerator.Close(); 

Remember two important points when you use a message enumerator. First, you can proceed forward through the list of messages, but you cannot go back to an earlier message. However, you can invoke the Reset method to go right back to the beginning of the enumeration and start over. The second point is that any changes you make to messages or the message queue using an enumeration are dynamic and will have instant effect. For example, if you change the Priority of a message to a high value, the message might suddenly be shifted to the head of the queue. A concurrent application receiving from the same queue might then read this message. Similarly, you can delete a message from the queue using the RemoveCurrent method of the enumeration, but this can lead to a range of concurrency issues if another application is currently receiving this message. We'll look at how to handle such issues next.

Message Queue Concurrency

Message queues are shared objects, and they can be accessed by multiple applications simultaneously . Although the Message Queuing infrastructure prevents two receive requests from obtaining the same message, it is possible for two peek requests to read the same object, which might be undesirable. The problem is exacerbated when concurrent applications access the same queue at the same time and one of them tinkers with the message queue using an enumerator (as previously described).

An application can request exclusive read access to a message queue when opening it. The message queue constructor has an optional Boolean parameter that defaults to false but when set to true will obtain exclusive read access when the application reads the message queue using Receive or Peek or creates an enumerator over the queue:

 MessageQueuecakeQueue=newMessageQueue(".\CakeQueue",true); 

A read lock is not obtained on the queue until the first read operation occurs, but then the read lock will be retained until the queue is closed (using the Close method). If a Receive or Peek method call blocks, the queue can potentially be read-locked for a long time. A second application that attempts to read from the queue at the same time will throw a MessageQueueException and access will be denied . Note that this restriction does not apply to Send operations. (It would be awkward if an application waiting on an empty queue were also to block all attempts to post a message to that queue!) Similarly, an enumerator can block read access to queue for a lengthy period. An alternative to using an enumerator to iterate through all the messages in a queue is to use the GetAllMessages method of the queue. This returns a copy of the contents of the queue as an array of Message objects. The queue is read-locked while the array is constructed , but then the queue can be closed and the lock released.

 Message[]list=cakeQueue.GetAllMessages(); 
Message Aging

When you post a message to a queue, you can never be quite sure when, or even if, it will be received. In some circumstances, if a message is not processed within a particular timeframe it will become obsolete or out of date and should be discarded. Message objects have two properties that can affect the lifespan of a message: TimeToReachQueue and TimeToBeReceived .

The TimeToReachQueue property is a TimeSpan indicating the maximum permissible time that can elapse before reaching the destination queue. This property is used most commonly when the destination queue is at a remote site and the message requires routing, if the sender (or the destination queue) is currently disconnected from the network, or if the message will be cached either on the sending machine or on a routing server. If this time expires before the message reaches the destination queue, the message will be deleted. If the UseDeadLetterQueue property of the message is set to true , a copy of the message will be recorded in the local dead-letter queue. Otherwise, it will be silently discarded, although it might generate a negative acknowledgment message (as explained later).

Note

The TimeToReachQueue property depends on the system clocks of machines in the network being accurately synchronized.


The TimeToBeReceived property is similar, except that it specifies how long the message can live once it reaches the destination queue before being discarded (unless it is received before the time interval elapses):

 Messagemsg=...; //Givethemessage10minutestoreachthedestinationqueue msg.set_TimeToReachQueue(newTimeSpan(0,10,0)); //Allowthemessagetoliveforanhouriftheserverprocess //isnotactive msg.set_TimeToBeReceived(newTimeSpan(1,0,0)); //Recordthemessageinthedead-letterqueueifitisdiscarded msg.set_UseDeadLetterQueue(true); 
Correlating Messages

If you're sending a message that expects a reply, you can employ the response queue strategy used in the earlier examples. In a more complex situation, a client might send a number of messages to a server, or to different servers, and expect a response for each message sent. It would therefore be useful to be able to correlate the replies received with the original messages sent.

Each Message object has a string Id property (which you can examine using the get_Id method in J#). This property is read-only and is populated with a message identifier when the Message object is sent. The identifier has two parts : the globally unique identifier (GUID) of the sending computer and a unique identifier for the message on that computer. These are fairly meaningless strings to the average human, but if you're interested you can display them. A typical message identifier looks like this:

 b4c092c0-29ad-43b5-8e73-0f7909f40114293 

The actual value of the message identifier is not important, but the fact that it's unique can be useful. Incidentally, if you examine the Id property of a message before sending it, it will consist entirely of zeros; the Send method populates the Id property.

When a server receives the message from a queue, it can examine the Id property of the message and use it to set another property called the CorrelationId of the reply message to the same value:

 //Server,receivingrequestandsendingreplytoclient MessagecakeMessage=cakeQueue.Receive(); Messagereply=...; reply.set_CorrelationId(cakeMessage.get_Id()); 

When the client receives the reply, it can compare the CorrelationId of the reply message against the Id of the messages it has sent ( assuming the client recorded them somewhere) to determine which message the reply is for. But one tiny little gotcha can occur.

If you examine the Message class in the Visual Studio .NET documentation, you'll see that it has a large number of properties, many of which are useful only in certain situations. To save time during a receive operation, not every property belonging to a message is retrieved when the message is received. Instead, message properties are filtered using the MessageReadPropertyFilter property object of the message queue they're posted to. The MessageReadPropertyFilter property object is really just a collection of Boolean flags that indicate which properties should be read and which should be omitted. By default, the flag corresponding to the CorrelationId property is false . (The documentation erroneously states that it is true , but you can easily verify for yourself that it is not.) If you want to read the CorrelationId property of a message, you must set the CorrelationId flag of the MessageReadPropertyFilter for the queue to true before receiving the message:

 //Client,readingreplyfromserver MessagePropertyFilterprops= responseQueue.get_MessageReadPropertyFilter(); props.set_CorrelationId(true); msg=responseQueue.Receive(...); 

If you attempt to read the correlation ID of a message without setting the property filter first, an InvalidOperationException will be thrown.

Peeking, Receiving, and Message IDs

Under normal circumstances, you can receive or peek at only the first available message at the head of a message queue. But if you're correlating messages, the MessageQueue class allows you to receive and peek at a message with a specified message ID or correlation ID using the ReceiveById , ReceiveByCorrelationId , PeekById , and PeekByCorrelationId methods. These methods expect the appropriate identifier as a parameter, passed in as a string. Message IDs are unique, so the ReceiveById and PeekById methods will match at most one message. However, several messages can use the same correlation ID. The ReceiveByCorrelationId and PeekByCorrelationId methods return the first matching message found.

These methods are overloaded. Unless you specify a timeout, they will throw an exception if a message with the specified identifier is not immediately available in the queue. If you do specify a timeout, an exception will be thrown only if a matching message does not appear during the specified interval.

Journaling Messages

Messages have a UseJournalQueue property. If this property is set to true , the message will be journaled on the originating computer ”a copy of the message will be recorded in the journal messages system queue:

 Messagemsg=...; msg.set_UseJournalQueue(true); 

Note that the message will not appear in the journal messages queue until it has been delivered to the destination queue if the destination queue is on a remote computer or until the message has been received if the destination queue is on the local computer.

You can also set the UseJournalQueue property of a queue. In this case, the message will be recorded in the journal queue attached to the message queue when the message is delivered to the queue. In other words, setting the UseJournalQueue property of a message records the message in the system journal queue on the sending computer, and setting the UseJournalQueue property of a message queue records the message in the local journal queue on the destination computer when the message arrives in the queue.

Managing Queues

You've learned how to create and delete queues using the static Create and Delete methods of the MessageQueue class and how to determine whether a given message queue exists using the Exists method. Of course, you can also create and delete message queues using the Computer Management console in Control Panel. But in addition to creating and removing queues, you'll often need to perform other administrative tasks with message queues, which we'll look at now.

Message Queue Capacity

By default, message queues have an unlimited capacity, which means, in theory, that they can be asked to hold an infinite number of messages of indeterminate length. (This would require a lot of disk storage!) Under normal circumstances, most commercial applications based on message queues tend to process messages at fairly regular intervals, and message queues never quite reach infinite length. But a rogue situation could arise whereby a process continually posts messages to a queue that is never read.

The MessageQueue class exposes two properties that you can use to limit the storage space occupied by a queue: MaximumQueueSize and MaximumJournalSize . The units for these properties are kilobytes. For example, to limit the size of the CakeQueue to 500 KB, you can use the following:

 cakeQueue.set_MaximumQueueSize(500); 

A word of warning: Be sure to set the maximum queue size to a value big enough to accommodate the day-to-day processing of the system. If a backlog of messages in a queue grows faster than a server application reading those messages can handle, the queue will fill up. When the maximum size is reached, further messages posted to that queue will be discarded without your realizing it. However, if the sender requests message acknowledgments, a negative acknowledgment for each lost message will be posted to the administration queue specified in the message (as explained later in this chapter).

You can empty a message queue by purging it. You can execute the Purge method of the MessageQueue object, which discards all messages that are in the queue. This is a nonreversible operation ”messages are not sent to the dead-letter queue or the journal queue.

 cakeQueue.Purge(); 
Message Queue Connection Caching

You're probably aware that when you connect to a shared resource such as a database, a certain amount of checking must be performed and some data structures will have to be initialized to handle the connection. The checks involved ensure that the requested resource actually exists and that the identity of the principal executing the connection request has sufficient privileges. If the shared resource is located remotely, this work can involve a significant burst of network traffic.

When an application has finished using the resource, a close operation often discards all this information. Another connection request by the same application will cause the checking and initialization to be repeated. To reduce this overhead, many shared resources provide connection pooling, whereby a pool of connection handles is created and cached in memory. Connection requests are redirected transparently to the pool. Some checking still needs to be performed, but much of the information required is cached, so a connection is retrieved much more quickly.

Message queue connections can be cached by an application. Whenever you execute Send , Peek , or Receive , a connection handle is used, so pooling can considerably improve performance. The MessageQueue class provides the static property EnableConnectionCache . When this is set to true (the default), message queue connections will be pooled; when set to false , pooling will be disabled. Here is how you set this property:

 MessageQueue.set_EnableConnectionCache(true); 

When the cache is full, the MessageQueue class discards entries from the pool on a least-recently-used basis. However, the size of the pool is managed by the MessageQueue class, and you have little control over how big it is, although you can clear the connection pool using the static ClearConnectionCache method:

 MessageQueue.ClearConnectionCache(); 
Message Queue Security

You might want to restrict access to message queues by limiting the identities of principals that can post or read messages on a queue. You use the SetPermissions method of a queue for this purpose. This method is overloaded and allows you to indicate the privileges required to manipulate a message queue by using an access control list or by explicitly assigning access rights to individual groups and users.

For example, to permit the user WONDERLAND\JSharp to send and receive messages from CakeQueue , you can use the code shown in this fragment:

 cakeQueue.SetPermissions("WONDERLAND\JSharp", MessageQueueAccessRights.WriteMessage MessageQueueAccessRights.ReceiveMessage, AccessControlEntryType.Set); 

Using this form of the SetPermissions method, you can grant any of the privileges listed in the System.Messaging.MessageQueueAccessRights enumeration, and you can combine privileges, as shown above. Note, however, that this form of SetPermissions is absolute ”the flag AccessControlEntryType.Set will set the specified permissions but eliminate any others previously granted to the selected principal.

Note

Granting ReceiveMessage access implicitly grants the PeekMessage privilege, which allows the user to peek at as well as receive messages.


The alternative is to use AccessControlEntryType.Allow , which adds the specified privileges to any that already exist. You can also specify Revoke to remove all privileges granted directly to a principal (the MessageQueueAccessRights parameter is ignored, and the result can be a little confusing for the unwary), or you can specify Deny to actively deny any rights specified.

The following line of code removes the PeekMessage privilege from WONDERLAND\JSharp . (The ReceiveMessage privilege granted earlier will remain , but the user will only be able to receive messages, not peek at them.)

 cakeQueue.SetPermissions("WONDERLAND\JSharp", MessageQueueAccessRights.PeekMessage, AccessControlEntryType.Deny); 

You can examine the privileges assigned to a message queue using the Computer Management console in Control Panel. Select the message queue, right-click, choose Properties, and then click on the Security tab. Figure 12-7 shows the Security page for CakeQueue after the Receive Message privilege has been granted, but the Peek Message privilege has been denied to the user WONDERLAND\JSharp. (The Send Message privilege was also granted ”you'd have to scroll down to see it in the dialog box.)

Figure 12-7. The permissions on the CakeQueue message queue for the user WONDERLAND\JSharp

If you revoke a user's privileges, the user might still be able to use the message queue if she is a member of one or more groups that still has access. On the other hand, if you explicitly deny a privilege, that user will not be able to perform the associated operation no matter what groups she belongs to.

The owner of the queue (the principal who first created it) has full rights over the queue and does not need to be granted any other privileges to use it, although it is remarkably easy to revoke privileges, even those of the owner ”so beware! You can change the permissions applied to the message queue back to the default values supplied by the operating system using the ResetPermissions method (assuming you still have sufficient privileges to do so).

Also note that even though any changes you make to the properties of a message queue will be propagated back to Active Directory on the domain controller, message queue properties are also cached in MessageQueue objects in the applications that are using them. If you've instantiated a MessageQueue object and suspect that its permissions have since been changed (by another application), you can invoke its Refresh method to obtain its updated properties from Active Directory.

Enumerating Message Queues

The message queuing infrastructure provides methods that allow you to enumerate the queues available in your domain or workgroup (security permitting, of course). The static method GetPublicQueues of the MessageQueue class returns an array containing all the public message queues found in Active Directory. The following line of code creates a list of public queues and stores it in the queues array:

 MessageQueue[]queues=MessageQueue.GetPublicQueues(); 

You can iterate through this array, querying each queue as required. The GetPublicQueues method is overloaded, and its other variant allows you to specify a selection of criteria that can be matched against properties of message queues. Only queues whose properties match these criteria will be retrieved. You create a System.Messaging.MessageQueueCriteria object to hold this information. You can specify a limited number of criteria: CreatedAfter , CreatedBefore , ModifiedAfter , ModifiedBefore , MachineName , Label , and Category . (The Category property of a message queue allows you to attach the same user-generated GUID to a set of related queues.)

The following example filters the list to return only the queues created before January 1, 2002, on the machine called Alice (my domain controller).

 MessageQueueCriteriacriteria=newMessageQueueCriteria(); criteria.set_CreatedBefore(newDateTime(2002,1,1)); criteria.set_MachineName("Alice"); MessageQueue[]queues=MessageQueue.GetPublicQueues(criteria); 

Note

The MessageQueue class also contains shorthand versions of GetPublicQueues that save you from creating a MessageQueueCriteria object. These methods are GetPublicQueuesByCategory , GetPublicQueuesByLabel , and GetPublicQueuesByMachine .


The GetPublicQueues method family returns a copy of the message queues identified. Although you can use these copies for sending and receiving messages, you cannot change their properties. If you want to modify the properties of a message queue, you should instead invoke the GetMessageQueueEnumerator of the MessageQueue class, which returns a MessageQueueEnumerator . The enumerator is used in much the same way as a MessageEnumerator ” you call the MoveNext method to iterate through message queues and read the Current property to obtain a reference to the current message queue. The GetMessageQueue ­Enumerator method can optionally take a MessageQueueCriteria parameter.

Note

The GetPublicQueues method family and the GetMessageQueueEnumerator method obtain message queue information from Active Directory. If the domain controller is down or Active Directory is not available, these methods will fail.


In addition to iterating through public message queues, you can obtain lists of private message queues on a particular computer. The GetPrivateQueuesByMachine method expects the name of a computer as its parameter and returns an array of MessageQueue objects:

 MessageQueue[]queues=MessageQueue.GetPrivateQueuesByMachine("Alice"); 

Unlike the GetPublicQueues method family, the GetPrivateQueuesByMachine method contacts the specified machine directly to obtain the list of private queues and will therefore function correctly even if a domain controller is not available and Active Directory is not operational.

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net