WCF双工通讯以及客户端间的间接通讯
由于学习计划安排不当,对WCF的认知一直停滞不前,最近工作上又用回了WCF,重拾一下,看到蒋老师介绍双工通讯的博文,实践一下,积累一下。原想着WCF的双工通讯就是原本的客户端能调用服务端的方法之余,服务端同样也能调用客户端的方法。把博文看了一遍之后发现这个双工实际上是借助了方法回调实现的。那么下面先介绍一下最基本的双工通讯形式,再介绍一下鄙人利用双工通讯设计了一种形式。
WCF通讯都是基于方法调用进行信息交互和传递,在开发基本模式的时候也需要往服务端下载元数据信息,从而让客户端知道服务端定义的方法签名,这就是契约;那么转到双工模式下,服务端调用客户端的方法,主调方也要知道方法的签名,这也是通过契约来实现,但是契约的定义并非在定义方法的客户端,仍然是在服务端,服务端定义了契约再由客户端下载了元数据后将其实现。
下面则定义了一个契约,其目的让客户端往服务端发起连接,等待服务端的回调
1 [ServiceContract(CallbackContract = typeof(ICallback))] 2 interface ILogic 3 { 4 [OperationContract] 5 void ListenToCall(); 6 }
在ServiceContract特性中,使用了CallbackContract,表名了这个契约的回调契约就是ICallback。该回调契约也是自定义的一个接口
1 [ServiceContract] 2 interface ICallback 3 { 4 [OperationContract] 5 List<int> GetHourMinuteFromClient(); 6 }
它与普通模式定义的契约一样,就是一个单单纯纯的服务契约而已。但是在服务端想调用客户端的方法时,就是调用这个ICallback接口里面的方法,在这里就是GetHourMinuteFromClient()
在服务端需要实现ILogic接口来实现契约
1 [ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple)] 2 public class LogicService:ILogic 3 { 4 5 public void ListenToCall() 6 { 7 ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>(); 8 callback.GetHourMinuteFromClient(); 9 10 } 11 }
这里实现的方法就是回调客户端的GetHourMinuteFromClient()方法,它是利用OperationContext.Current.GetCallbackChannel 来获取一个实现ICallback的对象,通过该对象则调用指定的回调方法。另外一个就是在ServiceBehavior特性上给ConcurrencyMode属性赋上Reentrant或者Multiple,这样就免得在调用回调方法时出现死锁的异常。
在客户端下载了元数据信息后,就可以实现之前的回调契约,
1 class ClassCallBack : ILogicCallback 2 { 3 4 public int[] GetHourMinuteFromClient() 5 { 6 7 int[] result= new List<int>() { DateTime.Now.Hour, DateTime.Now.Minute }.ToArray(); 8 9 Console.WriteLine("{0},{1}",result[0],result[1]); 10 11 return result; 12 } 13 }
不知这里是否与配置有关,鄙人在客户端获取到的接口名称是ILogicCallback,并非与蒋老师的Demo中一样——与服务端的ICallback接口同名。这里只是简单地输出了当前的小时和分钟,并作返回。
最后讲讲配置,能支持双工通讯的binding只有WSDualHttpBinding和NetTcpBinding(自定义的除外),我这里就用了NetTcpBinding。
1 <system.serviceModel> 2 <services> 3 <service name="Logic.LogicService" behaviorConfiguration="te"> 4 <host> 5 <baseAddresses> 6 <add baseAddress="net.tcp://127.0.0.1:8004/LogicService"/> 7 </baseAddresses> 8 </host> 9 <endpoint address="" binding="netTcpBinding" contract="Logic.ILogic" bindingConfiguration="transportNetTcpBinding"/> 10 <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/> 11 </service> 12 </services> 13 <bindings> 14 <netTcpBinding> 15 <binding name="transportNetTcpBinding" maxReceivedMessageSize="2147483647" maxBufferPoolSize="2147483647"> 16 <readerQuotas maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxDepth="2147483647" 17 maxNameTableCharCount="2147483647" maxStringContentLength="2147483647"/> 18 <security mode="None"/> 19 </binding> 20 </netTcpBinding> 21 </bindings> 22 <behaviors> 23 <serviceBehaviors> 24 <behavior name="te"> 25 <serviceMetadata httpGetEnabled="false"/> 26 </behavior> 27 </serviceBehaviors> 28 </behaviors> 29 </system.serviceModel>
客户端的配置信息鄙人没有自己去写了,直接从服务引用那里粘贴生成的,就可以用得上。
1 <system.serviceModel> 2 <bindings> 3 <netTcpBinding> 4 <binding name="NetTcpBinding_ILogic"> 5 <security mode="None" /> 6 </binding> 7 </netTcpBinding> 8 </bindings> 9 <client> 10 <endpoint address="net.tcp://127.0.0.1:8004/LogicService" binding="netTcpBinding" 11 bindingConfiguration="NetTcpBinding_ILogic" contract="Proxy.ILogic" 12 name="NetTcpBinding_ILogic" /> 13 </client> 14 </system.serviceModel>
在客户端那里调用没有按照蒋老师说介绍的利用通道工厂来创建客户端对象,而是直接利用一个实现了ILogic接口的类来调用,那个类不是自己定义的,也是通过服务引用那里生成得来的。
1 static void Main(string[] args) 2 { 3 InstanceContext context = new InstanceContext(new ClassCallBack()); 4 LogicClient client = new LogicClient(context); 5 client.Open(); 6 //using (client as IDisposable) 7 //{ 8 client.ListenToCall(); 9 //Console.ReadKey(); 10 //} 11 Console.ReadKey(); 12 }
蒋老师的博文上有用了using语句块的,但我这里没加上去也行,并没有抛异常,加了上去会更保险吧!
那么鄙人在实际中遇到了这么一个情况,网络通讯已经采用了WCF框架,如果要换技术的话得大动干戈了,想要实现两个客户端之间的通讯,如果像使用Socket实现两个客户端之间直接通讯,那个还比较简单,但是用了WCF之后就比较麻烦了,客户端于客户端之间没法直接通讯,所有交互都是往服务端发请求,调用方法。那么只能使用WCF的双工通讯来实现了,WCF的服务端在整个结构而言就相当于一个中介者
这只是一个通讯的过程,在通讯之前的话肯定是每个客户端都连接一下服务端,让服务端记录了客户端的信息,当有客户端发出与别的客户端通讯的请求时,服务端就会查找出之前记录的信息,调用相应客户端的回调方法来实现。这个过程没分析过其资源的占用情况,但经过实践得出是可行的。
基于上面的代码,契约里面则需要多增加一个方法,从而使得客户端能往服务端发送取数请求
1 [ServiceContract(CallbackContract = typeof(ICallback))] 2 interface ILogic 3 { 4 [OperationContract] 5 void ListenToCall(); 6 7 [OperationContract] 8 List<int> GetHourMinute (); 9 }
回调的契约不需要作改动,实现ILogic的类就改成这样
1 [ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple)] 2 public class LogicService:ILogic 3 { 4 protected delegate List<int> callbackDelegate(); 5 6 private static Dictionary<string, callbackDelegate> clientLst; 7 8 protected static Dictionary<string, callbackDelegate> ClientLst 9 { 10 get 11 { 12 if (clientLst == null) 13 clientLst = new Dictionary<string, callbackDelegate>(); 14 return clientLst; 15 } 16 } 17 18 public void ListenToCall() 19 { 20 ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>(); 21 MessageProperties properties = OperationContext.Current.IncomingMessageProperties; 22 RemoteEndpointMessageProperty endpoint = 23 properties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty; 24 ClientLst[endpoint.Address+endpoint.Port] = callback.GetHourMinuteFromClient; 25 26 } 27 28 29 public List<int> GetHourMinute () 30 { 31 //return new List<int>() { DateTime.Now.Hour, DateTime.Now.Minute }; 32 List<int> result= ClientLst.First().Value.Invoke(); 33 34 return result; 35 } 36 }
ListenToCall的作用就是相当于客户端给服务端报个到,让服务端记录了客户端的IP地址端口号这些信息,还要把回调方法的委托记录下来,这些信息都存在了一个Dictionary<string, callbackDelegate> 的字典集中。在另一个方法GetHourMinute方法里面就负责按照指定的IP地址端口号来调用委托,这样就能调取指定客户端的方法了,不过上面的代码只是一个很基础很基础的示范,很多安全性的判断没加上去。
本篇博文就此结束了,关于那个客户端间接通讯的有什么意见和建议恳请大家多多提出,鄙人虚心接受,谢谢!