This section provides a brief overview of the well-known queue types that are available to MSMQ programmers. Some of these queues, such as journal queues and dead letter queues, are created automatically by MSMQ. Others, such as administration queues and response queues, let you tell MSMQ and other applications where you want them to send messages that correspond to a message that you have sent.
MSMQ includes an auditing feature called journaling. Journaling allows you to store a copy of a message for auditing purposes. You can thus easily track messages that have already been sent and received. Two types of journaling are available: target journaling and source journaling.
Target journaling lets you make copies of incoming messages from the perspective of the destination queue. You can enable this feature on a queue-by-queue basis either programmatically or using the MSMQ Explorer. When you set the Journal property of a queue to True, MSMQ makes a copy of each message as it's removed from the queue. MSMQ places this copy in the queue's journal, which is another system-supplied queue inside the destination queue. When you create a queue, MSMQ creates a journal queue inside it. You can inspect a queue's journal programmatically or manually with the MSMQ Explorer to audit the messages that have been sent and received.
Source journaling lets you make copies of outgoing messages from the perspective of the message sender. You turn on source journaling by setting the Journal property of a message. Each computer has its own journal queue. A dependent client maintains its journal queue on an MSMQ server, but all other MSMQ computers maintain their journal queue locally. To enable source journaling on a message-by-message basis, you can write the following code:
msg.Journal = MQMSG_JOURNAL msg.Send q
Nontransactional messages are journaled as soon as the message is sent from the sender's computer to the next computer in the routing scheme. The message might have reached the computer holding the destination queue, but it's also possible that the message has only made it to a routing server on its way to the destination queue. A journal entry for a nontransactional message doesn't guarantee that the message has been delivered or will be delivered. When you journal a transactional message, however, the message isn't journaled until the message has reached its destination queue.
Dead letter queues keep a copy of messages that couldn't be delivered. Many things can prevent a message from being delivered. For example, a message might have been sent to an unknown destination queue, or the destination queue's quota might have been reached. Another reason for failed delivery is that a message can expire. For example, the sender of a message can assign a maximum time for the message to be delivered and a maximum time for it to be received. Look at the following code:
msg.MaxTimeToReachQueue = 10 msg.MaxTimeToReceive = 120 msg.Journal = MQMSG_JOURNAL + MQMSG_DEADLETTER msg.Send q
If this message doesn't arrive at its destination queue within 10 seconds or the message isn't received within 2 minutes, it expires. When a message expires, it is sent to a dead letter queue.
When the computer holding a nontransactional message has determined that the message can't be delivered, it stores the message in its local dead letter queue. The computer that stores the message in its dead letter queue can be the sender's computer, the destination computer, or any MSMQ server in between. This situation makes it fairly hard to use dead letter queues for nontransactional messages in a large enterprise.
When a transactional message can't be delivered, it is sent to a special queue on the sender's computer named the Xact Dead Letter queue. The sender of a transactional message can examine its Journal queue and its Xact Dead Letter queue to determine the status. The sender can thus determine whether the message has been delivered, has failed, or is still en route.
Administration queues are somewhat like journal queues and dead letter queues in that they let you determine when a message has been delivered or has failed. However, administration queues can provide a lot of other information as well. These queues are unlike journal queues and dead letter queues because you create them yourself. Once you designate a queue as an administration queue, you can prepare an MSMQQueueInfo object and assign it to the AdminQueueInfo property of a message:
Set q = qi.Open(MQ_SEND_ACCESS, MQ_DENY_NONE) ' Set up admin queue. Dim qiAdmin As MSMQQueueInfo Set qiAdmin = New MSMQQueueInfo qiAdmin.PathName = ".\MyAdminQueue" msg.Ack = MQMSG_ACKNOWLEDGMENT_FULL_RECEIVE Set msg.AdminQueueInfo = qiAdmin ' Send message. msg.Send q
The Ack property of a message tells MSMQ which type of acknowledgment messages you want to receive in the administration queue. Consult the MSMQ Programmer's Reference to see what other possibilities are available. Note that MSMQ places the ID of your message into the CorrelationId of any message that it sends to the administration queue.
The last type of queue I'll cover is a response queue. When an application sends a message to a queue, it is often desirable to have the receiver send a response message back to the sender. When you send a message, you can pass information in the message header that lets the receiver open a response queue on the sender's computer.
Each MSMQMessage object has a ResponseQueueInfo property. You must set up this property using an MSMQQueueInfo object, just as you would with the AdminQueueInfo property. The receiving application can use the ResponseQueueInfo to open the queue and send a response message. It's usually a good idea to place the ID of the original message into the CorrelationId of the response message. You should consider using private queues instead of public queues when setting up response queues in an application that has many clients.
The following code listings show how to set up the sending application and the receiving application to get all of this working. The first listing shows the code for setting up the sending application with a request queue. The listing that immediately follows shows the code that is required in the receiving application to receive the message, open the response queue, and send a response message. This example also includes several other programming techniques that were presented in this chapter, including using private queues, events, and internal transactions.
'******************* Const sRequestPath = "MyServer\MyRequestQueue" Private qRequest As MSMQQueue Private qResponseInfo As MSMQQueueInfo Private qResponse As MSMQQueue Private WithEvents qResponseEvents As MSMQEvent Private Sub Form_Load() ' Open request queue. Dim qi As New MSMQQueueInfo qi.PathName = sRequestPath Set qRequest = qi.Open(MQ_SEND_ACCESS, MQ_DENY_NONE) ' Open response queue (creating it if required). Set qResponseInfo = New MSMQQueueInfo qResponseInfo.PathName = ".\Private$/MyResponseQueue" qResponseInfo.Label = "Local Response Queue" On Error Resume Next ' Ignore error if queue exists. qResponseInfo.Create True ' Create transactional queue. On Error GoTo 0 ' Turn standard error handling back on. Set qResponse = qResponseInfo.Open(MQ_RECEIVE_ACCESS, _ MQ_DENY_NONE) ' Set up event to monitor incoming response messages. Set qResponseEvents = New MSMQEvent qResponse.EnableNotification qResponseEvents End Sub Private Sub cmdSendMessage_Click() Dim q As MSMQQueue Dim msg As New MSMQMessage msg.Body = txtBody.Text Set msg.ResponseQueueInfo = qResponseInfo msg.Send qRequest, MQ_SINGLE_MESSAGE End Sub Sub qResponseEvents_Arrived(ByVal Queue As Object, ByVal Cursor As Long) Dim q As MSMQQueue, msg As MSMQMessage Set q = Queue Set msg = q.Receive(Transaction:=MQ_SINGLE_MESSAGE, _ ReceiveTimeOut:=0) If Not msg Is Nothing Then lstResponses.AddItem msg.Label & " : " & msg.Body Else MsgBox "something went wrong" End If Queue.EnableNotification qResponseEvents End Sub '************************ '************************ Const qRequestPath = "MyServer\MyRequestQueue" Dim qr As MSMQQueue Private WithEvents qRecEvents As MSMQEvent Private Sub Form_Load() Dim qi As New MSMQQueueInfo qi.PathName = qRequestPath Set qr = qi.Open(MQ_RECEIVE_ACCESS, _ MQ_DENY_RECEIVE_SHARE) Set qRecEvents = New MSMQEvent qr.EnableNotification qRecEvents End Sub Sub qRecEvents_Arrived(ByVal Queue As Object, ByVal Cursor As Long) ' Set up transaction. Dim td As MSMQTransactionDispenser Set td = New MSMQTransactionDispenser Dim tx As MSMQTransaction Set tx = td.BeginTransaction() ' Receive message. Dim msg As MSMQMessage Set msg = Queue.Receive(Transaction:=tx, ReceiveTimeout:=0) If msg Is Nothing Then ' Something went wrong. MsgBox "oops!" tx.Abort Else ' Process the request. lstRequests.AddItem msg.Label & " : " & msg.Body lstRequests.Refresh Dim qResp As MSMQQueue Set qResp = msg.ResponseQueueInfo.Open(MQ_SEND_ACCESS, _ MQ_DENY_RECEIVE_SHARE) Dim msgResp As New MSMQMessage msgResp.Label = "Receipt for: " & msg.Label msgResp.Body = msg.Body msgResp.CorrelationId = msg.Id msgResp.Send qResp, tx tx.Commit End If Queue.EnableNotification qRecEvents End Sub
This chapter showed you many basic techniques for programming queues and messages with MSMQ. ActiveX components make some things remarkably easy. However, as you get deeper and deeper into an MSMQ project, you'll find yourself constantly scanning the MSMQ type library with the Object Browser and the MSMQ Programmer's Reference to find some new method, property, or constant. And every new release of MSMQ is sure to add more to what is already available. In this chapter, you saw the nuts and bolts of MSMQ programming. In Chapter 12, I'll show you how to incorporate MSMQ applications into a large information system based on MTS.