WCF服務通信測試


知識需要反復咀嚼,常讀常新,簡單的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;
    }
}
View Code

<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();
        }
    }
}
View Code

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);
    }
}
View Code

<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);
    }
}
View Code

<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);
    }
}
View Code

<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);
    }
}
View Code

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;
        }
    }
}
View Code

<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);
        }
    }
}
View Code

<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監聽器可以看到,服務是不可見的
        }
    }
}
View Code

<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;
        }
    }
}
View Code

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.運行效果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM