由於學習計划安排不當,對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地址端口號來調用委托,這樣就能調取指定客戶端的方法了,不過上面的代碼只是一個很基礎很基礎的示范,很多安全性的判斷沒加上去。
本篇博文就此結束了,關於那個客戶端間接通訊的有什么意見和建議懇請大家多多提出,鄙人虛心接受,謝謝!