知識需要反復咀嚼,常讀常新,簡單的WCF通信測試:basicHttpBinding(基本通信)\netTcpBinding(雙工通信)\netMsmqBinding(消息隊列),簡單的測試Demo。
簡單說一下代碼結構,后續整理一下具體的實現邏輯,為什么這么處理。
1.WCFTest.DataContracts類庫代碼(基礎數據契約類庫)
<1>.OrderItem.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.Runtime.Serialization; using System.Threading.Tasks; namespace WCFTest.DataContracts { [DataContract] public class OrderItem { [DataMember] public Guid ProductID; [DataMember] public string ProductName; [DataMember] public decimal UnitPrice; [DataMember] public int Quantity; } }
<2>.Order.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.Runtime.Serialization; using System.Threading.Tasks; namespace WCFTest.DataContracts { [DataContract] [KnownType(typeof(OrderItem))] public class Order { [DataMember] public Guid OrderNo; [DataMember] public DateTime OrderDate; [DataMember] public Guid SupplierID; [DataMember] public string SupplierName; [DataMember] public List<OrderItem> OrderItems=new List<OrderItem> (); //如果不這樣的構建方式,使用請使用get;set;,並且Order構造函數實例化(new)這個集合 /// <summary> /// 訂單信息描述 /// </summary> /// <returns></returns> public override string ToString() { StringBuilder strBuilder = new StringBuilder(); strBuilder.AppendLine("General Information:\n\t"); strBuilder.AppendLine(string.Format("Order No:{0}", OrderNo)); strBuilder.AppendLine(string.Format("Order Date:{0}", OrderDate.ToShortDateString())); strBuilder.AppendLine(string.Format("SupplierID:{0}", SupplierID)); strBuilder.AppendLine(string.Format("SupplierName:{0}", SupplierName)); strBuilder.AppendLine("\nProducts:"); foreach (OrderItem order in OrderItems) { strBuilder.AppendLine(string.Format("ProductID:{0}", order.ProductID)); strBuilder.AppendLine(string.Format("ProductName:{0}", order.ProductName)); strBuilder.AppendLine(string.Format("UnitPrice:{0}", order.UnitPrice)); strBuilder.AppendLine(string.Format("Quantity:{0}", order.Quantity)); } return strBuilder.ToString(); } } }
2.WCFTest.Contracts類庫代碼(服務端和客戶端通信契約接口)
<1>.ICalculator.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.Threading.Tasks; namespace WCFTest.Contracts { [ServiceContract] public interface ICalculator { [OperationContract] double Add(double numA, double numB); [OperationContract] double Sub(double numA, double numB); [OperationContract] double Multiply(double numA, double numB); [OperationContract] double Divide(double numA, double numB); } }
<2>.IDuplexCallBack.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.Threading.Tasks; namespace WCFTest.Contracts { public interface IDuplexCallBack { [OperationContract(IsOneWay = true)] void DisplayResult(double result); } }
<3>.IDuplexContract.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.Threading.Tasks; namespace WCFTest.Contracts { #region BaseInfoKnown /* * 如何理解雙工通信? * 請求過程中的回調,雙工消息交換模式的表現形式,客戶端調用服務的時候,附加上一個回調對象; * 服務端在處理該請求中,通過客戶端附加的回調對象(調用回調服務的代理對象)回調客戶端操作(該操作在客戶端執行)。 * 整個消息交換過程由兩個基本的消息交換,其一客戶端正常的服務請求,其二服務端對客戶端的回調。兩者可以采用請求-回復模式,也可以采用單向(One-way)的MEP進行消息交換。 * * 如何模擬測試? * 本例采用另外一種截然不同的方式調用服務並進行結果的輸出: * 通過單向(One-way)的模式調用CalculuateService(也就是客戶端不可能通過回復消息得到計算結果),服務端在完成運算結果后,通過回調(Callback)的方式在客戶端將計算結果打印出來。 */ #endregion [ServiceContract(CallbackContract = typeof(IDuplexCallBack))] public interface IDuplexContract { [OperationContract(IsOneWay = true)]//單向(只是客戶端請求,未做響應[回復]) void Add(double numA, double numB); } }
<4>.IMSMQContract.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using WCFTest.DataContracts; using System.Threading.Tasks; namespace WCFTest.Contracts { #region BaseInfoKnown /* * 比如,在Intranet內,通過TCP進行高效的數據通信;而在Internet內,通常使用Http進行跨平台數據交換。 * 這些通信的特點都是基於Connection的,也就是說,交互雙方必須有一個可用的Connection存在於他們之間。 * 而在某些時候比如用戶撥號、便攜式計算機的用戶,不能保證需要Server時有一個可用的Connection。這時候,基於Messaging Queue尤為重要。 * MSMQ的好處: * <1>.MSMQ是基於Disconnection。這種通信方式為離線工作成為了可能。 * <2>.MSMQ天生是One-way、異步的。對於用戶的請求,Server端無需立即相應,也就是說,Server對數據的處理無需和Client的數據發送進行同步。這樣可以避免峰值負載。 * <3>.MSMQ能夠提供高質量的Reliable Messaging。異步通信,無法獲知Message是否抵達Server端,也無法獲知Server端的執行結果和出錯信息。 * MSMQ提供Reliable Messaging的機制: * a.超時機制(Timeout) * b.確認機制(Acknowledgement) * c.日志機制(Journaling):消息被發送或接收后,被Copy一份。 * d.死信隊列(Dead letter Queue):保存發送失敗的message。 * * 在WCF中,MSMQ的數據傳輸功能被封裝在一個Binding中,提供WCF Endpoint之間、以及Endpoint和現有的基於MSMQ的Application進行通信的實現。 * 提供兩種不同的built-in binding: * <1>.NetMsmqBinding:從提供的功能和使用方式上看,和一般的使用的binding一樣,所不同的是,它提供的是基於MSMQ的Reliable Messaging。變成模式和一般的binding完全一樣。 * <2>.MsmqIntergrationBinding:主要用於將我們的WCF Application和現有的基於MSMQ的Application集成的情況。 * 實現了WCF Endpoint和某個Message Queue進行數據的通信,具體來說,就是實現了單一的向某個Message Queue發送Message,和從某個Message Queue中接收Message的功能。 * * MSMQ則有效地提供了這樣的機制:Server端建立一個Message Queue來接收來個客戶的訂單,客戶端通過向該Message Queue發送承載了訂單數據的Message實現訂單的遞交。 * 如果客戶在離線的情況,仍然可以通過客戶端進行訂單遞交的操作,存儲着訂單數據的Message會被暫保在本地的Message Queue中,一旦客戶聯機,MSMQ將Message從中取出,發送到真正的接收方,這個動作對於用戶的透明的。 */ #endregion [ServiceContract] [ServiceKnownType(typeof(Order))] public interface IMSMQContract { [OperationContract(IsOneWay = true)] void SubmitOrder(Order order); } }
3.WCFTest.Services類庫代碼(契約接口業務功能實現)
<1>.CalculatorService.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using WCFTest.Contracts; using System.Threading.Tasks; namespace WCFTest.Services { public class CalculatorService : ICalculator { public double Add(double numA, double numB) { return numA + numB; } public double Sub(double numA, double numB) { return numA - numB; } public double Multiply(double numA, double numB) { return numA * numB; } public double Divide(double numA, double numB) { if (numB != 0) { return numA / numB; } return 0; } } }
<2>.DuplexService.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using WCFTest.Contracts; using System.Threading.Tasks; namespace WCFTest.Services { /// <summary> /// 在Server端,通過OperationContext.Current.GetCallbackChannel<T>() /// 獲得Client指定的CallbackContent Instance,進行Client調用Opertion /// </summary> public class DuplexService : IDuplexContract { public void Add(double numA, double numB) { double result = numA + numB; IDuplexCallBack duplexCall = OperationContext.Current.GetCallbackChannel<IDuplexCallBack>(); duplexCall.DisplayResult(result); } } }
<3>.MSMQService.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using WCFTest.Contracts; using WCFTest.DataContracts; using System.Threading.Tasks; namespace WCFTest.Services { public class MSMQService:IMSMQContract { //設定服務端請求行為:需求事務操作\自動完成事務 [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=true)] public void SubmitOrder(WCFTest.DataContracts.Order order) { //將Order數據保存到數據庫 SaveData.SaveOrder(order); Console.WriteLine("Receive An Order:"); Console.WriteLine(order.ToString()); //服務端輸出的信息,在Host監聽器可以看到,服務是不可見的 } } }
<4>.SaveData.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using WCFTest.DataContracts; using System.Threading.Tasks; namespace WCFTest.Services { public class SaveData { private static Dictionary<Guid, Order> dicOrder = new Dictionary<Guid, Order>(); public static void SaveOrder(Order order) { if (!dicOrder.Keys.Contains(order.OrderNo)) { dicOrder.Add(order.OrderNo, order); } } public static Order GetOrder(Guid orderNo) { Order order = new Order(); if (dicOrder.Keys.Contains(orderNo)) { order=dicOrder[orderNo]; } return order; } } }
4.WCFTest.Hots 控制台程序代碼(多服務宿主程序)
<1>.App.config (服務端和客戶端通信配置ABC)
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.serviceModel> <!--配置行為--> <behaviors> <!--配置服務端行為--> <serviceBehaviors> <behavior name="CalculatorBehavior"> <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:7070/CalculatorService/Metadata"/> </behavior> </serviceBehaviors> </behaviors> <!--配置協議--> <bindings> <!--MSMQBinding配置--> <netMsmqBinding> <binding name="MSMQBinding" exactlyOnce="true"> <security mode="None"> <transport msmqAuthenticationMode="None" msmqProtectionLevel="None"/> <message clientCredentialType="None"/> </security> </binding> </netMsmqBinding> </bindings> <services> <!--CalculatorService--> <service name="WCFTest.Services.CalculatorService"> <endpoint address="http://localhost:7070/CalculatorService" binding="basicHttpBinding" contract="WCFTest.Contracts.ICalculator"> </endpoint> </service> <!--DuplexService--> <service name="WCFTest.Services.DuplexService"> <endpoint address="net.tcp://localhost:6060/DuplexService" binding="netTcpBinding" contract="WCFTest.Contracts.IDuplexContract"> </endpoint> </service> <!--MSMQService--> <service name="WCFTest.Services.MSMQService"> <endpoint address="net.msmq://localhost/Private/Orders" binding="netMsmqBinding" bindingConfiguration="MSMQBinding" contract="WCFTest.Contracts.IMSMQContract"> </endpoint> </service> </services> </system.serviceModel> </configuration>
<2>.Program.cs (多服務宿主啟用監聽)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using WCFTest.Services; using System.Configuration; using System.Threading.Tasks; using System.Xml; using System.Configuration; using System.Messaging; namespace WCFTest.Hots { /// <summary> /// 如何可以按需開啟不同的Host /// </summary> class Program { static void Main(string[] args) { #region 單Host調用測試 ////計算服務Host //CalculatorHosts(); ////雙工通信服務Host //DuplexHost(); #endregion #region 多Host調用測試 Dictionary<string, ServiceHost> dicHosts = new Dictionary<string, ServiceHost>(); dicHosts.Add("CalculatorHost", new ServiceHost(typeof(Services.CalculatorService))); dicHosts.Add("DuplexHost", new ServiceHost(typeof(Services.DuplexService))); dicHosts.Add("MSMQHost", new ServiceHost(typeof(Services.MSMQService))); foreach (KeyValuePair<string, ServiceHost> keyPair in dicHosts) { if (keyPair.Key.Contains("MSMQHost"))//MSMQHost的附加邏輯 { //MSMQ隊列不存在,或您沒有足夠的權限執行該操作異常:因為是這里的路徑\Host\Client的地址路徑問題 string strMSMQPath = @".\Private$\Orders"; //string strMSMQPath = ConfigurationManager.AppSettings["MSMQPath"]; if (!MessageQueue.Exists(strMSMQPath)) { //創建一個MessageQueue,參數說明:MSMQ的位置,支持事務隊列 MessageQueue.Create(strMSMQPath,true); } } keyPair.Value.Opened += delegate { Console.WriteLine(keyPair.Key + " Host Has Been Listening......."); }; keyPair.Value.Open(); } Console.WriteLine("\nPlease Enter Any Key To Abort All Hosts:"); Console.ReadLine(); foreach (KeyValuePair<string, ServiceHost> keyPair in dicHosts) { keyPair.Value.Abort(); keyPair.Value.Close(); Console.WriteLine(keyPair.Key + " Host Has Been Stoped!"); } #endregion Console.WriteLine("\nPlease Enter Any Key Exit:"); Console.Read(); } /// <summary> /// 計算服務Host /// </summary> private static void CalculatorHosts() { using (ServiceHost host = new ServiceHost(typeof(Services.CalculatorService))) { host.Opened += delegate { Console.WriteLine("Host Has Been Opened! Please Enter Any Key To Abort This Host:"); Console.ReadKey(); host.Abort(); host.Close(); Console.WriteLine("Host Has Been Stoped!"); }; host.Open(); } } /// <summary> /// 雙工通信服務Host /// </summary> private static void DuplexHost() { using (ServiceHost duplexHost = new ServiceHost(typeof(Services.DuplexService))) { duplexHost.Opened += delegate { Console.WriteLine("Duplex Host Has Been Listening....! Please Enter Any Key To Abort This Host:"); Console.ReadLine(); duplexHost.Abort(); duplexHost.Close(); Console.WriteLine("Duplex Host Has Been Stoped!"); }; duplexHost.Open(); } } } }
5.WCFTravelReview 控制台程序代碼 (客戶端調用)
<1>.App.config (客戶端和服務端通信配置ABC)
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.serviceModel> <bindings> <netMsmqBinding> <binding name="MSMQBinding" exactlyOnce="true"> <security mode="None"> <transport msmqAuthenticationMode="None" msmqProtectionLevel="None"/> <message clientCredentialType="None"/> </security> </binding> </netMsmqBinding> </bindings> <client > <!--CalculatorService終結點--> <endpoint name="CalculatorClient" address="http://localhost:7070/CalculatorService" binding="basicHttpBinding" contract="WCFTest.Contracts.ICalculator"></endpoint> <!--DuplexService終結點--> <endpoint name="DuplexClient" address="net.tcp://localhost:6060/DuplexService" binding="netTcpBinding" contract="WCFTest.Contracts.IDuplexContract"></endpoint> <!--MSMQService終結點--> <endpoint name="MSMQClient" address="net.msmq://loclahost/Private/Orders" binding="netMsmqBinding" bindingConfiguration="MSMQBinding" contract="WCFTest.Contracts.IMSMQContract"></endpoint> </client> </system.serviceModel> </configuration>
<2>.DuplexCallBackClient.cs (雙工通信回調接口實現)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using WCFTest.Contracts; using System.Threading.Tasks; namespace WCFTravelReview { //客戶端的回調類,顯示計算結果,實現ICallback接口 public class DuplexCallBackClient : IDuplexCallBack { public void DisplayResult(double result) { Console.WriteLine("The Result is {0}", result.ToString()); } } }
<3>.Program.cs (測試客戶端調用)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using WCFTest.Contracts; using WCFTest.DataContracts; using System.Transactions; using System.Threading.Tasks; namespace WCFTravelReview { class Program { static void Main(string[] args) { //計算服務Client CalculatorClient(); //雙工服務Client DuplexClient(); //消息隊列 MSMQClient(); Console.Read(); } /// <summary> /// 計算服務Client /// </summary> private static void CalculatorClient() { using (ChannelFactory<ICalculator> calculatorChannel = new ChannelFactory<ICalculator>("CalculatorClient")) { ICalculator proxy = calculatorChannel.CreateChannel(); using (proxy as IDisposable) { Console.WriteLine("計算通信測試:"); Console.WriteLine("請輸入數字A:"); double numA = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("請輸入數字B:"); double numB = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("WCF調用服務計算:"); Console.WriteLine("{0}+{1}={2}", numA, numB, proxy.Add(numA, numB)); Console.WriteLine("{0}-{1}={2}", numA, numB, proxy.Sub(numA, numB)); Console.WriteLine("{0}*{1}={2}", numA, numB, proxy.Multiply(numA, numB)); Console.WriteLine("{0}/{1}={2}", numA, numB, proxy.Divide(numA, numB)); } } } /// <summary> /// 雙工服務Client /// </summary> private static void DuplexClient() { #region TCP/IP DuplexInfo Known /* * 對於雙工通信 * 對於TCP/IP簇中的傳輸層協議TCP,它則是一個基於Connection的協議,在正式進行數據傳輸之前, * 必須要在Client和Server之后建立一個Connection,Connection的建立通過經典的“3次握手”。 * TCP具有Duplex的特性,就是說Connection被創建之后,從Client到Server,從Server到Client的數據傳遞可以利用同一個Connection來實現。 * 對於WCF的雙向通信,Client調用Service,Service Callback Client使用的都是同一個Channel。 */ #endregion InstanceContext instanceContext = new InstanceContext(new DuplexCallBackClient()); using (DuplexChannelFactory<IDuplexContract> duplexChannel = new DuplexChannelFactory<IDuplexContract>(instanceContext, "DuplexClient")) { IDuplexContract proxy = duplexChannel.CreateChannel(); using (proxy as IDisposable) { Console.WriteLine("\n雙工通信測試:"); Console.WriteLine("請輸入數字A:"); double numA = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("請輸入數字B:"); double numB = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("WCF調用雙工通信計算:"); proxy.Add(numA, numB); } } } /// <summary> /// MSMQ訂單Client /// </summary> private static void MSMQClient() { //創建訂單數據 Order order = new Order() { OrderNo=Guid.NewGuid(), OrderDate=DateTime.Now, SupplierID=Guid.NewGuid(), SupplierName="SupplierName" }; OrderItem orderItem = new OrderItem() { ProductID=Guid.NewGuid(), ProductName="PP", Quantity=1, UnitPrice=1 }; order.OrderItems.Add(new OrderItem() { ProductID=Guid.NewGuid(), ProductName="ProductName1", Quantity=300, UnitPrice=10 }); order.OrderItems.Add(new OrderItem() { ProductID = Guid.NewGuid(), ProductName = "ProductName2", Quantity = 10, UnitPrice = 1 }); ChannelFactory<IMSMQContract> msmqChannel = new ChannelFactory<IMSMQContract>("MSMQClient"); IMSMQContract proxy = msmqChannel.CreateChannel(); using (proxy as IDisposable) { Console.WriteLine("\n消息隊列通信測試:"); Console.WriteLine("MSMQ Submit Order To Server...."); using (TransactionScope tansactionScope = new TransactionScope(TransactionScopeOption.Required)) { proxy.SubmitOrder(order); tansactionScope.Complete(); Console.WriteLine("Order Has Been Submit! Complete This Order!"); } } } } }
6.運行效果: