论坛: 系统集成 标题: 使用Windows DNA 2000的新技术―MSMQ 复制本贴地址    
作者: humf [humf]    论坛用户   登录
摘要  随着计算机网络技术的飞速发展,越来越多的人认识到了网络编程的重要性,internet的应用越来越普及。微软对分布式的应用开发起了推波助澜的作用。本文对微软的windows DNA 2000所采用的message技术进行了研究,以VB为例,对如何在分布式应用中使用MSMQ进行了详细的介绍。
关键词  window DNA 2000;消息;队列;事务;VB

(一) 引言

当您查看组成windows DNA 2000的组件时,您将看到其中应用了一些不同的技术。其中有一个技术--messaging不是最新的。也许您会问,为什么一个旧的技术会残留在新版本的Windows DNA之中?因为messaging已经使用多年并且有着优秀的口俾。
在单线索应用中使用messaging就像您打电话给自己一样。什么意思?假想一下,您拨了那个正确无误的号码,结果如何您就很清楚啦。哦,有烦恼?当然,现在的应用已经是多线索的啦,单线索、单进程的应用已经落后了。
假设您住在一个城市而您的父母住在另一个城市,有一天您想打一个电话告诉他们一些事情,但没人接电话。您怎么办?过一会再打?还是请假跑过去再告诉他?还是不断地打?
当您的应用软件运行在不同的电脑里,通过一些不可靠的连接传递信息时,您就会面临越来越多这一类问题。在依赖网络运作的internet应用世界中,当您知道您们网络是正常运作的时候,应用程序之间会自由地迅速交换信息。现在很多internet应用软件是在网络上固定地工作着,这不仅仅是说web站点。一个应用软件可能透过网络去获取一些有用信息。作为一个例子,您可能拥有几个书店,您要求每一个书店每天给您一个日销售报告。您可能需要拨号上网(或者通过ADSL、ISDN等等)收发数据。现在,如果这个系统坏了,如您无法拨号上网,或者服务器down,或者发送系统down。您重新连接或再试。您现在可能希望您在服务器上的数据是有备份的。您能确信您的数据被正确接收吗?
这涉及到MSMQ的一些内容:一次性传输的保证、事务性消息、次序性的消息和无连接消息。这篇文章主要探讨有关这些内容和其它MSMQ主题,并显示您如何在您的应用软件中使用message队列。

(二)MSMQ的基本知识

设置MSMQ是罗嗦的。为了作为客户安装它,必须要有一个主控制器在线。在安装以前,您必须决定这个客户是约束的还是非约束的。约束性客户要保证随时在线发送message。它们不能离线运作。当您写一个应用软件目的在于一个约束性客户的时候,当系统离线时,您必须尝试存取任何MSMQ对象模式的API/COM方法。非约束性客户有一个本地队列,在系统离线时发送message去远端队列(queue)。(见图一)

当设置一个非约束性客户时,您将需要知道您希望发送的地址。为了了解如何找到那些信息,我们首先需要要对MSMQ有一个总的看法,并且要查看MSMQ模式的对象和基本结构。

(二) 队列(Queue)

Queue有两种类型―公共的(PUBLIC)和私有的(PRIVATE)。”private”这个名字可能令人有一些小小的误解。如果queue真的是私有的,作为私有的类成员一部分,没有人能看到它或发message给它。在MSMQ的文档中,private仅仅意味这个queue不允许那些通过查询服务器找出一个queue并发送数据给它的用户使用。您必须事先知道您要存取的queue的名字。通常情况下,private queue是用作response queue。下面我们再进一步介绍response queue。
让我们看一下,我们是如何知道在MSMQ land中一个queue是否是活的和在线的。这一步要求取到一个允许的public queue列表,这queue不像您希望的那样简单的。首先。您需要建立一个MSMQQuery对象通过一个MMSMQQueueInfo对象获取的所有public queue的列表后去取一个MSMQQueueInfos对象。

