zoukankan      html  css  js  c++  java
  • 用Duplex实现消息广播

    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预定义绑定类型中,WSDualHttpBinding和NetTcpBinding均提供了对双工通信的支持,但是两者在对双工通信的实现机制上却有本质的区别。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 { get; set; }  
    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


    [xhtml] view plain copy
     
    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

     Task task = new Task(() => Listen(cts.Token), cts.Token);  

    http://blog.csdn.net/fangxing80/article/details/6142861

  • 相关阅读:
    NXOpen测最最近距离和投影距离
    abp学习日志三(实体&聚合根)
    abp学习日志二(DDD)
    abp学习日记一(安装)
    abp学习日记 初记
    kubernetes学习——minikube入门
    windows10安装Kubernetes和MiniKube
    windows10安装docker desktop(非VMBox)
    Asp.netCore3.0 Docker 阿里云 部署 Demo
    小宝与老财
  • 原文地址:https://www.cnblogs.com/wangchuang/p/5736875.html
Copyright © 2011-2022 走看看