在开发分布式应用程序时,经常会遇到需要在客户端和服务端之间进行异步通信的场景。Windows Communication Foundation (WCF) 提供了强大的服务端推送功能,但实现起来可能会有些复杂。本文将介绍一种使用 WCF 和 Microsoft Message Queuing (MSMQ) 实现服务端推送的方法。
需求如下:
虽然 IIS WebService 是一个选项,但实现起来相对复杂,特别是需要一个公共客户端队列。
找到了一个解决方案,即使用 Microsoft Message Queuing 系统。客户端和服务端都有一个消息队列,用于相互通信。双方在发送消息时不需要运行对方。
为了实现这个解决方案,需要满足以下条件:
如果需要 WebService 进行外部互联网通信,可以创建一个包装器,它提供 WebService 端点并创建内部队列消息。这样可以消除客户端和服务端需要看到对方消息队列的要求。
客户端和服务端的通信流程如下:
客户端通过以下代码注册:
C# RegisterClientServiceClient registerClientServiceClient =
new RegisterClientServiceClient(
"RegisterClientEndPoint");
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
ClientInfo clientInfo = new ClientInfo();
clientInfo.GroupName = ConfigurationManager.AppSettings["GroupName"];
clientInfo.ClientName = System.Net.Dns.GetHostName();
clientInfo.PushMessageBoxAddress =
"net.msmq://" + clientInfo.ClientName + "/private/WcfServerPush/ClientMessageBox";
registerClientServiceClient.RegisterClient(clientInfo);
scope.Complete();
}
RegisterClientServiceClient 类继承自 RegisterClientIService,并使用 ClientBase 模板。这是标准的WCF实现。
服务器端的 RegisterClient 实现如下:
C# public class RegisterClientService : RegisterClientIService
{
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void RegisterClient(ClientInfo clientInfo)
{
WcfServerPushServerBO.Instance.RegisterClient(clientInfo);
}
}
在服务器端创建 MSMQ ServiceHost 非常简单:
C# if (!MessageQueue.Exists(registerClientMSMQName))
{
MessageQueue versionManagerQueue = MessageQueue.Create(registerClientMSMQName, true);
versionManagerQueue.SetPermissions(@"Everyone", MessageQueueAccessRights.FullControl);
versionManagerQueue.SetPermissions(@"ANONYMOUS LOGON", MessageQueueAccessRights.ReceiveMessage | MessageQueueAccessRights.PeekMessage | MessageQueueAccessRights.WriteMessage);
}
客户端实现与服务器端类似,但有一些小的修改。因为需要 WPF 注册一个事件,以便在接收到新消息时通知 ServiceHost。
C# [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class PushMessageService : MessageBoxIService, INotifyPropertyChanged
需要修改 App.Config 来定义服务。还可以给端点提供一个 http 绑定(不仅仅是 netMsmqBinding),以便将来提供元数据交换能力。
XML <service name="WcfServerPushServer.RegisterClientService" behaviorConfiguration="WcfServerPushServer.RegisterClientServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8001/WcfServerPush/RegisterClientServiceHttp/" />
</baseAddresses>
</host>
<endpoint address="net.msmq://localhost/private/WcfServerPush/RegisterClientService" binding="netMsmqBinding" bindingConfiguration="srmpBinding" contract="WcfServerPushIServices.RegisterClientIService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
客户端需要识别端点:
XML <client>
<endpoint name="RegisterClientEndPoint" address="net.msmq://localhost/private/WcfServerPush/RegisterClientService" binding="netMsmqBinding" bindingConfiguration="netMsmqBindingConfig" contract="WcfServerPushIServices.RegisterClientIService" />
</client>
WcfServerPush - 一个 WPF 项目,既可以作为客户端也可以作为服务器应用程序。可以在任一侧安装应用程序,并自行决定运行什么。WPF 项目还负责创建消息队列(如果未找到)并启动服务主机。在实际开发项目中,将视图与引导程序分离非常重要。
WcfServerPushServer - 服务器端服务和业务对象,包括客户端的队列。
WcfServerPushClient - 客户端服务。