Dim oQuery As MSMQQuery
Dim oQinfos As MSMQQueueInfos
Dim oQInfo As MSMQQueueInfo
Set oQuery = New MSMQQuery
Set oQinfos = oQuery.LookupQueue
OQInfos.Reset
Set oQInfo = oQInfos.Next
Debug.Print oQInfo.PathName
Do While Not (oQInfo is Nothing)
Set oQInfo = oQInfos.Next
If Not oQInfo Is Nothing Then
Debug.Print oQInfo.PathName
End If
Loop
下一步,通过询问这个oQInfo对象,您可以存取queue属性对话框中列出的任一信息。可能您需要的最重要信息是queue的名字和ID号码。在开发的一个offline应用中,我们需要一个或者其它信息位来发送信息给这个offiline queue。
 

在图二中显示了在我的服务器\\southside中一个queue的属性。为了存取这个queue offline,在下面的代码中,我们需要它的地址―ID号码。

Dim oQInfo As New MSMQQueueInfo
Dim oQueue As New MSMQQueue
oQInfo.FormatName=“PUBLIC=CFBEB43E-CABB-4C54-ACF5-13FEB702EB47”
Set oQueue = oQInfo.Open(MQ_SEND_ACCESS,MQ_DENY_NONE)
If oQueue.IsOpen Then
Msgbox “Queue “ & oQInfo.PathName & “ is open”
Else
Msgbox “Queue “ & oQInfo.PathName & “ is not open”
End If
您需要queue ID作为您要打开的那个queue的FormatName的参数。现在我们知道如何找到和打开一个queue,下面看一下在发送message时需要做些什么。
Dim oMsg As New MSMQMessage
oMsg.Body = “This is a test”
oMsg.Label = “Test send Message”
oMsg.Send oQueue,MQ_SINGLE_MESSAGE
上面显示了一个简单的例子,但有一点不是很明显。如果您正发送的queue被作为一个事务性queue建立的话,那么在send方法中的第二个参数是必须要有的。稍后我们再讨论关于事务的一些知识,但现在您知道您正发送的queue的事务性状态就行了。您能从MSMQQueueInfo对象的Transactional属性中获取信息。一个更完整的例子如下:
Dim oMsg As New MSMQMessage
oMsg.Body = “this is a test”
omsg.Label = “Test send Message”
If  oQueue.QueueInfo.IsTransactional  then
oMsg.Send oQueue, MQ_SINGLE_MESSAGE
Else
oMsg .Send oQueue
End if
如果您坚持用MQ_SINGLE_MESSAGE,但这个queue不是transactional queue时,程序将出错。在您发送以前检查MSMQQueueInfo对象是重要的。否则这将是一个潜在的问题。

(三)消息(Message)

如果您仅仅是想发送一个简单的Text Message的话,我们就可以结束这个对话了。但,通常情况下,我们需要发送更为复杂的Message。可能您需要经过MSMQ传送二进制的数据或一些特殊的客户定制的数据。您可以发送任何已经格式化为一个Byte 数组的数据。您能建立一个PropertyBag并且作为message发送,或者通过message body发送recordset。事实上,任何格式化的对象都可在一个message body中发送。
Dim  oPropBag  As  New  PropeertyBag
‘ 填充preperty bag
oPropBag.WriteProperty “Title”,txtTitle.Text
oPropBag.WriteProperty  “Author”,txtAuthor.Text
oPropBag.WriteProperty “ISBN”,txtISBN.Text
‘建立MSMQMessage对象并填充数据
Dim oMsg As New MSMQMessage
oMsg.Body = oPropBag.Contents
oMsg.Label = “Message to demonstrate Passing PropertyBag Data”
oMsg .Send m_oRequestQueue
这段例子显示了您要建立一个propertyBag、填入数据和发送是如何的容易。
rsNew.AddNew
rsNew(“Title”).value = txtTitle.Text
rsNew(“Author”).Value = txtAuthor.Text
rsNew(“ISBN”).Value = txtISBN.Text
rsNew.Update
‘建立MSMQMessage对象并填充数据
Dim oMsg  As New MSMQMessage
OMsg.Body = rsNew
OMsg.Lable = “message to demonstrate passing Recordset Data”
OMsg.Send m_OrequestQueue
传送一个recordset也是相当的容易,您使用一个动态建立的recordset或查询数据库的结果。您用IpersistStorage工具或者将它指定给message body来传送任何的对象。在VB6中您如何取得IpersistStoragede 的接口?它比您想的更容易。建立一个类,persistable属性设为Persistable即可。您建立类的instance,用数据填写后指定给message body。
Dim oBook As New Cbook
‘赋值给Cbook 对象
oBook.m_strTitle = txtTitle.Text
oBook.m_strAuthor = txtAuthor.Text
oBook.m_ISBN = txtISBN.Text
‘建立MSMQMessage对象并填充数据
Dim oMsg  As New MSMQMessage
OMsg.Body = oBook
OMsg.Label = “Message to demostrate Passing persistent Classes”
OMsg.Send m_oRequestQueue

