微软MSMQ消息件研究(一)
首先把我在网上找的一些基础资料给大家:
一、基本概述
MSMQ(MicroSoft Message Queue,微软消息队列)是在多个不同的应用之间实现相互通信的一种异步传输模式,相互通信的应用可以分布于同一台机器上,也可以分布于相连的网络空间中的任一位置。它的实现原理是:消息的发送者把自己想要发送的信息放入一个容器中(我们称之为Message),然后把它保存至一个系统公用空间的消息队列(Message Queue)中;本地或者是异地的消息接收程序再从该队列中取出发给它的消息进行处理。
在消息传递机制中,有两个比较重要的概念。一个是消息,一个是队列。消息是由通信的双方所需要传递的信息,它可以是各式各样的媒体,如文本、声音、图象等等。消息最终的理解方式,为消息传递的双方事先商定,这样做的好处是,一是相当于对数据进行了简单的加密,二则采用自己定义的格式可以节省通信的传递量。消息可以含有发送和接收者的标识,这样只有指定的用户才能看到只传递给他的信息和返回是否操作成功的回执。消息也可以含有时间戳,以便于接收方对某些与时间相关的应用进行处理。消息还可以含有到期时间,它表明如果在指定时间内消息还未到达则作废,这主要应用与时间性关联较为紧密的应用。
消息队列是发送和接收消息的公用存储空间,它可以存在于内存中或者是物理文件中。消息可以以两种方式发送,即快递方式(express)和可恢复模式(recoverable),它们的区别在于,快递方式为了消息的快速传递,把消息放置于内存中,而不放于物理磁盘上,以获取较高的处理能力;可恢复模式在传送过程的每一步骤中,都把消息写入物理磁盘中,以得到较好的故障恢复能力。消息队列可以放置在发送方、接收方所在的机器上,也可以单独放置在另外一台机器上。正是由于消息队列在放置方式上的灵活性,形成了消息传送机制的可靠性。当保存消息队列的机器发生故障而重新启动以后,以可恢复模式发送的消息可以恢复到故障发生之前的状态,而以快递方式发送的消息则丢失了。另一方面,采用消息传递机制,发送方必要再担心接收方是否启动、是否发生故障等等非必要因素,只要消息成功发送出去,就可以认为处理完成,而实际上对方可能甚至未曾开机,或者实际完成交易时可能已经是第二天了。
应用程序发送消息到 MSMQ,而 MSMQ 用消息的队列来确保消息可最终到达他的目的地。MSMQ 提供可保证的消息交付、有效的消息路由、安全和基于优先权的消息传递。
二,MSMQ主要的使用函数:
利用 MSMQ,最终用户可越过有时也会脱机的网络和系统相互通讯,使其独立于通讯程序和系统的当前状态。而开发者可利用 MSMQ 把精力集中于商务处理的编程而不是网络问题,因为 MSMQ 提供了网络通讯方面的保证。系统管理员可用 MSMQ 的 Explorer 有效地管理大型的、复杂的网络上的消息队列。通过 MSMQ,MIS 系统的决策人可获得更可信赖的通讯在诸于审查、网络相关的分布应用程序;有效使用网络资源;减少软件开发和维护时间;提高生产率从最终用户、开发人员及系统管理人员。
二、基本操作方法
。用create方法创建你指定路径的消息队列,使用delete方法删除一个已经存在的消息队列。
。使用exists方法判别是否存在一个消息队列。
。使用GetPublicQueues方法获取消息队列网络中的一个消息队列
。使用Peek或者是BeginPeek方法查看消息队列中的消息,而不会删除它们
。使用Receive或者上BeginReceive方法从消息队列中取出一个消息,同时在消息队列中删除它。
。使用Send方法,送一个消息到指定的消息队列中。
7.3.3 消息队列的操作
1. 创建消息队列
。创建公共消息队列
MessageQueue.Create(“MyMachine\MyQueue”)
。创建私有消息队列
MessageQueue.Create(“MyMachine\Private$\MyPrivateQueue”)
说明:标识Private$表示创建的是私有消息队列
2. 队列引用说明
当你创建了一个MessageQueue部件的一个实例以后,就应指明和哪个队列进行通信。在。Net 中有3种访问指定消息队列的方法:
。使用路径,消息队列的路径被机器名和队列名唯一确定,因而可以用消息队列路径来指明使用的消息队列。
。使用格式名(format name),它是由MSMQ在消息队列创建时或者应用程序在队列创建以后生成的唯一标识。
。使用标识名(label),它是消息队列创建时由队列管理者指定的带由描述意义的名字。
它可能并不唯一。
采用路径(path)方式引用队列
消息队列类型 路径使用格式
Public queue MachineName\QueueName
Private queue MachineName\Private$\QueueName
Journal queue MachineName\QueueName\Journal$
Machine journal queue MachineName\Journal$
Machine dead letter queue MachineName\Deadletter$
Machine transactional dead letter queue MachineName\XactDeadletter$
。因为消息队列服务器接收到一个使用路径方式使用消息队列的操作请求时,会去解析出路径和格式名(format name),因此它的效率上不如格式名方式使用队列。
。消息队列未连接时,只能使用格式名方式对它发送消息。
路径名的引用除了path属性以外,还可以由MachineName和QueueName两个属性得到。
路径引用的例子:
MessageQueue1.path=”.\MyQueue”
采用格式名(format name)方式引用队列
格式名由公有私有标识串加上队列产生的GUID,以及其他必需的标识构成。
消息队列类型 格式名的构成规则
Public queue PUBLIC=QueueGUID
Private queue PRIVATE=MachineGUID\QueueNumber
Journal queue PUBLIC=QueueGUID;JOURNAL 或者PRIVATE=MachineGUID\QueueNumber;JOURNAL
Foreign queues DIRECT=AddressSpecification\QueueName
格式名由不由用户指定,而是在队列创建时由队列管理者自动产生。
。当你的部件作为一个WEB sevice或者是WEB调用的一部分的时候,最好采用格式名方式引用队列,因为它速度较快。
。当向一个非连接的队列发送消息时,应使用格式名方式,因为当队列不可连接时,路径解析会导致失败。
。网络拓扑结构发生变化或者消息队列重建以后,格式名会变化。
可以由消息队列对象的FormatName属性得到格式名。
例如:采用格式名方式引用消息队列的例子
MessageQueue1.Path = "FORMATNAME:PUBLIC=3d3dc813-c555-4fd3-8ce0-79d5b45e0d75"
采用标识方式引用消息队列
标识是消息队列创建时,由消息队列创建者指定由于描述队列的文本属性。采用标识的好处在于屏蔽了低层的具体位置,对于移植和程序修改时,应用的修改很小。它的缺点也是显然的,就是不能保证标识的唯一性,当标识有冲突时,向该标识发送消息会导致一个错误的发生。标识可以由访问消息队列对象的Label属性得到。
总结,我们对消息队列引用的步骤大致为:
首先,产生一个MessageQueue对象的实例。
然后,根据引用消息队列的方式设置不同的属性。
如果为路径方式,设置它的path属性
如果为格式名方式,设置其FormatName属性
如果为标识方式,设置它的Label属性
3. 删除消息队列
删除一个队列使用Delete方法。当删除一个队列时,队列中含有的所有消息将首先被删除,然后删除该队列,它不会把消息队列中的信息发往死信队列中。删除一个队列最主要的问题是用户是否有删除该队列的足够的权限。
使用Delete方法的例子如下:
MessageQueue.Delete(“MyMachine\MyQueue”)
4.清除消息队列中的内容
有时我们需要把送入消息队列中而尚未发出的消息清除,或者定期需对消息发送日志队列进行清除,可以使用消息队列对象提供的Purge方法。它把指定的消息队列中的消息全部清空,并不再发送。
使用的例子如下:
MessageQueue1.Path="MyMachine\MyQueue"
MessageQueue1.Purge()
5.创建消息队列对象的实例
创建一个消息队列实例的步骤如下:
1. 创建一个MessageQueue类的实例
例如:
dim MyQue as New MessageQueue
2. 对MessageQueue类实例的path属性进行设置
例如:
MyQue.path=”.\MyQueue”
3.设置你需要其他MessageQueue类的属性
6.消息队列配置属性
关于队列的属性:
path属性:它可以决定引用队列的三种方式,路径引用、格式名引用、标识引用
category属性:标识当前使用的队列的类型。Category是队列所有者定义的GUID值。该GUID值可以有GUID生成工具产生或者是用户自定义的数字值。GUID值不会唯一,这样才可以根据相同的GUID值,把多个消息队列划分为不同的类别(category)。
跟发送数据类型相关的属性
Formatter属性:决定在一个队列中如何发送和接收消息的顺序,以及可以在一个消息中发送什么样的内容。
和队列交互相关的属性
DenyShareReceive属性:决定同一时间内只有一个部件能够访问消息队列中的消息。
CanRead和CanWrite属性:决定队列是否可以被读取或者是写入。
MaximumQueueSize和MaximumJournalSize属性:以千字节为单位设置一个队列(日志队列)的消息最大容纳量。一旦接收的消息到达这个容量,新的消息将不再被接收。
一般情况下,消息队列的最大值为消息队列管理员所设置,如果这个值没有控制的话,那么缺省的消息队列最大容量将是无限制的。
UseJournalQueue属性::设置是否将收到的消息拷贝到日志消息队列中去。
7.消息发送
MSMQ消息队列中定义的消息由一个主体(body)和若干属性构成。消息的主体可以由文本、二进制构成,根据需要还可以被加密。你可以在属性窗口或者是直接对消息对象的属性进行赋值。但是,在MSMQ中消息的大小不能够超过4MB。
消息可以被送往公用、私有、日志、死信、交易队列。
。简单消息的发送
简单消息发送的步骤大致为:
1. 建立与要发送消息的队列的连接
2. 指定你要发送的消息的格式
3. 提供要发送的数据
4. 使用Send方法将消息发送出去
采用简单消息发送方式发送的数据类型可以是:对象、原数据类型、数据流或其他简单的数据类型。
例子:以发送一个整型数和字符串为例
首先创建一个连接:
Dim MessageQueue1 as new MessageQueue ("MyMachine\MyQueue")
或者
Dim MessageQueue1 as New MessageQueue
MessageQueue1.path=”MyMachine\MyQueue”)
由于是标准数据类型,消息格式可以不指定,使用缺省的
然后发送数值1
MessageQueue1.Send(1)
再发送字符串“hello world”
MessageQueue1.Send("Hello world")
。发送复杂的消息
相对于简单的消息发送,发送较为复杂的消息对象能够给你带来对消息更多的控制的控制能力。对于复杂对象的发送,我们是通过创建消息的对象的实例,然后对它的属性实行相应设置来实现的。
发送复杂消息的步骤大致为:
1. 创建一个MessageQueue的实例,然后对它的path属性设置,以指明操作的消息队列。
2. 创建一个消息对象实例。
3. 设置消息的主体(body), 然后修改不希望使用缺省值的属性。
4. 同样使用send方法把消息对象发送至相应的队列中。
例子:
‘创建MessageQueue实例,指明连接的队列
dim MessageQueue1 as New MessageQueue(“.\MyQueue”)
‘创建消息对象实例
dim MyMessage as New Message(“Hello world”)
‘设置消息队列属性
MyMessage.Label=”MyLabel”
‘发送消息
MessageQueue1.Send(MyMessage)
8.消息接收
消息的接收分为两种,即同步和异步。
同步方式,我们使用receive方法。当调用receive方法时,它会从指定的消息队列中选取出第一个适合要求的消息对象返回给用户,然后把它从消息队列中删除。如果调用receive方法的时候,消息队列中没有一条记录存在,那么方法将导致程序挂起,直到有消息到达消息队列中。为了不使这个等待过程太长,你可以在调用Receive方法时指定time-out值(毫秒为单位),以指定在相应时间到达后退出等待过程。在使用Receive方式时,还可以指定消息队列的DenyShareReceive属性,防止其他用户对队列进行操作。试想这样一种情况,当消息队列中剩一条消息的时候,2个用户同时对它调用peek方法,发现都有消息,于是都放心的使用Receive方式去获取消息,结果必然导致其中一个用户被挂起。如果,在调用receive方法以前使用了DenyShareReceive属性拒绝其他用户的对该队列的Receive方法,就会避免上述现象的发生。
同步接收的例子:
dim MyMessageQue as MessageQueue
MyMessageQue=New MessageQueue(“MyMachine\MyQueue”)
‘指定要连接的消息队列
dim MyMessage as New Message
MyMessage=MyMessageQue.Receive(1000)
‘同步收取一条消息,超时时间为1秒
Peek方法和Receive方法比较类似,只是它并不把取得的消息对象从消息队列中删除。Peek方法只取出消息队列中的第一条消息,若要取得所有的消息可以使用GetMessages方法或者是GetMessagesEnumrator方法。Peek方法也是同步方法,所以当消息队列中没有消息的时候,它也将被挂起,直到有消息产生。同样的,Peek也可以指定超时时间(单位毫秒),一般最常用的方式是设为0或无穷大。设为0的意义是,对消息队列搜索看是否有消息产生,并马上返回;设为无穷大的意义是,直到有消息产生才进行处理。
例如:
‘设置为超时时间为0
MyMessage.TimeToBeReceived=0
‘设置超时时间为无穷大
MyMessage.TimeToBeReceived=Message.Infinite
‘以同步方式使用Peek方法的例子
dim MyMessageQue as New MessageQueue(“MyMachine\MyQueue”)
‘指定连接的消息队列
‘对消息队列查询首条消息,若无1秒钟后超时返回
dim MyMessage as Message
MyMessage=MyMessageQue.Peek(1000)
异步接收方式,是指接收消息时,不必理会调用的消息接收方法是否成功,方法将立即返回,并继续进行程序处理。对于异步接收方式收到的消息,有两种处理方式。一种是消息接收到以后,会发出一个事件,我们可以定义一个事件处理函数,在该函数中,对接收到的消息进行处理。另一种方法是为消息接收函数定义一个回调函数,当消息接收到以后,它会去处理带来的消息。
在ASP.NET中,事件处理方式中,在事件处理函数中,我们使用BeginPeek或者是BeginReceive方法从消息队列中取得一条消息。如果要处理多条消息,那么就要反复调用BeginPeek或者BeginReceive方法。
在回调处理方式中,回调函数定义了一个和BeginPeek或者BeginReceive方法绑定到一起的代理,这样即使是在消息处理当中,代理仍然可以监视是否有新的消息到来。
因为消息处理函数方式用得比较普遍,所以下面我们就重点介绍消息函数方式得使用。
在事件模型中,首先必须绑定一个消息处理函数到一个消息,它是当异步调用完成以后,你希望对它进行处理的代码入口。然后调用BeginReceive方法,启动异步接收模式。当接收过程完成,.net平台会发出一个消息,表示已经从消息队列中接收到一个消息,然后调用和该消息绑定的处理函数,以处理到达的消息。
它的步骤如下:
① 对MessageQueue对象的BeginReceiveCompleted事件绑定一个消息处理函数
② BeginReceiveCompleted处理函数中,创建一个消息对象实例和一个消息队列 对象的实例
③ 定义如何处理收到的信息,代码框架如下:
Public Sub MyQueue_BeginReceiveCompleted(sender as Object, args as System.Messaging.ReceiveAsyncEventArgs)
Dim myMessage as Message
Dim myMessageQue as MessageQueue
MyMessageQue = CType(sender,MessageQueue)
myMessage = MyMessageQue.EndReceive(args.AsyncResult)
End Sub
④ 在主程序中,使用BeginReceive方法启动异步接收方式。
例如:
MyMessageQue.BeginReceive
使用peek方法的异步接收模式和使用receive方法的方式差不多,就不再多述了。
9.消息确认
为确保消息被正确发送到目的消息队列,我们还可以对消息队列进行设置,让其返回消息是否正确发送到指定队列的确认消息。确认消息有两种,一种是消息队列到达指定队列后发出的确认消息,另一种是消息被对方应用从消息队列中删除。而每一种消息又存在有两种形式,确认和否定。当消息正确到达目的队列或应用时,它会发回发送成功的确认消息。如果消息无法到达指定的队列或应用,那么它会发回发送失败的确认消息。不过收到发送失败的消息,未必是对方消息队列无法到达,它有可能是在超时时间设置过小或者是无法通过对方的验证。
从对方返回的确认消息通常不会放入一般的队列中,而是放入一个称之为管理队列的特殊队列中。确认消息也和一般的消息不一样,它不包含消息的主体,仅仅通过消息的头部就可以知道确认消息的意义。
在ASP.NET中,确认消息发往的管理队列,由其AdministrationQueue属性指定的队列所决定。
你可以把不同的确认消息发往不同的管理队列中。对管理队列的操作就和操作不同的队列是一样的,可以用peek方法查看,也可以用remove移走。
那么,如何设置一个消息,要求它返回确认信息呢?
首先设置一个消息对象的AdministrationQueue属性,指定确认消息返回的管理队列。
接着设置返回消息的确认类型, 使用消息对象的Acknowledge属性。
Acknowledge属性的值可以是:
。FullReachQueue 无论发送的消息到达或不能到达目的队列,都会返回确认消息
。FullReceive 无论发送消息能否到达对方应用程序,都会返回确认消息。
。NotAcknowledgeReachQueue 只有发送的消息未能到达目的队列,才返回确认消息。
。NotAcknowledgeReceive 只有发送的消息未能到达对方应用程序,才返回确认消息。
。None 不返回任何确认消息
最后,发送消息,并检查管理队列,看是否有确认消息产生。
10.消息日志
日志队列可以保存你操作过的消息的备份。它的好处是,一但发现前面的操作失败,可以从日志队列中重新创建出原先的消息对象,然后再进行操作。例如,向远方发送一个消息对象,然后对方返回一个失败的确认。我们可以从失败确认消息中提取出一个和开始发送的消息相关的ID值,然后根据提出的ID值从日志队列中找到发送的消息,重新创建一个消息对象,并再次发送。在.net中,我们使用ReceiveByCorrelationID或PeekByCorrelationID方法根据correlation ID值取得消息对象。
在一台机器上,都会有一个全局消息队列,它保存任何从该机器发出的消息,而不论消息发送是否成功。每个消息队列也可以有自己的消息日志队列。日志队列的使用有两种方式,一种是对消息队列对象设置UseJournalQueue属性,它表示对该队列收到的所有消息使用日志记录方式,而对于发出的消息不做任何记录;另一种方法是对消息对象设置UseJournalQueue属性,所有被发送的消息将被记录到系统日志队列中去。消息日志队列有一个最大容量,称作quota,一旦日志队列存储容量到达该值后,以后到来的本应存储的的消息将不再被存储,同时不会发出任何的出错信息。所以作为管理人员,应该定期清理日志队列,以防止上述现象的发生。消息队列只是被动的接收端,它们不可能返回确认消息,或者发送删除的消息到死信队列中,或者是进行超时处理。
例子:
设置消息队列对象的UseJournalQueue属性,以记录收到的消息到日志队列中
MyMessageQueue.UseJournalQueue=True
设置消息对象的UseJournalQueue属性,以记录收到的消息到系统日志队列中
MyMessage.UseJournalQueue=True
现在可以在电脑上建自己要的MQ了.