There are times when the functionality you need on your page takes an awful lot ofprocessing. This processing may take so long that the ASP page expires on the client or,even worse, your user gives up and goes to a different site. One possible solution forthis problem is to use Microsoft Message Queue (MSMQ) to off-load the processing toanother system.
Microsoft Message Queue
MSMQ (code name "Falcon") is a service that runs in Windows NT, whichprovides asynchronous communication between applications. It comes with NT4 Option Packand plays a part in Microsoft¡¯s DNA Architecture.
There are seven different installs for MSMQ, principally Primary EnterpriseController (PEC), Independent Client and Dependent Client. PECserver is the one install option that runs the MSMQ network for enterprises, and only oneinstallation is needed. The Independent client install is typically installed on a webserver. An independent client can store messages locally, so it can function even if thePEC is unavailable. A Dependent Client install does not have its own local queue andtherefore will not function unless the PEC is available.
The basic concept of MSMQ is very simple: it is email for applications. A message ispackaged into a container of some kind and then stored in a queue at a particulardestination until the recipient is ready to read it. These queues mean that MSMQ canguarantee message delivery, regardless of the state or reliability of a networkconnection.
Just like an email message, MSMQ messages have a sender and receiver, where thereceivers are those who have access to the queue (NT¡®s security). So a single messagein a single queue can have multiple receivers - known as the responder. The senderof the message is usually the web server (IIS) and the responder is a custom applicationthat resides on a different machine. Although the custom application doesn¡¯t have tobe on a different machine, the system allows greater scalability.
MSMQ can also communicate with other messaging systems. Level 8 Systems isdeveloping a third-party solution to communicate with MSMQ on platforms such as SunSolaris, HP-UNIX, OS/2, VMS, and AS/400 platforms. For more information check out theirweb site at http://www.level8.com.
MSMQ architecture comprises one to N servers that route, send, and receive messages.Each one of these servers may or may not contain multiple message queues, and within eachqueue may reside one or more messages. Independent client installations can also containmultiple messages within multiple queues.
Just like most BackOffice services, MSMQ has a COM API ( mqoa.dll
) towhich developers can program. Although MSMQ¡¯s object model is comprised of nineclasses, most applications only need to utilise three of these: MSMQQueueInfo
,MSMQQueue
, and MSMQMessage
.
MSQMQueueInfo
MSMQQueueInfo
allows you to create, open or delete message queues. Inorder to work with a queue the PathName
must first be set, a property whichnames the queue and tells MSQM on which machine the queue can be located.
<%
Dim objQueueInfo
Dim objQueue
Set objQueueInfo=Server.CreateObject("MSMQ.MSMQQueueInfo")
objQueue.PathName = ".\MyQueue"
Set objQueue = objQueueInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE)
%>
The code opens a queue named MyQueue
located on the local computer. First,a variable is dimensioned (optional, but good practice), and then an instance of the MSMQQueueInfo
class created. The next line sets the name and location of the desired queue and the lastline opens the queue. If the queue is located on a different machine simply replace theperiod with the server name.
objQueue.PathName = "SomeOtherComputer\MyQueue"
When opening a queue, two parameters must be specified: Access
and ShareMode
.Access
specifies what is going to be done with the queue. The possible valuesfor this parameter are defined in the MQACCESS
enumerator. The MQACCESS
enumerator has three possible values: MQ_PEEK_ACCESS
(32), MQ_RECEIVE_ACCESS
(1), and MQ_SEND_ACCESS
(2). MQ_PEEK_ACCESS
is used to look(peek) at messages within the specified queue, but it does not remove them. When MQ_RECEIVE_ACCESS
is specified, messages in the queue are removed as they are read. MQ_SEND_ACCESS
is used to send messages to the queue, not to receive messages. Note that the open
routine returns a reference to a MSMQQueue
object. This object is whatmessages are sent and received from. The MSMQQueue
is further discussedbelow.
Creating and deleting message queues is performed much like opening a queue. Set the PathName
and then call the Create
or Delete
routine.
<%
Dim objQueue
Set objQueue = Server.CreateObject("MSMQ.MSMQQueueInfo")
objQueue.PathName = ".\MyQueue"
objQueue.Create
%>
<%
Dim objQueue
Set objQueue = Server.CreateObject("MSMQ.MSMQQueueInfo")
objQueue.PathName = ".\MyQueue"
objQueue.Delete
%>
MSMQQueue
The MSMQQueue
class represents an opened queue that resides the inMSMQ service. This class provides the ability to loop through the messages within a queuein a cursor-like manner. You can't open a queue using the MSMQQueue
class; todo that you ned to use the MSQMQueueInfo
(see above).
Even though most ASP applications that utilise MSMQ usually send messages, there aretimes when ASP pages, administrative pages for example, need to open a queue and displaythe messages. Most MSMQ applications also need a server-side piece to respond to themessages as they are received and act accordingly.
Messages can be retrieved from a queue synchronously or asynchronously, although ASPpages can only retrieve messages synchronously. This is because ASP pages can not declarea sever-side variable WithEvents
, which makes sense since the life of avariable in an ASP page is relatively short. The server application that responds to themessages and processes them, however, really benefits from asynchronous message retrieval.Accessing MSMQ messages asynchronously allows the application to wait for an event to firewhen a new message appears in the queue. This means the server side applicationdoesn¡¯t use up resources while it sits in a loop waiting to receive a message. The MSMQEvent
provides events that the server side application can respond to.
The following code example shows how to open a local queue and respond to messagesasynchronously through the use of the MSMQQueue
and MSMQEvent
classes. (There is a MSMQMessage
class that is used to hold information for amessage and it is returned from the PeekCurrent
routine, which we'll look atfurther on.)
Option Explicit
Dim m_objQueueInfo As New MSMQQueueInfo
Dim m_objQueue As MSMQQueue
Dim WithEvents m_objMSMQEvent As MSMQEvent
Private Sub Form_Load()
'Create the queue, ignoring the error if it's already there
m_objQueueInfo.PathName = ".\MyQueue"
m_objQueueInfo.Label = "My Sample Queue"
'If queue already exists iqnore it
On Error Resume Next
m_objQueueInfo.Create
On Error GoTo 0
'open the queue
Set m_objQueue = m_objQueueInfo.Open(MQ_RECEIVE_ACCESS, _ MQ_DENY_NONE)
'link the events
Set m_objMSMQEvent = New MSMQEvent
m_objQueue.EnableNotification m_objMSMQEvent, MQMSG_CURRENT, 1000
End Sub
Private Sub m_objMSMQEvent_Arrived(ByVal Queue As Object, _
ByVal Cursor As Long)
'process the message
Dim m_objMessage As MSMQMessage
Set m_objMessage = Queue.PeekCurrent
MsgBox "Message Received: " & m_objMessage.Label
'link the event for the next message
m_objQueue.EnableNotification m_objMSMQEvent, MQMSG_NEXT, 10000
End Sub
Private Sub m_objMSMQEvent_ArrivedError(ByVal Queue As Object, _
ByVal ErrorCode As Long, ByVal Cursor As Long)
'something went wrong!
MsgBox "Error accorded: " & ErrorCode
End Sub
This code starts out with the Form_Load
event and creates a queue if onedoesn¡¯t exist already. Next, the m_objMSMQEvent
object is set up and andlinked by calling the EnableNotification
method on the MSMQQueue
object. Once the MSMQEvent
object is linked, the only thing left to do isfulfill the Arrived
and Arrived_Error
(optional) events. The Arrived
event will fire every time a new message is sent to the queue (as long as the EnableNotification
method was called following the occasion that a message was read). This event returns tworeferences, one to the queue that the message can be read from and one to the Cursor
.If an error occurs, the ArrivedError
event will fire with reference to thequeue object, a long variable holding the error number, and a reference to the cursor
.
When retrieving messages synchronously the code execution is halted until a message isavailable or the time-out expires. The following code opens a queue and peeks at all ofthe messages it contains:
Public Sub DisplayMessages()
Dim objQueueInfo As New MSMQQueueInfo
Dim objQueue As MSMQQueue
Dim objMessage As MSMQMessage
'Create the queue, ignoring the error if it's already there
objQueueInfo.PathName = ".\MyQueue"
objQueueInfo.Label = "My Sample Queue"
'If queue already exists iqnore it
On Error Resume Next
objQueueInfo.Create
On Error GoTo 0
'open the queue
Set objQueue = objQueueInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE)
'Loop through all the messages in the queue
Do While True
Set objMessage = objQueue.Peek(, , 1000)
If objMessage Is Nothing Then Exit Do
MsgBox "Message: " & objMessage.Label
Loop
MsgBox "No more new messages."
'clean up
objQueue.Close
Set objMessage = Nothing
Set objQueue = Nothing
Set objQueueInfo = Nothing
End Sub
The code above starts off the same as the asynchronous code, but instead of using the MSMQEvent
class, it enters a Do While ¡ Loop until the Peek routine of the MSMQQueueclass returns nothing. This routine returns nothing when the time-out expires, which inthis case is set to 1000 milliseconds.
MSMQMessage
The MSMQMessage
class holds all of the attributes for a message in aqueue. MSMQ messages have two methods and a great deal of attributes. I'm going to focuson the two main attributes - Body
and LabeL -
and the one mainmethod - Send
.
A MSMQMessage
is designed to hold all sorts of information. In fact, the Body
of the message can hold a string, byte array, and any numeric types. The body of a messageis also designed to hold the state of any COM object that supports the IPersist andIDispatch interfaces. The Label
of a message is a string that simplydescribes it - very similar to the title of an email message.
There are two ways to retrieve a message: opening and peeking. A message is removedfrom the queue after it has been opened, but remains in the queue until expiry if it'speeked. The open
and Peek
routine are carried out in the MSMQQueue
class, and both return a reference to the message. The following code opens a message, anddisplays the Body
and Label
.
Private Sub LookForMessage()
Dim objQInfo As New MSMQQueueInfo
Dim objQReceive As MSMQQueue
Dim objMessage As MSMQMessage
'open the queue
objQInfo.PathName = ".\test" 'or ".\test"
Set objQReceive = objQInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE)
'look for a message (time-out set to 100 ms)
Set objMessage = objQReceive.Receive(, , , 100)
'was there a messsage?
If Not objMessage Is Nothing Then
'display the contents of the message
MsgBox objMessage.Label & " - " & objMessage.Body
Else
'no message
Msgbox "Nothing in the queue"
End If
'clean up
objQReceive.Close
Set objQInfo = Nothing
Set objQReceive = Nothing
Set objMessage = Nothing
End Sub
This code begins by opening a queue and looking for a message within it. The Receive
routine, with a time-out of 100 milliseconds, is called to look for the messages. Thetime-out tells the MSMQ library to stop waiting for a message, and a very low time-outperiod means that the code is just checking for existing messages rather than waiting fora message to be received. If the user wants to see if there is a message they can simplycheck whether the Receive
function has returned a reference to a validmessage or if it returned Nothing (If Not objMessage Is Nothing)
. If theroutine did return a message then the code displays the Label
and the Body
of the message.
Sending a message is the most common task performed with MSMQ in ASP. To send amessage, a Queue must be opened with send access (MQ_SEND_ACCESS
) and amessage must be prepared, before invoking the Send
routine on the MSMQMessage
class.
<%
'dim some variables
Dim objQInfo
Dim objQSend
Dim objMessage
'open the queue
Set objQInfo = Server.CreateObject("MSMQ.MSMQQueueInfo")
objQInfo.PathName = ".\test"
Set objQSend = objQInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE)
'build/send the message
Set objMessage = Server.CreateObject("MSMQ.MSMQMessage")
objMessage.Label = "This is the label."
objMessage.Body = "This is the body."
objMessage.Send objQSend
objQSend.Close
'clean up
Set objQInfo = Nothing
Set objQSend = Nothing
Set objMessage = Nothing
%>
This code opens a queue with send messages access, and the message is assembled bycreating an instance of a message class and setting a Label
and a Body
. The Message is sent by calling the Send routine on the queue and passing a referencethrough a parameter.
Uses for MSMQ
There are many possible uses for MSMQ in an application, but the most common is that ofoff-loading processing to another thread (MSMQ on the same machine as IIS) and/or toanother computer (MSMQ on a different machine as IIS). By moving processing to anotherthread and/or machine the ASP page can continue processing (executing server-side code).
Two things normally determine whether you will want to move task processing: processingtime itself, and whether the user expects feedback or not. Processing can be shiftedelsewhere so long as the user doesn¡¯t expect immediate feedback - recalculatinginventory levels after an order has been placed, for example. If a task on the server-sideprocessing of the web page takes too long, then there is a risk that the page willtime-out on the user and they'll receive an error message, hit refresh or simply give up. Shifting the processing, otherwise known as background processing, not only speedsup the processing time of a page itself, but also benefits the web server scale.
MSMQ also has the ability to hold certain Component Object Model (COM) objects in thebody of the message. The only requirement is that the COM objects support IDispatch
and IPersist
( IPersistStream
or IPersistStorage
)interfaces. This is really easy to use, and saves masses of coding time and effort. Twoclasses that support these interfaces are ADODB.Recordset
(and ADOR.Recordset
) and Word.Document
. This final code example sends an ADODB.Recordset
as the body of a MSMQMessage
.
Public Sub SendRecordsetInMessage()
Dim objQInfo As New MSMQ.MSMQQueueInfo
Dim objQSend As MSMQ.MSMQQueue
Dim objMessage As New MSMQ.MSMQMessage
Dim objRS As New ADOR.Recordset
Dim a As New MSMQQueue
'build/fill the recordset
With objRS
'build the recordset structure
.CursorLocation = adUseClient
.Fields.Append "FN", adVarChar, 25
.Fields.Append "LN", adVarChar, 25
.Open
'add a record
.AddNew
.Fields("FN") = "Chris"
.Fields("LN") = "Blexrud"
.Update
'add a record
.AddNew
.Fields("FN") = "Shayna"
.Fields("LN") = "Blexrud"
.Update
End With
'build/get the queue
objQInfo.PathName = ".\test"
Set objQSend = objQInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE)
'build/send the message
objMessage.Label = "Recordset State!!!!"
objMessage.Body = objRS
objMessage.Send objQSend
objQSend.Close
¡®clean up
Set objQInfo = Nothing
Set objQSend = Nothing
Set objMessage = Nothing
Set objRS = Nothing
End Sub
When the recordset is retrieved from the message it is Set
to the Body
of the message, because the Body
of the message is stored as a Variant
Set objRS = objMessage.Body
Considerations
There is one difficulty with developing applications with MSMQ - the lack of feedback.Although it makes sense in the architecture of MSMQ, some tricky code is called for if youwant to find the results of a task that was assigned to another process through a MSQMmessage.
There are ways to get around this problem. You could send a message back to the user,so if the user wants to know if the task was carried out they can check the queue. Or youcan use a table in a database to track tasks assigned through MSMQ and define a set ofstandards to be fulfilled by each MSMQ user. Then users can lookup the tasks in a table tosee if they have been processed, and whether they succeeded or failed.
One final note - the install footprint of MSMQ is quite large. The server installrequires SQL Server (6.5 or 7) to be installed first, in order to keep track of thevarious queue information. Although the client install doesn¡¯t require a preinstalleddatabase, its footprint is still pretty large.