(四)获取回应信息和异步事件

当我们用MSMQ发送数据的时候都希望能收到回应信息,如数据已收到或有其它事件发生。为了做到这一点,您应将MSMQQueuInfo对象作为message的一部分送出去。一个设计完善的接收端应用看到这个对象后就会通过queue回应信息给您。在文章的前面,我们提及response queue通常是private queue或者您想单独使用的,每一个接收者的queue都期望会回应给您信息。
为了发送response queue给接收者,您将需要建立一个引用(reference)给这个对象。
Dim oQueueInfo As New MSMQQueueInfo
Dim bIsTransactionable As Boolean
Dim oRespQueue As MSMQQueue
‘如果queue已存在,则忽略错误
On error Resume Next
OQueueInfo.PathName = “ \\someserver\private$\response1 “
OQueueInfo.Label = “Private Queue for Response 1”
OQueueInfo.Create IsTransactional:=bIsTransactionable
注意您建立一个MSMQQueueInfo 对象而不是一个MSMQQueue对象。一旦建立MSMQQueueInfo对象后,就可以打开它取得MSMQQueue对象。然后您在message中传递这个对象。
Set oRespQueue = oQueueInfo.Open(MQ_RECEIVE_ACCESS,MQ_DENY_NONE)
‘告诉接收端我们需要回复口信
Set oMsg.ResponseQueueInfo = oRespQueue
OMsg.Send oRequestQueue
稍后,我们将看一下接收者如何结束本次通信,但首先我们看一下如何接收回应信息。记住,我们的应用可能是跟一个可能断线的客户对话。如果我们发送message后坐等回应信息返回的话,那我们可能是望眼欲穿。
为了承担这项工作,我们需要用MSMQ事件去接收那个异步的响应信息。首先您给指定的queue绑上一个event handler。
Private WithEvents qevtRecEvents As MSMQEvent
Set qevtRecEvents = New MSMQEvent
ResponseQueueInfo.EnableNotification qevtRecEvents
一旦连接被建立,您就可以继续执行程序。当接收端回应信息时就会激活qevtRecEvents_Arrived事件。这是一个警戒陷井。每次事件发生后,它必须重新初始化以便下一次事件的发生。
Sub qevtRecEvents_Arrived(ByVal Queue As Object,ByVal Cursor As Long)
Dim oMsg As MSMQMessage
Dim oQueue As  MsMqqueue
Set oQueue = Queue
Set omsg = oQueue.Receive(receiveTimeOut:=0)
‘初始化notification为enabled
oQueue.EnableNotification qevtRecEvents
End Sub
在事件中这个queue参数被传递进来,您可以利用这个对象接收返回的信息。在上面的例子中,注意queue参数指定给MSMQQueue对象。这是很高明的。虽然它不是必要的,但它是一个很有用的窍门。
接收端结束本次通信所要做的工作跟我们前面所做的是很相似的。为了异步工作,我们利用MSMQEvent来接收处理进入我们queue的message。一旦事件触发的话就检查是否需要回应信息。如果确实需要,我们打开传递过来的queue,建立一个MSMQMessage对象,建立message和发送回应信息给客户。
Sub qevtRecEvents_Arrived(ByVal  Queue As Object,ByVal Cursor As Long)
Dim oMsg As MSMQMessage
Dim oResQueue  As MSMQQueue
Dim oRespMsg As MSMQMessage
Set oMsg=Queue.Receive(ReceiveTimeOut:=0)
‘您可以在这里处理message 
lstMessages.AddItem “We got a message:” & oMsg.Label
lstMessages.Refresh
‘检查是否是我们要发送的一个 request queue 
If Not oMsg.ResponseQueueInfo is Nothing Then
Set oResponseQueue = oMsg.ResponseQueueInfo.Open(MQ_SEND_ACCESS,MQ_DENY_NONE)
Set oRespMsg = New MSMQMessage
ORespMsg.Label = “Response for:” & oMsg.Label
ORespMsg.Body = “Received your message and processing is complete”
ORespMsg.Correlational = oMsg.Id
ORespMsg.Journal = MQMSG_JOURNAL
ORespMsg.Send oRespQueue
End if
‘重新初始化queue的notific
Queue.EnableNotification qevtRecEvents
End Sub
在回应的message中我们需要的一些附加段数据是和回应原始message有关的一些方法。对于每一个要求回应的message,发送的客户可能用同样的queue回应给多个客户,或者发送不同的message给同一个客户。当MSMQ发送一个指定的message和ID给组成一个GUID的message。对于每一message来说,像所有的GUID一样,这个ID是唯一的,用来区分哪一个需要回应的message。在上面的例子中,您能看到给最新建立的message的CorrelationID属性指定这个ID。

