Messaging

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.

Messages

A 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

Priority

Description

Highest

Highest message priority

VeryHigh

Between Highest and High message priority

High

High message priority

AboveNormal

Between High and Normal message priority

Normal

Normal message priority

Low

Low message priority

VeryLow

Between Low and Lowest message priority

Lowest

Lowest message priority

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 Queues

A 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 :

  • Response queues

    These are queues designated for responses. When you send a message, you can specify a desired response. That response from the target queue ends up in a response queue.

  • Journal queues

    You can think of a journal queue as a kind of a subqueue. It allows you to keep a log, or journal, of received messages, which is typically used as an auditing mechanism. You cannot create standalone journal queues.

  • Dead letter queues

    These queues are repositories for messages that could not be delivered. Messages can end up here for a variety of reasons. The target queue might have refused the message, or a message's expiry timeout might have been exceeded. You cannot create dead letter queues. These queues are also used for auditing.

  • Administration queues

    Administration queues are another way to provide an explicit auditing trail and retain more information than might be found in either a journal queue or a dead letter queue. You can create your own administration queues.

Transactional Queues

Transactional 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 Components

A 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 .NET

Messaging 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 Class

As 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 Class

The 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:

  • Create new queues.

  • Send and retrieve messages from queues.

  • Set the default properties for messages set to the queue. (This is valid only for the current instance of the MessageQueue class.)

  • Create an object reference to deal with a remoted component. (This is the mechanism that provides support for Queued Components.)

  • Administer a queue's properties (if the user context has permissions).

  • Asynchronously retrieve messages.

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 Queue

Manually 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.

graphics/f08pn08.jpg

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:

  • Authentication

    The Boolean Authenticate property allows to you specify whether the queue accepts only authenticated messages.

  • Encryption

    The EncryptionRequired property has three possible values. None indicates that received messages cannot be encrypted (that is, the message appears in clear text). Optional , the default for new queues, allows both encrypted and unencrypted messages to be received. Setting the property to Body requires all received content to be encrypted to be accepted.

  • Queue size

    The MaximumQueueSize property specifies the maximum size of the queue.

    Figure 8-9. The Properties window for a queue selected in Server Explorer.

    graphics/f08pn09.jpg

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:

  1. Open the Visual Basic IDE.

  2. Expand Server Explorer.

  3. Expand the node for your local machine, expand Message Queues, and then expand Private Queues.

  4. Right-click on the Private Queues node, and click Create Queue.

  5. Enter a name for the Queue (in this case, MyQueue), and click OK.

That's it. You now have a real messaging queue. Now let's try using it.

Working with Your Queue

To 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 Asynchronously

The 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:

  1. Create a method to process a message. It must correspond to the signature of a ReceiveCompletedEventHandler delegate.

  2. Create a MessageQueue object.

  3. Use AddHandler to add the message processing method to the ReceiveComplete event of the MessageQueue class.

  4. Call MessageQueue.BeginReceive .

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 Messages

So 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  Class 
ToAccount'{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  Class 
FromAccount'{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.

graphics/f08pn10.jpg

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 Installation

Manually 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


Designing Enterprise Applications with Microsoft Visual Basic .NET
Designing Enterprise Applications with Microsoft Visual Basic .NET (Pro-Developer)
ISBN: 073561721X
EAN: 2147483647
Year: 2002
Pages: 103

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