I l @ ve RuBoard |
Messaging allows an application to enable asynchronous communication between two systems in a robust manner. These systems can reside in the same process, in separate processes on the same machine, or in separate processes on distributed machines. Messaging hides these complexities so the sender of a message doesn't care where the receiver is. The two components to messaging are messages and message queues. MessagesA message is simply a packet of information. You can send a string as a message, or you can send a serialized object (or set of objects). Messages can have priorities, which allow them to skip ahead of preexisting messages in a queue. Table 8-5 lists the available message priorities. Table 8-5. Available Message Priorities
A message can contain not only information about itself, but information about what it expects the receiving queue to do. This category of functionality includes features such as acknowledgements, where the message fits into a transaction (first or last, or in the middle), the response queue, and the source machine. More Info For more information about serialization, see Chapter 5. I will refer to many of the concepts covered in that chapter as we work with our upcoming messaging example. Message QueuesA message is sent by an application to a specific queue. This can be a local queue or a queue on another machine. Where this queue resides is not important. The message is received by the queue and is stored in an ordered list, which preserves the original arrival ordering of messages (ignoring the concept of priority for the moment). Note Regardless of whether there exists a client process that is retrieving the messages, if the message queue exists, messages will be stored. This helps protect against process failures, which can happen from time to time. Another process, or possibly the same process, retrieves messages from the queue. All messages are retrieved in a first-in-first-out (FIFO) manner. The process retrieving the queue entries processes each entry in turn until the queue is exhausted (empty). Simple, right? Not quite. Messaging can also provide certain guarantees . So you can send a message and guarantee, within reason, that the message will arrive . Even if the network connection between the systems goes down, the message will be stored and eventually sent when the connection is reestablished. In addition to the standard queuing functionality, a robust communication mechanism is provided. This allows you to ensure, within reason, eventual delivery of a message regardless of application failures, transient network conditions, power failures, and so forth. As long as a message has been queued, you can ensure that it will be delivered. This reliability feature, also called recoverability , causes your message to be stored on the local machine before it is sent to the destination message queue. Recoverability is the default but is optional ”for reasons of performance. Enabling recoverability reduces the number of messages per second that your system can generate. Yet again, you have the choice of sacrificing optimal performance for reliability. There are two basic types of message queues: public and private. Public queues are published in Active Directory and require more time and disk space to set up. One advantage of public queues is that they can all be administered and controlled centrally . You can set your security policies and other information in a single location. Private, or local, queues are not globally published. However, private queues are available to other systems on the network. Private queues must be administered on a machine-by-machine basis. There are also four types of what are known as auxiliary queues :
Transactional QueuesTransactional queues are designed to ensure exactly-once delivery of messages. When you create a queue, you have the option of making it a transactional queue. Messages in the same transaction arrive in the same order they were sent. If multiple transactions are sending messages at the same time, they will likely be interleaved in the same queue, but the ordering of the messages within a transaction is guaranteed . One downside to transactional queues is that they cannot provide support for message priorities ”all messages are priority 0. Transactional queues include support for both internal and COM+ transactions. This means that if you have implemented a transactional component in COM+ that also uses a transactional queue, your transactional context will automatically flow to the messaging system, much like it did with our database example. Queued ComponentsA queued component is a COM+ component that transparently uses messaging as an invocation mechanism. You can create a client-side object from a queued component and invoke method calls as you would normally. Under the covers, the method calls are translated into a series of messages and are sent to a special queue. From this queue, the messages are retrieved and translated into the intended method with the passed parameters and executed. Messaging in Visual Basic .NETMessaging in Visual Basic .NET is implemented in the System.Messaging namespace. This namespace and its classes are contained in the System.Messaging.dll assembly. The two major classes that you need to be familiar with are Message and MessageQueue . The Message ClassAs I've said, a message is simply a packet of information. Messages are sent to, and retrieved from, message queues. The Message class encapsulates everything about a message and makes the configuration options and content easily accessible. You can specify all sorts of information about a message, including encryption algorithms, security options, and digital signatures. The message class is what is sent by clients to queues and what is returned. The MessageQueue ClassThe MessageQueue class allows you to manage and manipulate messaging queues. An instance of the MessageQueue class represents a single queue. You can create more than one MessageQueue object that points to a single queue, if you want to. Here are some of the things you can do using the MessageQueue class:
All of this a just a bunch of generalities. What we need is a good, solid example. First, we'll create a real message queue. Creating Your First QueueManually creating messaging queues is a snap, thanks to the Visual Basic .NET IDE. The Server Explorer feature is often overlooked but is extremely powerful (See Figure 8-8.) From this window, you can inspect all of the queues available on a machine, as well as create new ones. Figure 8-8. Using Server Explorer to manage queues.
Note You must install the message queuing software through the Windows Installer if you have not already done so. Without it , you cannot use any of the System.Messaging features. Selecting an existing queue in Server Explorer also exposes a number of properties through the Properties window, shown in Figure 8-9. From here, you can manipulate the following queue properties:
Manually creating your own queue is a snap. To create your own messaging queue ”in this case, a private non-transactional one ”just follow these simple steps:
That's it. You now have a real messaging queue. Now let's try using it. Working with Your QueueTo work with any queue, you must first add a reference to the System.Messaging.dll assembly to your project to have access to the System.Messaging namespace. To help clarify what else you need to do, let's take a look at a simple messaging example: Console.WriteLine("AttachingtotheMessagequeue") DimmqAsNewMessageQueue(".\Private$\MyQueue") Console.WriteLine("Sendingamessage...") mq.Send("Thisisatest") Console.WriteLine("Detachingfromthequeue") mq.Dispose() You can see how easy it is to send a message. You create an instance of a MessageQueue class, specifying the path to the queue. In this case, we're accessing the local private queue called MyQueue that you created earlier. We then send a String object to the queue. The MessageQueue class obscures the fact that the String is first serialized, then added to a newly created message, and then sent to the target queue. As soon as we send the message, we dispose of the MessageQueue object (which doesn't affect the target queue ”it just releases the resources of the MessageQueue object). On the receiving side, things are only a little more complicated: Console.WriteLine("AttachingtotheMessagequeue") DimmqAsNewMessageQueue(".\Private$\MyQueue ") DimformatterAsXmlMessageFormatter=CType(mq.Formatter,_ XmlMessageFormatter) formatter.TargetTypeNames()=NewString(){"System.String"} m=mq.Receive() Console.WriteLine(m.Body()) Console.WriteLine("Detachingfromthequeue") mq.Dispose() Here you can see some similarities. We still create an instance of the MessageQueue class, pointing to the same queue as in our previous example. The next step is a little different, however. Because the message contents have been serialized, we must first provide a formatting mechanism to deserialize the object. In this case, we know that the XmlMessageFormatter is used (because it's the default), so all we have to do is tell the formatter what kind of object it is trying to deserialize (in this case, a System.String object). Note Two other formatters are available besides XmlMessageFormatter : ActiveXMessageFormatter and BinaryMessageFormatter . The BinaryMessageFormatter is the same binary serialization mechanism we encountered in Chapter 5; we'll use it again later with more complex messages. Once we have the formatting issues out of the way, all we have to do is retrieve the Message by calling Receive . Calling the Body method of the Message object will retrieve the original object using the specified message formatter. Next, we detach from the queue. Easy. But wait ”there's a problem. How do you know if there's a message in the queue? What if there are more messages? This way of retrieving messages isn't really that helpful because it ties up a thread waiting for a message. You can specify a timeout parameter for receive, but that still doesn't really solve the problem. It would be much more interesting if the queue could tell us when a message is available rather than your application continuously polling for messages that might or might not be in the queue. This is where receiving messages asynchronously comes into play. Receiving Messages from a Queue AsynchronouslyThe MessageQueue class provides support for receiving messages and processing messages asynchronously. The two methods of interest are BeginReceive and EndReceive . To begin an asynchronous read, you need to do the following:
If a message arrives or one already exists in the queue, the MessageQueue class will signal your application by calling your message-handling method. The event will be fired only once for each call to BeginReceive , so if you want to receive a continuous set of messages, your message handler method should call BeginReceive before it exists. This will cause your handler method to be called repeatedly as long as there are messages in the queue. If the queue is emptied, your method will be called as soon as a new message is received. Here's what this looks like in action: SubMain() Console.WriteLine("AttachingtotheMessagequeue") DimqueueAsNewMessageQueue(".\Private$\MyInstalledQueue") DimformatterAsXmlMessageFormatter=_ CType(queue.Formatter,XmlMessageFormatter) formatter.TargetTypeNames()=NewString(){"System.String"} AddHandlerqueue.ReceiveCompleted,AddressOfMessageReceived queue.BeginReceive() Console.ReadLine() queue.Dispose() EndSub PublicSubMessageReceived(_ ByValsourceAsObject,_ ByValasyncResultAsReceiveCompletedEventArgs) 'StorethesourceinaMessageQueuereferenceforeasyaccess DimqueueAsMessageQueue=CType(source,MessageQueue) 'Getthemessageandcompletethereceiveoperation DimmAsMessage=queue.EndReceive(asyncResult.AsyncResult) Console.WriteLine(m.Body()) 'Restarttheasynchronousreceiveoperation. queue.BeginReceive() EndSub Sending More Sophisticated MessagesSo far, I've demonstrated only how to send strings in the message content, which is pretty boring and not very useful. Let's look at a something more interesting: a complex type. Imagine you're designing an ATM-based application. A customer walks up to the ATM and withdraws 100 dollars. The ATM first checks the account balance and then delivers the money to the customer. Now the ATM must post the result of the transaction to its central data store to update the customer's bank balance. You'd probably want to provide some degree of assurance that, regardless of what happens, the transaction will be posted to the account. Let's look at what information we need to provide. First, we define the Account object (which would typically be in a database): PublicClassAccount PublicSubNew(ByValnameAsString,ByValidAsInteger,_ ByValbalanceAsSingle) Me.Name=name Me.Id=id Me.Balance=balance EndSub PublicNameAsString PublicIdAsInteger PublicBalanceAsSingle EndClass Now we define a base transaction class called, strangely enough, Transaction . All of our real transaction classes, Deposit and Withdrawal , derive from this base class. This allows our message handler to deal with transactions in a generic way rather than special- casing every possibility. Each class, including the base Transaction class, is marked with the Serializable attribute because I intend to use binary serialization (the most efficient serialization method) to serialize these types. <Serializable()>PublicMustInheritClassTransaction Publicm_AccountIdAsInteger Publicm_AmountAsSingle PublicPropertyAccountId()AsInteger Get Returnm_AccountId EndGet Set(ByValValueAsInteger) m_AccountId=Value EndSet EndProperty PublicPropertyAmount()AsSingle Get Returnm_Amount EndGet Set(ByValValueAsSingle) m_Amount=Value EndSet EndProperty PublicMustOverrideSubUpdate(ByValacctAsAccount) EndClass <Serializable()>PublicClassDeposit InheritsTransaction PublicOverridesSubUpdate(ByValacctAsAccount) acct.Balance+=Amount Console.WriteLine(_ "Depositing<Serializable()> Public MustInherit Class Transaction Public m_AccountId As Integer Public m_Amount As Single Public Property AccountId() As Integer Get Return m_AccountId End Get Set(ByVal Value As Integer) m_AccountId = Value End Set End Property Public Property Amount() As Single Get Return m_Amount End Get Set(ByVal Value As Single) m_Amount = Value End Set End Property Public MustOverride Sub Update(ByVal acct As Account) End Class <Serializable()> Public Class Deposit Inherits Transaction Public Overrides Sub Update(ByVal acct As Account) acct.Balance += Amount Console.WriteLine(_ "Depositing ${0} To Account '{1}' Ending Balance: ${2}", _ Amount, acct.Name, acct.Balance) Return End Sub End Class <Serializable()> Public Class Withdrawal Inherits Transaction Public Overrides Sub Update(ByVal acct As Account) acct.Balance -= Amount Console.WriteLine(_ "Withdrawing ${0} From Account '{1}' Ending Balance: ${2}", _ Amount, acct.Name, acct.Balance) Return End Sub End ClassToAccount'{1}'EndingBalance:",_ Amount,acct.Name,acct.Balance) Return EndSub EndClass <Serializable()>PublicClassWithdrawal InheritsTransaction PublicOverridesSubUpdate(ByValacctAsAccount) acct.Balance-=Amount Console.WriteLine(_ "Withdrawing<Serializable()> Public MustInherit Class Transaction Public m_AccountId As Integer Public m_Amount As Single Public Property AccountId() As Integer Get Return m_AccountId End Get Set(ByVal Value As Integer) m_AccountId = Value End Set End Property Public Property Amount() As Single Get Return m_Amount End Get Set(ByVal Value As Single) m_Amount = Value End Set End Property Public MustOverride Sub Update(ByVal acct As Account) End Class <Serializable()> Public Class Deposit Inherits Transaction Public Overrides Sub Update(ByVal acct As Account) acct.Balance += Amount Console.WriteLine(_ "Depositing ${0} To Account '{1}' Ending Balance: ${2}", _ Amount, acct.Name, acct.Balance) Return End Sub End Class <Serializable()> Public Class Withdrawal Inherits Transaction Public Overrides Sub Update(ByVal acct As Account) acct.Balance -= Amount Console.WriteLine(_ "Withdrawing ${0} From Account '{1}' Ending Balance: ${2}", _ Amount, acct.Name, acct.Balance) Return End Sub End ClassFromAccount'{1}'EndingBalance:",_ Amount,acct.Name,acct.Balance) Return EndSub EndClass Now that we have our types defined, we need to be able to send them. This really isn't that big a deal. In fact, it is trivial. I've created a sample application (actually, two applications ”one a message sender and the other a receiver). (See Figure 8-10.) The first application is the Account Viewer, which is a console application that receives messages and processes the results. The second is a Windows Form application, called ATM, that sends messages to Account Viewer. Shared between the two is a component library, called Bank Library, that contains the definitions for the transactions we just looked at. Figure 8-10. The ATM application and Account Viewer running side-by-side.
When the ATM application first loads, it initializes a private MessageQueue object and specifies a BinaryMessageFormatter . This ensures that all of the messages sent to the queue will use binary serialization. DimqueueAsMessageQueue PrivateSubForm1_Load(ByValsenderAsSystem.Object,_ ByValeAsSystem.EventArgs)HandlesMyBase.Load queue=NewMessageQueue(".\Private$\BankQueue") queue.Formatter=NewBinaryMessageFormatter() EndSub The ATM application allows you to specify an account number, an amount, and whether you want to cause a deposit or withdrawal. The implementation code is pretty simple for both possibilities: PrivateSubDepositButton_Click(ByValsenderAsSystem.Object,_ ByValeAsSystem.EventArgs)_ HandlesDepositButton.Click DimdAsNewDeposit() d.AccountId=CInt(AccountNumber.Text) d.Amount=CSng(Amount.Text) queue.Send(d) EndSub PrivateSubWithdrawButton_Click(ByValsenderAsSystem.Object,_ ByValeAsSystem.EventArgs)_ HandlesWithdrawButton.Click DimwAsNewWithdrawal() w.AccountId=CInt(AccountNumber.Text) w.Amount=CSng(Amount.Text) queue.Send(w) EndSub This is pretty easy. So what does the receiving code look like? Not much more complicated than what we're used to, other than that the application uses asynchronous message processing. (You wouldn't expect anything less, would you?) Ignoring some of the initialization code, you can see that the asynchronous read stuff is nothing to write home about ”you've seen it already. The action is really in the MessageReceived method: OptionStrictOn ImportsBankLibrary ImportsSystem.Messaging ModuleModule1 Dimaccounts()AsBankLibrary.Account PublicSubMain() Console.WriteLine("Startingthebankingserver") Console.WriteLine("Creatingtheaccounts") Dimacct1AsNewAccount("Bob",1,231.23) Dimacct2AsNewAccount("Alice",2,654.01) Dimacct3AsNewAccount("Roy",3,984.65) accounts=NewAccount(){acct1,acct2,acct3} Console.WriteLine("Attachingtothequeue") DimqueueAsNewMessageQueue(".\Private$\BankQueue") AddHandlerqueue.ReceiveCompleted,AddressOfMessageReceived queue.BeginReceive() 'Waitfortheusertoterminatetheapplication Console.ReadLine() queue.Dispose() EndSub PublicSubMessageReceived(_ ByValsourceAsObject,_ ByValasyncResultAsReceiveCompletedEventArgs) 'StorethesourceinaMessageQueuereferenceforeasyaccess DimqueueAsMessageQueue=CType(source,MessageQueue) queue.Formatter=NewBinaryMessageFormatter() 'Getthemessageandcompletethereceiveoperation DimmAsMessage=queue.EndReceive(asyncResult.AsyncResult) DimtAsTransaction Try t=CType(m.Body(),Transaction) CatchexAsException Console.WriteLine(ex.ToString()) EndTry DimiAsInteger Fori=0Toaccounts.Length-1 Ifaccounts(i).Id=t.AccountIdThen t.Update(accounts(i)) ExitFor EndIf Next 'Restarttheasynchronousreceiveoperation. queue.BeginReceive() EndSub EndModule You can see from this sample that you need to explicitly set the MessageFormatter each time you come through the MessageHandler method. Other than that minor complexity, there's nothing in here that should surprise you. The message body is cast to a Transaction class (we don't care what type of transaction), and then its Update method is executed on the proper account. Done. The transaction is responsible for updating the account and spitting the account information to the console. Automating Queue InstallationManually creating your queues works just fine when you're developing your application. Deployment, however, is a completely different story. Some applications must be able to create a new messaging queue on a machine. Thankfully, there is a solution for this. The MessageQueueInstaller class in the System.Messaging namespace is intended to be used by the InstallUtil.exe utility for installing and uninstalling message queues from a machine. As I explained in Chapter 7, the reasons for using the installer services instead of a more manual process are simple. While you might need to install your own messaging queue, you absolutely need to provide some way of cleaning up after yourself. If you use the installer services, cleanup becomes much simpler. The following sample illustrates how you can use this class to define a queue that can be installed using the InstallUtil.exe utility: ImportsSystem.ComponentModel ImportsSystem.Messaging <RunInstaller(True)>_ PublicClassMyQueueInstaller InheritsMessageQueueInstaller PublicSubNew() MyBase.New() Me.Authenticate=False Me.BasePriority=5 Me.EncryptionRequired=EncryptionRequired.Optional Me.Label= "MyInstalledQueue" Me.Path= ".\Private$\MyInstalledQueue" EndSub EndClass Do you have to create your own installer to set up queues? Obviously not. We manually created one earlier. You can also use the MessageQueue class to programmatically create your own queues. The problem is that this process is unnecessarily messy and presents a deployment nightmare. You can use the MessageQueueInstaller class to define all the queues that your application needs in a structured and well organized fashion. Why mess around with manual installation when you can easily take care of all of the installation and uninstall issues in one go? |
I l @ ve RuBoard |