(五)事务和MSMQ
从接收端返回的信息可能是简单的信息,如已经收到和已经执行等等。作为一个重大事件的message需要什么内容呢?也许在message真的完全返回以前,这是一个数据库的update动作要发生?在单事务中,MSMQ提供了两种方法去处理一系列的message和event。一个是支持事务的内部MSMQ,另一个被认为是外部事务,它跟MTS/COM+事务有关。所有的事务中最容易的是一个内部、单一message事务。您能传递MQ_SINGLE_MESSAGE作为发送方法message的第二个参数。这保证这个message是传送的一个和唯一的一个。这种事务不能分担任何包括其它资源管理的事务。一个单一message事务也是一个MSMQ内部事务。
一个内部事务有一个小小的限制,它只能发送和接收MSMQ message。作为单一message事务,更复杂的内部事务不能传递给参与外部事务的外部资源管理。下面代码中,发送一个包含引用MSMQTransationDispenser对象的内部message事务。
Dim oxDispenser As New MSMQTransactionDispenser
Dim oxTrans As MSMQTransaction
‘启动内部事务,在填写完毕后才调用Commit
set oxTrans = oxDispenser.BeginTransaction
‘ 发送作为内部事务的message给queue
mSend.Label = “internal transaction”
mSend.Body = “This is a test message”
mSend.send qsend,xact
mSend.Label = “Send it message”
msend.Body = “This is another test message”
mSend.Send qSend,xact
oxTrans.Commit
qSend.close
在MSMQTransactiondispencer中处于BeginTransaction和Commit之间的任何message被认为是事务体。这是一个很简单的例子。在真实个案中,BeginTransaction和Commit方法调用可能不是处于同一procedure范围中。在从message queue中读取这些message时,您需要检验在queue中的message的事务边界。您也需要MSMQMessage对象的三个属性去检验一个message是否在事务中。IsFirstInTransaction和IsLastInTransaction属性是跟字面意思一样的。如果message是single-message事务,那么这两个属性为”TRUE”。TransactionID,这第三个属性常用来检查事务的边界。所有从内部事务发送的message必须全部来源于同一机器。如果从正接收queue中的一个single事务读取信息,这一点是重要的。在事务中的message直到Commit被调用时才发送。它们是匹配的。如果在您的发送应用中有两个事务出现,哪一个首先执行Commit,哪一个就首先出现在接收queue中,不管它们是何时从发送机器中发出。
在一个包括不仅仅发送和接收message的事务中,外部事务才被使用。它们在Microsoft Distributed Transaction Coordinator中被用作外部资源管理。
Dim xoExtDispenser As New MSMQCoordinatedTransactionDispenser
Dim xoTrans  As MSMQTransaction
Dim qSend As MSMQQueue
Dim msg As New MSMQMessage
Set xoTrans = xoExtDispenser.BeginTransaction
Qinfo.PathName = “.\vbcTransactionsQueue”
‘ 假设queue已存在并是事务性的.
Set qSend = qInfo.Open(MQ_SEND_ACCESS,MQ_DENY_NONE)
Msg.Label = “External Transaction”
Msg.Body = “A test message body”
Msg.Send qSend,xoTrans
XoTrans.commit
注意事务dispenser对象的使用与内部事务的使用是不同的。作为外部事务的一部分,在接收queue中的message可以来自于不同的机器发送。它可能在两台不同的机器发送一个作为单一事务一部分的MSMQ message时发生。当初我们在接收queue中所有msssage假定是匹配的在这时候不再是合法的。这有一个小小的技巧,那就是注意TransactionId属性。
最后,我们查看一下MTS事务。您可能想这可以隐藏为一个外部事务,这部分是对的。COM+事务与DTC一起工作处理这些类型的事务。事实上,MTS事务是缺省发送MSMQ message。MSMQ message发送方法的事务参数是下面5个value中的一个。
MQ_NO_TRANSACTION
MQ_MTS_TRANSACTION(default)
MQ_SINGLE_TRANSACTION
MQ_XA_TRANSACTION
A dispenser object
如果您没有显式指定参数,MSMQ假设您参与一个MTS事务。因为您正发送一个建作事务queue的queue,您将需要去检查COM+是否正运行在COM+事务的正文之中。这给您一个常量传给MSMQMessage的send方法。
Set oCtxObject = GetObjectContext
QSend.Body = “MTS Transaction Test message”
QSend.Label = “test message”
‘ 检查我们是否运行在MTS
If (Not oCtxObject Is Nothing) Then
‘ 我们在MTS中,下一步判断是否在事务中
If (oCtxObject.IsInTransaction) Then
‘MQ_MTS_TRANSACTION is default
QSend.Send
Else
QSend qInfo,MQ_SINGLE_MESSAGE
End If
Else
QSend.Send qInfo,MQ_SINGLE_MESSAGE
End If
QInfo.Close
‘ 如果我们在MTS中,则关闭这个对象
If (Not ctcObject Is Nothing) Then
CtxObject.setComplete
‘use octxObject.SetAbort to abort the transaction
End if
Set oCtxObject = Nothing
虽然MSMQ是相当的简单,但事务使事情变得十分复杂和需要更多的代码行。在这篇文章中,我们试图介绍那些在您编写一个MSMQ应用的时候需要关心的着重点。以上所有代码都可以在VB6中使用。利用这些代码您将能更快地编写一个利用MSMQ技术的应用软件。


参考文献
[1] StenSundblad微软公司核心技术书库――WindowsDNA可扩展设计 机械工业出版社 2001
[2] 黄淼云 VB6.0办公自动化编程 国防工业出版社 2000
[3] 许建志 e时代网络编程系列――开发WindowsDNA应用程序 中国青年出版社 2000

作者简介

黄振明 系统维护支持事业部,工程师。1992年毕业于华中理工大学计算机软件专业,获学士学位。长期从事软件开发工作。现主要为网络系统管理员。


地主 发表时间: 11/06 11:54

论坛: 系统集成

20CN网络安全小组版权所有
Copyright © 2000-2010 20CN Security Group. All Rights Reserved.
论坛程序编写:NetDemon

粤ICP备05087286号