|
作者: 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号