I l @ ve RuBoard |
Many commercial applications have stringent requirements regarding reliability, security, and connectivity. The remainder of this chapter will examine how Message Queuing 3.0 addresses these issues. Reliability and TransactionsEarlier, you learned the basic theory behind using transactions with message queues. The practical implementation of this theory is quite straightforward. First and foremost, transactions can be used only with transactional message queues. (Message queues are nontransactional by default.) You specify whether a message queue supports transactions when you create it. If you're using the Computer Management console in Control Panel or Server Explorer in Visual Studio .NET, the dialog box that specifies the queue details includes a check box that you should select if you want to make the queue transactional. Figure 12-9 shows the Create Message Queue dialog box of Server Explorer. Figure 12-9. The Create Message Queue dialog box in Visual Studio .NET
Alternatively, you can execute the static MessageQueue.Create method and use the optional second parameter. A value of true indicates that the queue is transactional: MessageQueue.Create("WhiteRabbit\CakeOrderQueue",true); You can query the Transactional property of a message queue to determine whether it supports transactions, but once the message queue is built you cannot change this property. To post a message to a transactional queue, you should either create a new transaction or join an existing one. Before going any further, you should understand that message queuing supports two categories of transactions, internal and external. Internal transactions are created using the MessageQueueTransaction class in the System.Messaging namespace, and they are implemented by the message queuing service. External transactions are transactions created and managed by software and services that are not part of the message queuing service ”SQL Server or COM+, for example. External transactions and internal transactions offer similar functionality, but internal message queuing transactions are independent of external transactions. A COM+ component or a SQL Server stored procedure cannot join a transaction created by the message queuing service. However, the message queuing service can join some transactions that were created externally depending on how they're defined. Confused? Let's consider both types of transaction and discuss how to use them. Using Internal TransactionsTo use an internal transaction to send a message, you create a MessageQueueTransaction object, initiate the transactions using the Begin method of the transaction object, perform one or more transactional send operations, and then execute the Commit method of the transaction object. The Send method includes an optional MessageQueueTransaction parameter that specifies the transaction that the Send method call belongs to. The messages become available to a receiver only after the transaction has been successfully committed. The messages will be delivered in the same sequence in which they were sent. If the sender detects an error while posting messages, it can execute the Abort method of the transaction object. This method will remove all messages from the queue that have been sent since the Begin method was executed. The following code fragment shows two send operations to the transactional CakeOrderQueue , which are performed as part of the same transaction: //Sendingapplication MessageQueuecakeOrderQueue=...; MessageQueueTransactiontransact=newMessageQueueTransaction(); transact.Begin(); Messagemessage1=...; Messagemessage2=...; try { cakeOrderQueue.Send(message1, "MessageLabel1",transact); cakeOrderQueue.Send(message2, "MessageLabel2",transact); transact.Commit(); } catch(System.Exceptione) { transact.Abort(); } Note Transactions can be nested, and inner transactions will not complete until the outermost transaction either commits or aborts. If the outermost transaction aborts, all inner transactions that indicated that they would commit will be aborted instead. Furthermore, a nested transaction that aborts will ultimately cause the outermost transaction to abort as well, along with all other nested transactions belonging to the outermost transaction. Also note that if you fail to commit a transaction, it will be aborted when the MessageQueueTransaction object is destroyed . You cannot use the transactional version of the Send method to post a message to a nontransactional queue. (It will throw an exception.) However, you can post a message to a transactional queue without defining a transaction. In this case, the message queuing service will automatically create a transaction for you, begin it, send the message, and commit it when the send operation is complete. This is referred to as a singleton transaction because it comprises a single operation only. One exception to this rule is especially important if you're building applications designed to function in a disconnected environment: If you send a single, nontransactional message to a transactional message queue on a remote computer identified using a direct format name , a transaction will not be created and the message will be posted to the local transactional dead-letter queue instead (provided that you set the UseDeadLetterQueue property of the message ” otherwise , it will disappear silently). You receive messages from a transactional queue using a similar approach. You create a transaction, execute Begin , perform one or more transactional Receive method calls ( Receive can optionally take a MessageQueueTransaction as a parameter), and then execute Commit when all the messages have been successfully received. If an error occurs, you can execute Abort , and all messages received since the Begin method call will be returned to the queue: //Receivingapplication MessageQueuecakeOrderQueue=...; MessageQueueTransactiontransact=newMessageQueueTransaction(); transact.Begin(); try { Messagemessage1=cakeOrderQueue.Receive(transact); Messagemessage2=cakeOrderQueue.Receive(transact); transact.Commit(); } catch(System.Exceptione) { transact.Abort(); } Although transactions guarantee the atomicity of multiple send and receive operations and ensure that messages are received in precisely the same order that they were sent, the programmer is responsible for ensuring that everything that is sent is received. In other words, if a sender posts four messages as a transaction, the receiver should receive four messages ”not three, not five. How does a sender know how many messages comprise a transaction? Each message has a TransactionId property that contains the 16-bit identifier of the sending computer and a 4-byte transaction sequence number. All messages that belong to the same transaction will have the same TransactionId . A receiving application can peek at each message in the queue, examine its TransactionId , and determine whether it is part of the same transaction as the previous message received. ( Peeking is nondestructive and does not count as a transactional operation.) If the TransactionId matches, the message should be received; if not, the final message has been received, so the receive transaction should commit. Alternatively, you can examine the IsFirstInTransaction and IsLastInTransaction properties of a message, which will be true if the message is the first or the last message, respectively, in a transaction. If the transaction contains a single message only, both of these properties will be true at the same time. Caution The TransactionId, IsFirstInTransaction and IsLastInTransaction properties are further examples of message properties that are not retrieved by default. You must set the TransactionId, IsFirstInTransaction and IsLastInTransaction flags of the MessageReadPropertyFilter of the message queue to true before peeking at or receiving messages if you want to query these properties. A final point to consider: Message queue transactions are apartment-aware, so if the apartment state of your application is single-threaded, you will not be able to access the same transaction from multiple threads. Chapter 8 describes how to change the apartment state of a thread. Using External TransactionsAn external transaction is created outside the scope of the message queuing service. When you use the .NET Framework, classes that employ external transactions must inherit from the System.EnterpriseServices.ServicedComponent class. External transactions can then be defined declaratively (as described in Chapter 3). For example, you can tag the QueuedCakeClient class with a TransactionAttribute that forces the creation of a new external transaction when its methods are executed: importSystem.EnterpriseServices.*; /**@attributeTransactionAttribute(TransactionOption.RequiresNew)*/ publicclassQueuedCakeClientextendsServicedComponent { } The Send and Receive methods of the MessageQueue class allow you to specify that the send or receive operation should be part of the external transaction, as in this example: cakeOrderQueue.Send(message1, "MessageLabel1", MessageQueueTransactionType.Automatic); You can commit the transaction using the static SetComplete method of the System.EnterpriseServices.ContextUtil class: ContextUtil.SetComplete(); Alternatively, you can use the SetAbort method to roll back the transaction. (External transactions, the ContextUtil class, and serviced components will be described in detail in Chapter 14.) Message Authentication and EncryptionAn earlier section described how to query and modify the access control privileges associated with an individual message queue. An application can send or receive messages from a message queue only if the principal executing the application has been granted the appropriate access rights. However, in addition to controlling who has access to message queues, it is also important to be able to authenticate the sender of a message and to guarantee the integrity of that message. These considerations become critical when you perform messaging over a WAN or the Internet. Authenticating MessagesAuthenticating the sender of a message is a slightly more complex process than authenticating principals when you use other technologies because of the asynchronous nature of message queues. The security mechanisms cannot assume that the sender is always connected to the computer that holds the destination message queue. To be authenticated, therefore, messages themselves must contain information about the sender. This information takes the form of a digital signature and a certificate. To send an authenticated message, the principal sending the message must have previously obtained a certificate and registered it in Active Directory in the domain that holds the destination queue, associating it with the Windows Security ID (SID) of the principal. A certificate is generated automatically for each user of a computer after message queuing has been installed, and this internal certificate can be registered with Active Directory using the Computer Management console in Control Panel. You select the Message Queuing service, choose Properties from the Action menu, and then click on the User Certificate tab in the Message Queuing Properties dialog box, as shown in Figure 12-10. Click Register to register the current user certificate. Figure 12-10. The User Certificate tab of the Message Queuing Properties dialog box
Certificates created and stored in this way are referred to as internal certificates . If you require a higher level of security, you can also obtain external certificates issued by a certification authority and register them manually in Active Directory. When an authenticated message is posted to a queue, the sender creates a digital signature for the message based on its contents and a private cryptographic key. The sender attaches its SID, the public certificate (containing the public key needed to decrypt the digital signature), and the digital signature to the message as it is sent to the message queue. A receiver can compute its own digital signature for the message, decrypt the signature sent with the message using the public key supplied with the message, and compare the two signatures. If they are the same, the message has not been tampered with. The receiver can then examine the user certificate held in Active Directory for the SID supplied with the message, and if it matches the certificate posted with the message, the receiver can also be reasonably sure that the sender is authentic . Sending and Receiving an Authenticated MessageOnce users have been certified and registered, you can enable authentication when you use a message queue by setting the Authenticate property of the queue. You can do this in code (call set_Authenticate(true) on the appropriate MessageQueue object) or from the Computer Management console. Just select the queue to use and choose Properties from the Action menu. In the Properties dialog box, select the Authenticated check box, as shown in Figure 12-11. Figure 12-11. The Properties dialog box for the CakeQueue message queue
When this property is set, all messages sent to the queue must be authenticated; otherwise, they'll be rejected. (They'll be redirected to the sender's dead-letter queue if the UseDeadLetter property of the message is set to true , or they will just vanish ). You can arrange for a message to be authenticated by setting the UseAuthentication property to true (call set_UseAuthentication(true) in J#). When the message is posted, it will be signed as described above, and the digital signature and SID of the sender will be attached automatically. When the message is received, the authentication process will check the contents of the message and the attached certificate. Again, this will happen automatically. If you want to use a different mechanism, you can set the AuthenticationProvider and AuthenticationProviderType properties of the message to the name of an authentication provider and the type of authentication it performs , respectively. (Registering alternative authentication providers is beyond the scope of this book.) Encrypting a MessageAuthentication guarantees that the sender and the message are both valid, but the message can still be intercepted and read by a third party. To secure a message, you must encrypt it. Encryption is optional by default, but you can enforce it on a message queue by setting the EncryptionRequired property to EncryptionRequired.Body in code or by setting the Privacy Level to Body in the message queue Properties dialog box in the Computer Management console (shown earlier in Figure 12-11). After you set this message queue property, you should set the UseEncryption property for all messages to true and set their EncryptionAlgorithm property to one of the supported encryption algorithms. Messaging over HTTPA neat new feature of Message Queuing 3.0 is the ability to access message queues over the Internet. You can address a message queue using a format name comprising a URL that specifies the host computer and the name of the message queue. For example, to access the public CakeQueue message queue on WhiteRabbit over HTTP, you can use the following: FORMATNAME:DIRECT=HTTP://WhiteRabbit/msmq/CakeQueue A format name is required because Active Directory does not hold the details of message queues available over the Internet. The msmq element is important because it specifies that the URL is that of a message queue rather than some other type of resource. It will always occur after the computer name but before the name of the queue. Messages sent to an HTTP destination will be transmitted using SOAP. Note that you cannot send messages that have the UseEncryption property set to true over HTTP ”an exception will be thrown if you try. If you want to encrypt messages over the Internet, you should use Secure Sockets Layer (SSL) and specify the HTTPS transport in the format name. |
I l @ ve RuBoard |