WCF 队列消息






4.63/5 (14投票s)
详细介绍如何通过 NetMsmqBinding 将 MSMQ 与 WCF 结合使用。
首先
在开始之前,我想为将配置代码作为图像提供而提前道歉。我尝试了所有标签组合,但未能使 XML 代码正确显示。我一定是做错了什么,因此未来文章的任何帮助都将不胜感激:)
本文档面向哪些读者?
本文档面向在分布式系统之间进行消息传递的开发人员。假定您熟悉以下内容:
- WCF
- 消息传递概念
- MSMQ
引言
消息交换模式(MEP)对于经验丰富的开发人员来说很熟悉。简而言之,消息传递有四种模式:
- 单向:客户端发送消息给服务,不期望获得结果。不过,客户端仍会通过确认收到消息的通知。
- 请求-响应 / 同步:客户端将消息发送给服务,服务通过同一通道直接回复一条消息。这意味着客户端将阻塞,直到收到它所请求的数据(以另一条消息的形式)。
- 请求-响应 / 异步:这是同步模式的一种变体,在这种模式下,客户端不会阻塞等待服务响应。客户端继续工作,一旦服务完成请求处理,它会在一个单独的线程上将响应消息发送回客户端。
- 双工:这是双方都可以发起会话的情况。系统 A 可以向系统 B 发送请求,在这种情况下,A 是客户端,B 是服务。另一方面,系统 B 可以向系统 A 发送请求,在这种情况下,B 是客户端,A 是服务。
所有 MEP 的一个共同要求是消息持久性;也就是说,能够保持消息在所有条件下的完整性,并确保消息能够到达它应该到达的目的地。
Microsoft 消息队列服务(MSMQ)是一种经过验证的方法,可以为消息提供一个队列,直到接收者准备好接收它们。WCF 通过 NetMsmqBinding 提供对 MSMQ 的隐式集成,这正是本文的主题。
场景
本文将指导您实现一个场景:客户端通过 MSMQ 发送一个请求给服务。服务处理该请求,并通过 MSMQ 发送响应回来;当然,这一切都通过 WCF 的 NetMsmqBinding
完成。客户端名为“Client”,服务名为“Service”…我知道这创意不够,但就足够了。
步骤 1:创建队列
我们首先要做的是创建服务和客户端将要通信的队列。MSMQ 本身不是本文的主题,但我将强调以下几点:
- MSMQ 是 Windows 组件的一部分,可以从计算机管理控制台访问(右键单击“我的电脑” ->“管理”)。
- MSMQ 队列有两种类型:私有和公有。私有队列适用于服务和客户端在同一台机器上。公有队列用于客户端和服务在加入同一 Active Directory 的不同机器上。本文使用私有队列,但概念同样适用于公有队列。此外,稍后会解释公有队列的区别。
因此,从计算机管理控制台,创建两个私有队列,分别命名为“requestqueue”和“responsequeue”,如下图所示。将两个队列都创建为事务性的。我们稍后会在 WCF 中编写事务代码。
“requestqueue”将接收所有来自客户端发往服务的请求。“responsequeue”则将接收所有来自服务发往客户端的响应。
步骤 2:创建服务
该服务是自托管的,并且仅公开一个操作,客户端通过该操作查询某个 ID 的信用额度(不用说,这是一个相当 dummy 的场景,但足够了)。
下面是服务契约的简单代码:
[ServiceContract]
public interface ICreditLimitRequest
{
[OperationContract(IsOneWay = true)]
void SendCreditLimitRequest(string id);
}
public class CreditLimitRequestService : ICreditLimitRequest
{
public void SendCreditLimitRequest(string id)
{
double value;
if (id == "1")
value = 10;
else if (id == "2")
value = 20;
else
value = 0;
}
}
上面的代码中有一点值得注意,我们将操作标记为 IsOneWay = true
。这确保我们使用的是单向 MEP(稍后详述)。
该服务托管在一个 Windows 应用程序中,其中包含两个按钮,一个用于启动服务,另一个用于停止服务。代码如下:
private void button1_Click(object sender, EventArgs e)
{
myServiceHost = new ServiceHost(typeof(CreditLimitRequestService));
myServiceHost.Open();
MessageBox.Show("Service Started!");
}
private void button2_Click(object sender, EventArgs e)
{
if (myServiceHost.State != CommunicationState.Closed)
{
myServiceHost.Close();
MessageBox.Show("Service Stopped!");
}
}
最后,服务配置如下:
配置中需要注意的几个重要事项:
- 终结点地址指定服务将监听私有队列“requestqueue”。
- 终结点绑定设置为“
netMsmqBinding
”,这是 WCF 用于处理 MSMQ 的隐式绑定。 - 基地址设置为“https://:8020/msmqservice”。这对于能够使用 Visual Studio 的“添加服务引用”(svcutil.exe)从客户端应用程序生成代理非常重要。正如我们稍后将看到的,一旦服务启动,我们就可以使用基地址作为生成代理时服务的 URL。
- 最后,公开了 MEX 终结点,以便通过 HTTP 进行元数据交换(同样是为了“添加服务引用”功能)。
注意:我是在 WinXP 上创建的示例,它会占用端口 80 作为己用。而且,由于没有 http.sys 内核,我不得不将端口更改为 8020(或您机器上的任何其他可用端口);如果您使用的是 Win2003,则不会遇到这种情况。 |
步骤 3:创建客户端
好吧,标题只有 50% 是正确的。从业务角度来看,我们实际上是在构建客户端,因为它是向服务请求信用额度的应用程序;它是发起由获取额度需求的流程的源头。
然而,从技术角度来看,您将看到客户端应用程序也像服务应用程序一样公开了服务契约;因此,从技术上讲,客户端应用程序也是一个服务。整个周期将如下所示:
- 客户端调用服务,请求某个用户 ID 的信用额度。服务公开一个服务契约和一个由客户端通过生成的代理消费的操作。
- 客户端调用被放入“requestqueue”中。
- 服务从“requestqueue”中获取消息。
- 服务处理请求并调用客户端,将响应传达给它。在这种情况下,客户端也公开了一个服务契约和一个由服务通过生成的代理消费的操作。
- 服务响应被放入“responsequeue”中(稍后讨论)。
- 客户端从“responsequeue”中获取响应消息。
下面的代码显示了在客户端应用程序中定义的服务契约:
[ServiceContract]
public interface ICreditLimitResponse
{
[OperationContract(IsOneWay = true)]
void SendCreditLimitResponse(double limit);
}
public class CreditLimitResponseService : ICreditLimitResponse
{
public void SendCreditLimitResponse(double limit)
{
System.Windows.Forms.MessageBox.Show(limit.ToString());
}
}
再次注意,我们将操作标记为 IsOneWay = true
。这确保我们使用的是单向 MEP(稍后详述)。
与服务应用程序类似,客户端应用程序中的服务托管在一个 Windows 应用程序中,用于启动和停止服务。
private void button1_Click(object sender, EventArgs e)
{
myServiceHost = new ServiceHost(typeof(CreditLimitResponseService));
myServiceHost.Open();
MessageBox.Show("Service Started");
}
private void button2_Click(object sender, EventArgs e)
{
myServiceHost.Close();
MessageBox.Show("Service Stoped");
}
最后,客户端应用程序的配置如下:
上面的配置与服务配置类似。注意使用了“responsequeue”私有队列,它将接收发送给客户端的消息。
步骤 4:生成代理
好的,现在我们已经创建了一个服务应用程序和一个客户端应用程序。服务应用程序定义了服务契约和操作,允许客户端请求信用额度。而客户端应用程序则定义了服务契约和操作,允许服务将其响应发送回来。这很棒,但整个场景将如何实现呢?
步骤 4.1:服务代理
首先,我们希望客户端将请求发送给服务,因此我们需要在客户端应用程序中生成一个代理。继续运行服务应用程序,然后单击“Start”按钮;服务将启动,如下图所示:
现在,转到客户端应用程序,并像下面这样为服务添加一个服务引用(注意,使用服务配置文件中的基地址):
现在,我们可以使用生成的代理来请求服务端的信用额度。下面的代码正是这样做的:
private void button3_Click(object sender, EventArgs e)
{
CreditLimitRequestClient proxy = new CreditLimitRequestClient();
using (TransactionScope scope =
new TransactionScope(TransactionScopeOption.Required))
{
proxy.SendCreditLimitRequest("1");
scope.Complete();
}
}
步骤 4.2:客户端代理
现在,我们要添加将结果返回给客户端的代码。回想一下,客户端已经公开了服务契约,所以我们将像处理普通 WCF 服务一样处理它。
运行客户端应用程序,然后单击“Start”按钮启动服务,如下图所示:
现在,转到服务应用程序,并像下面这样为客户端添加一个服务引用(注意,使用客户端配置文件中的基地址):
现在,您可以使用生成的代理通过更新服务契约将结果传递回客户端应用程序。更新后的服务的完整代码如下:
public void SendCreditLimitRequest(string id)
{
double value;
if (id == "1")
value = 10;
else if (id == "2")
value = 20;
else
value = 0;
CreditLimitResponseClient proxy = new CreditLimitResponseClient();
using (TransactionScope scope =
new TransactionScope(TransactionScopeOption.Required))
{
proxy.SendCreditLimitResponse(value);
scope.Complete();
}
}
运行示例
现在,让我们进入有趣的部分!运行示例并观察 WCF 队列消息的实际效果。
运行客户端应用程序,并单击“Start”按钮启动服务。现在,通过单击“Send”按钮向服务发送请求。
请注意,我们尚未运行服务应用程序的服务。因此,从客户端发送的消息将无法到达服务。它在哪里?您猜对了……去 MSMQ 检查“requestqueue”,您会发现消息正在那里等待。
现在,我们希望运行服务应用程序来获取消息;但是,就在那之前,单击“Stop”按钮停止客户端应用程序的服务……停止客户端应用程序将允许我们查看“responsequeue”中的响应,因为客户端将无法及时接收它。
现在,运行服务应用程序并单击“Start”按钮。
返回 MSMQ,您会发现消息已成功被服务应用程序消耗,并且不再存在于“requestqueue”中了。
然而,回想一下我们已经关闭了客户端应用程序。因此,本应从服务发送到客户端的响应也将挂在“responsequeue”中。通过再次检查 MSMQ 来验证这一点。
现在,最后,回到客户端应用程序并重新启动服务。立即,客户端将从 MSMQ 中获取消息(它将从“responsequeue”中消失),您将收到一个消息框,显示结果,这表明客户端已收到响应。
这就是消息持久性的最佳体现!
我们刚刚使用了什么 MEP?
尽管这看起来可能有点奇怪,在经历了所有这些工作之后,我们实际上只使用了单向 MEP。回想一下,所有操作都带有 IsOneWay = true
标记。毕竟,服务和客户端都暴露了 MSMQ 绑定,以单向方式进行通信。
公有队列又如何?
此示例在实现中使用了私有 MSMQ 队列。如前所述,队列也可以是公有的,在这种情况下,服务和客户端可以位于两台不同的机器上。
使用公有队列时,我们的程序不会改变。假设我们示例中的客户端和服务运行在两台不同的机器上;在这种情况下,场景将如下所示:
- 客户端将向服务发送请求。请求将“停留在” MSMQ 在同一机器上生成的私有队列中。
- 然后,这个中间队列会将请求发送到我们创建的另一台机器上的公有队列。然后,服务应用程序将从那里获取请求。
- 服务与客户端通信时情况相反。
示例程序
您可以在附件中找到包含客户端和服务器应用程序的解决方案。