Microsoft Message Queue






4.35/5 (10投票s)
Microsoft Message Queue (MSMQ) 简介。
MSMQ 序言 |
![]() |
高速网络(甚至是速度不太快的互联网)带来的新时代,催生了需要新技术来满足大量大规模分布式应用程序的需求。如今,即使应用程序分布在火星和海王星,一切都必须快速可靠,并且在事务级别上支持完整性的基本原则。Microsoft 以其 Windows DNA、三层概念出色地解决了这一问题。当人们告诉你他们想要应用程序的最新热门浏览器界面,而不是你辛辛苦苦才运行起来的经典 GUI 时,你是否感到恼火?所有这些都随着 Windows DNA 引入的三层概念而改变。
Windows 2000(带服务包的 NT 4.0)提供了 MTS、IIS、MSMQ。Microsoft 事务服务器 (MTS) 使你能够编写支持事务的服务(或称为包),这些服务通过参与 MTS 中的事务管理器支持的事务来发挥作用。MTS 允许你提供对象池和负载平衡。因此,应用程序在可扩展性方面受益匪浅,因为不再需要为应用程序处理比预期更高的负载而从头开始重写应用程序以获得额外的性能提升。相反,这意味着增加一些额外的处理器、一点更多的内存,甚至可能是一些额外的处理器来实现负载平衡,并为硬件销售商带来更多业务。
你的应用程序包现在支持两个基本层:数据库访问层,用于与数据存储交互;业务层,用于提供必要的业务服务。数据库层的更改不一定意味着业务层的更改——至少不一定意味着业务服务公开的 API 的更改。你的第三层是表示层,可能是你的经典 GUI、浏览器界面,甚至是未来几年某人可能要求的新界面。
在分布式方法中,有可能参与你事务的所有服务器不能同时可用!一个例子可能是用户填写了一个关于他的电子邮件账户详细信息的请求表单。这些你存储在远程 SQL 服务器上的详细信息可能暂时不可用。为什么不先接受请求并将其保存在某种消息存储中,然后再转发给你的 SQL 服务器应用程序进行处理呢?用户不必收到“SQL 服务器已宕机”的消息,他为什么需要知道呢?这时就引入了 Microsoft 消息队列,它系统化了这种在目标可用时存储和转发消息请求的需求。
必备组件
MSMQ 支持 Windows 2000、Windows NT(带 Option Pack 4+)、Windows 95 和 Windows 98。(注意 MSMQ 可以与 IBM MQSeries 定义的队列进行通信。)
要对 MSMQ 进行编程,你需要 MSMQ API,这需要任何支持 COM 的编译器,如 Microsoft Visual C++、Microsoft Visual Basic 或 Delphi。对 COM 和 DCOM 的程序员意识(至少在概念层面)是理想的。Microsoft 提供 MSQM 的两种形式:COM API 和 C API。然而,本教程使用的是 COM API 和 Visual C++,因为 COM API 更易于使用。
引言
MSMQ 最初的代号是 Falcon。MSMQ 使应用程序能够在不可靠的分布式环境中高效可靠地相互通信,而无需中间服务器始终可用。
目的
消息队列 (MSMQ) 技术使在不同时间运行的应用程序能够跨越可能暂时离线的异构网络和系统进行通信。应用程序将消息发送到队列,并从队列读取消息。下图显示了队列如何容纳发送方和接收方应用程序使用的消息。
MSMQ 还为发生的请求提供日志记录,这在创建数据恢复和系统审计时大有帮助。
MSMQ 部署了存储和转发机制,其中消息不是直接在应用程序之间交换,而是通过称为消息队列的消息存储进行交换。这使得两端的应用程序不必同时可用。MSMQ 不是一项全新的技术,但它是标准化并为 Microsoft 平台提供此功能的真诚尝试。
MSMQ 如何工作?
要对 MSMQ 进行编程,你需要 MSMQ API,这需要任何支持 COM 的编译器,如 Microsoft Visual C++、Microsoft Visual Basic 或 Delphi。对 COM 和 DCOM 的程序员意识(至少在概念层面)是理想的。Microsoft 提供 MSQM 的两种形式:COM API。
MSMQ 如何参与事务
MSMQ 支持以下类型的队列
- 公共队列
任何人都可以查看和定位的队列。
- 私有队列
只有了解完整路径的应用程序才能查看和定位的队列。
- 事务性队列
可以参与事务并提供事务支持的队列。它们有很高的 I/O 开销;它们是有保证且可恢复的。当 MSMQ API 在 MTS(Microsoft 的逻辑事务服务器)下使用时,可以成为当前事务的一部分。在这种情况下,消息要么一起发送,要么都不发送,如果发送,消息的提供顺序与传输顺序相同。这超出了当前讨论的范围,将在后续教程中讨论。
Visual C++ 中的原生 COM 支持
很多时候,我们(VC 开发者)看到 VB 开发者通过创建对象并调用其成员函数来调用 COM 函数,会感到恼火。而 VC 开发者则在 `CreateDispatch` 和其他细节的繁琐操作中挣扎。然而,情况并没有那么糟糕,因为 VC 具有原生 COM 支持,这意味着你无需自行完成 `CreateDispatch` 调用和 GUID。VC 会在内部为你完成所有这些工作。当你需要创建对象时,它会声明接口并使用 `CreateDispatch` 实例化所有对象。
这种原生 COM 支持是通过使用 `#import` 指令实现的。该指令允许你从 COM 模块(如 COM DLL 或类型库)导入信息。
`#import` 语法描述如下
#import <filename> [attributes]
`#import` 指令会导致编译器生成两个文件:.TLH 和 .TLI 文件(在你调试目录中)。这些文件包含接口声明和成员函数实现。它非常类似于标准 VC 应用程序创建的预编译头文件 (PCH)。
在我的示例中,我使用了 `no_namespace` 属性,这样编译器就不会为我们要使用的类型库生成命名空间,而是使用原始接口定义中的命名空间。此外,检查编译器生成的文件的内容以获取额外信息也非常有用。
编程 MSMQ
类 `CMSMQApiWrapper` 是一个 MSMQ API 包装器,足以满足大多数常见用途。然而,这个包装器并没有包含队列消息到达的事件通知,但示例应用程序使用 `CMSMQEventNotifier` 类演示了这一点。
MSMQ API 包装器使用 `#import "mqoa.dll" no_namespace` 指令导入包含消息队列接口的 mqoa.dll。`CMSMQApiWrapper` 的构造函数通过调用 `OleInitialize` 来初始化 OLE 环境。
创建队列
//Creating A Queue [Public/Private] int CreateQueue(LPCTSTR pszPathQueue,LPCTSTR pszQueueLabel,BOOL bPublic); int CreatePublicQueue(LPCTSTR pszPathQueue,LPCTSTR pszQueueLabel); int CreatePrivateQueue(LPCTSTR pszPathQueue,LPCTSTR pszQueueLabel);
上述成员函数有助于创建队列。私有队列通过在队列名称前加上 `Private$` 路径来定义。
使用上述 API 创建队列的方法是实例化一个 `IMSMQQueueInfoPtr` 对象,设置其 `PathName` 和 `Label` 属性,然后调用 `create` 方法。
定位队列
//Locating A Public Queue by Label int LocatePublicQueue(LPCTSTR pszPathQueue,IMSMQQueueInfoPtr &qinfo);
上述成员函数有助于定位公共队列。
使用上述 API 可以通过创建 `MSMQQuery` 对象并使用其 `LookUp` 函数(给定标签)来定位队列。成功时,它将返回一个 `MSMQQueuesInfo` 对象,可以对其进行循环以获取与给定查询匹配的队列。
删除队列
//Deleting A Queue int DeletePublicQueue(LPCTSTR pszQueueLabel);
上述成员函数有助于删除公共队列。
使用上述 API 删除队列的方法是先通过标签定位队列,该标签接受一个 `IMSMQQueueInfo` 指针。成功后,在 `IMSMQQueueInfo` 对象上调用 `Delete` 方法。
同步清除队列
//Synchronously Purging A Queue int SynchronousPurgePublicQueue(LPCTSTR pszQueueLabel);
此方法允许你清除公共队列,首先通过标签定位公共队列,然后以接收访问权限打开队列,最后接收队列中的所有消息,从而清除队列中的所有消息。
将消息发送到队列
// Sending A Message [String other data types and persistent objects for files) // Sending A String Message is currentlu supported // Other Data types, files and even persistent objects can be sent // using MSMQ int SendStringMessage(LPCTSTR pszQueueLabel,CString szLabel,CString szMessage);
此方法允许发送字符串类型的消息。任何类型的数据都可以传递给 MSMQ 队列,因为它接受 Variant 形式的数据。因此,除简单数据类型外,还可以发送文件和持久对象(实现 `IPersistStream` 接口的对象)。此成员函数使用 `VT_BSTR` 类型的 Variant 和给定的标签将字符串消息发送到指定的 MSMQ 队列。这是通过先定位队列并以发送访问权限打开它来完成的。然后实例化 `IMSMQMessage` 类型的对象,设置其 `Body` 和 `Label` 属性,然后使用 `Send` 函数发送消息。
同步从队列检索消息
// Reading A Message[String other data types and persistent objects for files) // Reading A String Message is currentlu supported // Other Data types , files and even persistent objects can be read // using MSMQ // Requesting Events Notification For A Queue is implemented in the // CMSMQEventNotifier class int ReadStringMessage(LPCTSTR pszQueueLabel, CString &szLabel, CString &szMessage);
此方法允许检索字符串类型的消息。任何类型的数据都可以传递给 MSMQ 队列,因为它接受 Variant 形式的数据。因此,除简单数据类型外,还可以发送文件和持久对象(实现 `IPersistStream` 接口的对象)。此成员函数使用 `VT_BSTR` 类型的 Variant 和发送到指定 MSMQ 队列的标签来检索字符串消息。这是通过先定位队列并以接收访问权限打开它来完成的。然后实例化 `IMSMQMessage` 类型的对象,并从消息队列指针中检索其 `Body` 和 `Label` 属性。
异步从队列检索消息
可以使用 `MSMQEvent` 对象从应用程序异步检索消息。`MSMQEvent` 对象允许创建与应用程序中的接收器接口的连接点。在示例应用程序中,通过消息队列服务器获取事件以进行进一步处理,从而演示了这一点。
设置队列属性
// Retrieving and Setting Queue Properties int SetAuthenticationLevelOfQueue (LPCTSTR pszQueueLabel,int iAuthenticationLevel);
此函数设置队列的身份验证级别。
int SetPriorityLevelOfQueue(LPCTSTR pszQueueLabel,int iPriorityLevel);
此函数设置队列的优先级级别。
int SetJournalingLevelOfQueue(LPCTSTR pszQueueLabel,int iJournalingLevel);
此函数设置队列的日志记录级别。
int SetMaximumSizeOfQueueJournal(LPCTSTR pszQueueLabel,long lMaximumSize);
此函数设置队列日志的最大大小。
int SetLabelOfQueue(LPCTSTR pszQueueLabel,CString szLabel);
此函数设置队列的标签。
int SetPrivacyLevelOfQueue(LPCTSTR pszQueueLabel,int iPrivacyLevel);
此函数设置队列的隐私级别。
int SetMaximumSizeOfQueue(LPCTSTR pszQueueLabel,long lMaximumSize);
此函数设置队列的最大大小。
上述函数包装器设置队列的相应属性,并在定位队列后调用其更新函数。
检索队列属性
int RetrieveAuthenticationLevelOfQueue(LPCTSTR pszQueueLabel,int &iAuthenticationLevel);
此函数检索队列的身份验证级别。
int RetrievePriorityLevelOfQueue(LPCTSTR pszQueueLabel,int &iPriorityLevel);
此函数检索队列的优先级级别。
int RetrieveFormatNameOfQueue(LPCTSTR pszQueueLabel,CString &szFormatName);
此函数检索队列的格式名称。
int RetrieveTransactionLevelOfQueue(LPCTSTR pszQueueLabel,int &iTransactionLevel);
此函数检索队列的事务级别。
int RetrieveReadLevelOfQueue(LPCTSTR pszQueueLabel,int &iReadLevel);
此函数检索队列的读取级别。
int RetrieveJournalingLevelOfQueue(LPCTSTR pszQueueLabel,int &iJournalingLevel);
此函数检索队列的日志记录级别。
int RetrieveMaximumSizeOfQueueJournal(LPCTSTR pszQueueLabel,long &lMaximumSize);
此函数检索队列日志的最大大小。
int RetrieveLabelOfQueue(LPCTSTR pszQueuePath,CString &szLabel);
此函数检索队列的标签。
int RetrievePrivacyLevelOfQueue(LPCTSTR pszQueueLabel,int &iPrivacyLevel);
此函数检索队列的隐私级别。
int RetrieveMaximumSizeOfQueue(LPCTSTR pszQueueLabel,long &lMaximumSize);
此函数检索队列的最大大小。
int RetrievePathNameOfQueue(LPCTSTR pszQueueLabel,CString &szPathName);
此函数检索队列的路径名。
上述函数包装器检索队列的相应属性,并在定位队列后调用其刷新函数。
示例 MSMQ 应用程序
此应用程序旨在用作基于图形用户界面的注册系统,并使用 Active Server Pages 提供类似的 Web 界面。此系统将在注册时接受以下详细信息:
- UserName
- 密码
- 年龄
- 性别
- 地址
- 电子邮件 ID
- 出生日期
注册消息时,它会被放入 MSMQ 机器上的 `InboundRequests` 队列。实现 `MSMQEvents` 的目标应用程序通过将此信息添加到数据库并发送响应电子邮件来提供消息处理。稍后,用户可以通过提供其电子邮件地址来请求将他/她注册的所有详细信息通过电子邮件发送给他/她。它使用了 P J Naughter 开发的电子邮件 SMTP 组件,可在此处找到。
上面显示的是 MFC GUI 示例应用程序注册用户的屏幕。此应用程序将在创建队列(如果不存在)后将消息放入 `InboundRequests` 队列。
上面显示的是 MFC GUI 示例应用程序中用户请求他/她详细信息的对话框。此应用程序将在创建队列(如果不存在)后将消息放入 `InboundRequests` 队列。
此测试应用程序要求在所有客户端上安装 MSMQ,但示例代码写入本地机器队列,因此你需要进行路由,或者你可以修改测试应用程序以写入远程队列。如果远程计算机宕机,MSMQ 将确保你的消息到达目的地。
上面显示的是 ASP 基于网页注册用户的屏幕。此应用程序将在创建队列(如果不存在)后将消息放入 `InboundRequests` 队列。
上面显示的是 ASP 基于网页用户请求他/她详细信息的屏幕。此应用程序将在创建队列(如果不存在)后将消息放入 `InboundRequests` 队列。
Active Server Pages 不需要为每个客户端页面设置 MSMQ 软件。注册和请求用户详细信息的页面会调用嵌入在 srvmsgsend.asp 文件中的服务器端脚本。脚本会在服务器上创建队列(如果不存在),然后将消息放入队列。
服务器端脚本如下
<% on Error resume next set queueinfo = CreateObject ("MSMQ.MSMQQueueInfo") queueinfo.PathName = ".\InboundRequests" queueinfo.Create set queue = queueinfo.Open ( 2, 0 ) If queue.IsOpen Then Set message = CreateObject("MSMQ.MSMQMessage") message.Body = CStr (Request.QueryString ( "Body" )) message.Label = CStr (Request.QueryString ( "Label" )) message.Send queue Response.Write "Message has been sent, you will be notified ASAP." End If queue.Close %>
`MSMQReceiver` 是测试应用程序,负责管理 MSMQ 队列的事件。它启用给定队列的通知,并在消息到达队列时,通过在 Access MDB 中注册或检索用户信息来处理信息,然后将注册用户的信息通过电子邮件发送给用户。
它实现了一个 `CMSMQEventCmdTarget` 类,该类派生自 `CCmdTarget`,这是 MFC 用于 OLE 自动化的魔术类。它实现了 `Arrived` 方法作为消息到达的入口点,可以为你自己的应用程序进行自定义。
`EnableNotification` 方法打开队列并调用其 `EnableNotification` 方法。此方法获取 MSMQ.MSMQEvent COM 类的 disp 事件,用于在指定队列上启用通知。然后,它获取接收器的 `ConectionPointContainer` 接口。然后,它获取由 `IID_IMSMQSinkEvent` 定义的连接点。成功调用后,它会将 Dispatch 接口的连接点通知到 `CMSMQEventCmdTarget` 类。
`DisableNotification` 方法关闭队列并移除与接收器的连接。此方法获取 MSMQ.MSMQEvent COM 类的 disp 事件,用于在指定队列上启用通知。然后,它获取接收器的 `ConectionPointContainer` 接口。然后,它获取由 `IID_IMSMQSinkEvent` 定义的连接点。成功调用后,它会将 Dispatch 接口的连接点从 `CMSMQEventCmdTarget` 类中取消通知。
实现相同代码的难度比上述描述要简单得多。
摘要
在理解了 MSMQ 的功能和基本设计之后,你现在可以更好地理解队列可以在你的下一代应用程序中提供什么。Web 现在应该会展示更多有保证的消息队列应用程序,它们可以在不影响性能的情况下提供更高的安全性和可靠性。你现在可以开始探索完整的 MSMQ 和 MTS 基于 DNA 的三层应用程序。随着时间的推移,应用程序开始使用这些强大的功能和 XML 等标准文档格式进行通信将是理想的选择。
参考和一些有用的链接
MSDN 上的 MSMQ | http://msdn.microsoft.com |
MSMQ 下载 | http://www.microsoft.com/windows/downloads/default.asp |
Microsoft Systems Journal 关于 MSMQ 的文章 | http://www.microsoft.com/msj/0798/mtsmsmq/mtsmsmqtop.htm |
队列与其他非 Microsoft 平台之间的交互 | http://www.level8.com/ |
Gopalan Suresh Raj 的 Microsoft Message Queue Server (MSMQ) | http://www.execpc.com/~gopalan/mts/msmq.html |
欢迎来到 MessageQ | http://www.messageq.com |