WCF 消息推送

WCF中定义3种消息交换模式: 1. Request/Reply; 2. One-Way; 3. Duplex。 
Request/Reply 是缺省模式,即同步调用。在调用服务方法后需要等待服务的消息返回,即便该方法返回 void 类型。
One-Way 这种方式在调用方法后会立即返回。需要注意的是 One-Way 不能用在非void,或者包含 out/ref 参数的方法上,会导致抛出 InvalidOperationException 异常。
Duplex 又称为双工通信,实现起来比前两种来说要稍微复杂些。(1) ServiceContract 中指定 Callback类型; (2) 对于回调操作,指定[OperationContract(IsOneWay=true)] ; (3) 服务契约中通过 OperationContext.Current.GetCallbackChannel 来获得客户端 Callback 实例。

另外,在WCF预定义绑定类型中,WSDualHttpBindingNetTcpBinding均提供了对双工通信的支持,但是两者在对双工通信的实现机制上却有本质的区别。WSDualHttpBinding是基于HTTP传输协议的;而HTTP协议本身是基于请求-回复的传输协议,基于HTTP的通道本质上都是单向的。WSDualHttpBinding实际上创建了两个通道,一个用于客户端向服务端的通信,而另一个则用于服务端到客户端的通信,从而间接地提供了双工通信的实现。而NetTcpBinding完全基于支持双工通信的TCP协议。

接下来,介绍如何用WCF的Duplex消息交换实现服务端对客户端的广播。
duplex1 
1. 定义服务契约 (创建WCF Service Library工程:WcfDuplexMessageService)

[c-sharp] view plain copy
  1. using System.ServiceModel;  
  2. namespace WcfDuplexMessageService  
  3. {  
  4.     [ServiceContract(CallbackContract=typeof(IClient))]  
  5.     public interface IMessageService  
  6.     {  
  7.         [OperationContract]  
  8.         void RegisterClient();  
  9.     }  
  10.     public interface IClient  
  11.     {  
  12.         [OperationContract(IsOneWay = true)]  
  13.         void SendMessage(string message);  
  14.     }  
  15. }  

(1) 定义的IClient用于客户端回调。
(2) 定义的RegisterClient()用于将客户端回调实例注册到服务端


2. 实现服务(工程WcfDuplexMessageService)
(1) 为了所有客户端都注册到一个服务对象上,所以定义服务端为Singleton实例模式:
      InstanceContextMode=InstanceContextMode.Single (Singleton的实例在服务Host启动即实例化)
(2) 定义了一个static的List<IClient>统一保存客户端回调实例,并公开为Property,便于ServerUI能访问。
(3) 为了防止广播时不会因为客户端关闭而导致服务端异常,监听了Channel.Closing事件
     客户端关闭(Channel被关闭)时就会触发这个事件,在此事件处理中移除该客户端回调实例。

[c-sharp] view plain copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ServiceModel;  
  4. namespace WcfDuplexMessageService  
  5. {  
  6.     [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]    
  7.     public class MessageService : IMessageService, IDisposable   
  8.     {  
  9.         public static List<IClient> ClientCallbackList { getset; }  
  10.         public MessageService()  
  11.         {  
  12.             ClientCallbackList = new List<IClient>();  
  13.         }  
  14.         public void RegisterClient()  
  15.         {  
  16.             var client = OperationContext.Current.GetCallbackChannel<IClient>();  
  17.             var id = OperationContext.Current.SessionId;  
  18.             Console.WriteLine("{0} registered.", id);  
  19.             OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);  
  20.             ClientCallbackList.Add(client);  
  21.         }  
  22.         void Channel_Closing(object sender, EventArgs e)  
  23.         {  
  24.             lock (ClientCallbackList)  
  25.             {  
  26.                 ClientCallbackList.Remove((IClient)sender);  
  27.             }  
  28.         }  
  29.         public void Dispose()  
  30.         {  
  31.             ClientCallbackList.Clear();  
  32.         }  
  33.     }  
  34. }  


3. 服务端Host兼UI实现


Broadcast 按钮按下时,遍历 WcfDuplexMessageService.MessageService. ClientCallbackList 回调。
[c-sharp] view plain copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Windows.Forms;  
  5. using System.ServiceModel;  
  6. namespace WcfDuplexMessageSvcHost  
  7. {  
  8.     public partial class Form1 : Form  
  9.     {  
  10.         public Form1()  
  11.         {  
  12.             InitializeComponent();  
  13.         }  
  14.         private ServiceHost _host = null;  
  15.         private void Form1_Load(object sender, EventArgs e)  
  16.         {  
  17.             _host = new ServiceHost(typeof(WcfDuplexMessageService.MessageService));  
  18.             _host.Open();  
  19.             this.label1.Text = "MessageService Opened.";  
  20.         }  
  21.         private void Form1_FormClosing(object sender, FormClosingEventArgs e)  
  22.         {  
  23.             if (_host != null)  
  24.             {  
  25.                 _host.Close();  
  26.                 IDisposable host = _host as IDisposable;  
  27.                 host.Dispose();  
  28.             }  
  29.         }  
  30.         private void button1_Click(object sender, EventArgs e)  
  31.         {  
  32.             var list = WcfDuplexMessageService.MessageService.ClientCallbackList;  
  33.             if (list == null || list.Count == 0)  
  34.                 return;  
  35.             lock (list)  
  36.             {  
  37.                 foreach (var client in list)  
  38.                 {  
  39.                     // Broadcast  
  40.                     client.SendMessage(this.textBox1.Text);  
  41.                 }  
  42.             }  
  43.         }  
  44.     }  
  45. }  

配置:
为了客户端能直接通过公开的Metadata生成proxy,配置文件中加上:
<endpoint address="mex" binding=" mexHttpBinding" contract=" IMetadataExchange"/>
因为元数据公开的服务(IMetadataExchange)使用的是mexHttpBinding,而Duplex使用的是netTcpBinding,所以需要追加http协议对应的BaseAddress: http://localhost:9998/WcfDuplexMessageService
或者修改元数据公开服务的Binding方式:改为mexTcpBinding


  1. <?xml version="1.0"?>  
  2. <configuration>  
  3.   <system.web>  
  4.     <compilation debug="true"/>  
  5.   </system.web>  
  6.   <system.serviceModel>  
  7.     <services>  
  8.       <service name="WcfDuplexMessageService.MessageService">  
  9.         <endpoint address="" binding="netTcpBinding" bindingConfiguration="" contract="WcfDuplexMessageService.IMessageService">  
  10.           <identity>  
  11.             <dns value="localhost"/>  
  12.           </identity>  
  13.         </endpoint>  
  14.         <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>  
  15.         <host>  
  16.           <baseAddresses>  
  17.             <add baseAddress="net.tcp://localhost:9999/WcfDuplexMessageService/"/>  
  18.             <add baseAddress="http://localhost:9998/WcfDuplexMessageService"/>  
  19.           </baseAddresses>  
  20.         </host>  
  21.       </service>  
  22.     </services>  
  23.     <behaviors>  
  24.       <serviceBehaviors>  
  25.         <behavior>  
  26.           <serviceMetadata httpGetEnabled="True"/>  
  27.           <serviceDebug includeExceptionDetailInFaults="False"/>  
  28.         </behavior>  
  29.       </serviceBehaviors>  
  30.     </behaviors>  
  31.   </system.serviceModel>  
  32. <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>  


4. 客户端实现
(1) 通过Add Service Reference生成客户端Proxy

(2) 实现 WcfSvc.IMessageServiceCallback (Client.cs)
[c-sharp] view plain copy
  1. using System;  
  2. namespace WcfDuplexMessageClient  
  3. {  
  4.     public class Client : WcfSvc.IMessageServiceCallback   
  5.     {  
  6.         public void SendMessage(string message)  
  7.         {  
  8.             Console.WriteLine("[ClientTime{0:HHmmss}]Service Broadcast:{1}", DateTime.Now, message);  
  9.         }  
  10.     }  
  11. }  

(3) 启动客户端,调用服务端的注册方法:WcfSvc.MessageServiceClient,将客户端的Client实例注册到服务。
[c-sharp] view plain copy
  1. using System;  
  2. using System.ServiceModel;  
  3. namespace WcfDuplexMessageClient  
  4. {  
  5.     class Program  
  6.     {  
  7.         static void Main(string[] args)  
  8.         {  
  9.             var client = new Client();  
  10.             var ctx = new InstanceContext(client);  
  11.             var svc = new WcfSvc.MessageServiceClient(ctx);  
  12.             svc.RegisterClient();  
  13.             Console.Read();  
  14.         }  
  15.     }  
  16. }  

OK,运行一下:

补充:
1. 如果去掉回调契约的IsOneWay属性,将会导致服务端引发InvalidOperationException异常。关于Duplex的消息交换定制还可以看看这篇blog:http://www.cnblogs.com/xinhaijulan/archive/2011/01/09/1931272.html
2. XP的IIS 5.x 使用wsDuplexBinding时, 因为回调的服务监听地址默认采用是80,而80正是IIS独占的监听端口。此时会出现AddressAlreadyInUseException异常。为了解决这个问题,需要修改回调服务监听地址:wsDuplexBinding的clientBaseAddress

猜你喜欢

转载自blog.csdn.net/yangangwuwuyangang/article/details/72528577
WCF