本文的出發點
通過閱讀本文,您能理解以下知識:
- WCF定義了哪幾種消息交換模式?
- One-Way Calls
- Request/Reply
- Duplex
- 用示例來解析WCF的消息交換模式
本文適合的讀者
本文涉及到了SOA中的消息交換的基礎概念,需要一些初級的Xml Web Service和分佈式系統開發的經驗,最好理解WCF架構
WCF定義了哪幾種消息交換模式?
WCF定義了三種消息交換方式 ,分別爲:
- One-Way Calls
- Request/Reply
- Duplex
One-Way Calls
在幾種消息交換模式中,one-way calls是最沒良心的,對於客戶端,one-way calls就如肉包子打狗,有去無回。下面的圖示給出這種交換模型的特徵:
在這種交換模式中,存在着如下的特徵
- 沒有返回值,返回類型只能爲void(但它會有一個狀態碼返回的)
- 不能包含ref或者out類型的參數
- 只有客戶端發起請求,服務端並不會對請求進行回覆。
通過設置OperationContract的IsOneWay=True可以將滿足要求的方法設置爲這種消息交換模式,方法如下:
[OperationContract(IsOneWay=true)]
void Test(int intVal);
上面的代碼,就是將方法Test設置成爲了one-way call的消息交換模式,注意如果Test方法的返回類型不是void或者帶有ref或者out類型的參數,都會拋出異常InvalidOperationException,類似下面列表中的方法均不能被聲明爲one-way模式(大家要舉一反三哦)
int Test(int intVal);
int Test();
int Test();
void Test(ref int intVal);
void Test(out int intVal);
Request/Reply
request/reply比起one-way來說,就更懂得禮尚往來,它是缺省的消息交換模式,類似於http協議中的請求/響應模型。下面的圖示給出這種交換模式的特徵:
這種交換模式是使用最多的一中,它有如下特徵:
- 調用服務方法後需要等待服務的消息返回,即便該方法返回 void 類型
- 相比Duplex來講,這種模式強調的是客戶端的被動接受,也就是說客戶端接受到響應後,消息交換就結束了。
- 在這種模式下,服務端永遠是服務端,客戶端就是客戶端,職責分明。
它是缺省的消息交換模式,設置OperationContract便可以設置爲此種消息交換模式
[OperationContrac]
void Test(int intVal);
注意,儘管Test方法返回爲void,但Server也會生成reply響應併發送給client.有來有往是這種模式的特徵。
Duplex
這種交換模式比起上面兩種,比較複雜,它和request/reply模式類似,也是有來有往,但處理過程卻比request/reply要複雜,因爲它可以在處理完請求之後,通過請求客戶端中的回調進行響應操作,這種模式的圖示爲:
注意,這種方式和request/reply方式的圖示也很類似,但二者存在着至關重要的不同,它在客戶端也有監聽節點,在callback的時候,服務器和客戶端的角色會進行交換,服務端此時成了嚴格意義上的客戶端,而客戶端此時能接受服務端的callback請求,所以成爲了服務端。呵呵,辯證法,都拗口死了,但事實就是這種,就像對與錯一樣,會相互轉換,失敗是成功之母,而成功是失敗之源。廢話少說,Duplex的特徵主要包括
- 消息交換過程中,服務端和客戶端角色會發生調換
- 服務端處理完請求後,返回給客戶端的不是reply,而是callback請求。
打個比方,Reqeust/Reply方式像是搓澡,1個管搓,1個被搓
而duplex像是拳擊,兩個人都會出拳
Duplex模式對Bindding有特殊的要求,它要求支持Duplex MEP(Message Exchange Pattern),如WSDualHttpBinding和NetTcpBinding,有關Binding的介紹請參見http://blog.csdn.net/goright12/article/details/8848380#t0
用示例來解析WCF的消息交換模式
建立示例的步驟不做具體闡述,下面看一下項目的最終結構:
下表說明各個項目的作用
項目名稱 |
項目作用 |
包含文件 |
Jillzhang.Messaging.Contract |
定義WCF服務端和客戶端共同使用的Contract接口 |
IOneWayJob.cs INormalJob.cs IJob.cs ICallback.cs |
Jillzhang.Messaging.Service |
實現WCF服務的Contract |
OneWayJob.cs NormalJob.cs Job.cs |
Jillzhang.Messaging.Host |
一個Console應用程序,用於承載WCF服務端 |
Program.cs App.config |
Jillzhang.Messaging.WebSite |
一個用於WebSite,用於承載WCF服務。是例外一中Host |
OnewayService.svc NormalJobService.svc JobService.svc web.config |
Jillzhang.Messaging.Client |
WCF客戶端,一個Console應用程序 |
OnewayProxy.cs NormalJobProxy.cs DuplexProxy.cs MyCallback.cs Program.cs app.config |
下面就看下如何定義消息交換模式爲one-way的Contract接口
而IOneWayJob的實現類代碼爲:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jillzhang.Messaging.Contract;
namespace Jillzhang.Messaging.Service
{
public class OneWayJob : IOneWayJob
{
public void Do(string jobName)
{
System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch();
watcher.Start();
System.Threading.Thread.Sleep(1000);
Console.WriteLine("服務" + AppDomain.CurrentDomain.FriendlyName + "執行任務:" + jobName);
watcher.Stop();
}
}
}
Request/reply的Contract接口定義如下:
而INormalJob的實現代碼如下:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jillzhang.Messaging.Contract;
namespace Jillzhang.Messaging.Service
{
public class NormalJob:INormalJob
{
public string Do(string jobName)
{
try
{
System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch();
watcher.Start();
System.Threading.Thread.Sleep(1000);
Console.WriteLine("服務" + AppDomain.CurrentDomain.FriendlyName + "執行任務:" + jobName);
watcher.Stop();
return "成功";
}
catch
{
return "失敗";
}
}
}
}
Duplex的交換模式需要現定義Callback的Contract接口,如下:
而服務端的Contract接口爲:
Duplex的Contract實現爲:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jillzhang.Messaging.Contract;
using System.ServiceModel;
namespace Jillzhang.Messaging.Service
{
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple)]
public class Job:IJob
{
public string Do(string jobName)
{
try
{
ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>();
System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch();
watcher.Start();
System.Threading.Thread.Sleep(1000);
Console.WriteLine("服務" + AppDomain.CurrentDomain.FriendlyName + "執行任務:" + jobName);
watcher.Stop();
callback.Done((int)watcher.ElapsedMilliseconds);
return "成功";
}
catch
{
return "失敗";
}
}
}
}
下面,我們來看一下,如何創建承載服務的應用程序,首先在app.config做如下配置
而Host的代碼如下:
而客戶端的配置文件,如下:
<configuration>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="netTcpBinding" />
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://localhost:6987/Service/duplex" binding="netTcpBinding"
bindingConfiguration="netTcpBinding" contract="Jillzhang.Messaging.Contract.IJob"
name="NetTcpBinding">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="net.tcp://localhost:6987/Service/oneway" binding="netTcpBinding"
bindingConfiguration="netTcpBinding" contract="Jillzhang.Messaging.Contract.IOneWayJob"
name="NetTcpBinding">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="net.tcp://localhost:6987/Service/normal" binding="netTcpBinding"
bindingConfiguration="netTcpBinding" contract="Jillzhang.Messaging.Contract.INormalJob"
name="NetTcpBinding">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
需要注意的是:在設定Duplex模式時,如果服務端採用的是WsDualHttpBinding,而不是本文中的NetTcpBinding,最好指定以下clientBaseAddress,默認情況下,clientBaseAddress會嘗試用80端口,可通常情況80端口都是被佔用,你需要設置一個其他端口。
因爲回調的Contract實現是在客戶端的,所以需要在客戶端實現1個ICallback實現,代碼如下:
下面是客戶端調用的代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jillzhang.Messaging.Contract;
namespace Jillzhang.Messaging.Client
{
class Program
{
static void Main(string[] args)
{
Program p = new Program();
p.OneWayCall();
p.NormalCall();
p.DuplexCall();
Console.Read();
}
void DuplexCall()
{
try
{
MyCallback callback = new MyCallback();
IJob ws = new JobClient(new System.ServiceModel.InstanceContext(callback));
Console.WriteLine("--------------------Duplex Calls ---------------------------");
Console.WriteLine("開始調用服務");
string result = ws.Do("duplex job");
Console.WriteLine("收到返回信息:" + result);
Console.WriteLine("-------------------------------------------------------------");
Console.WriteLine("\r\n\r\n\r\n");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
void OneWayCall()
{
try
{
Console.WriteLine("-----------------------One-Way Calls-----------------------");
IOneWayJob ws = new OneWayJobClient();
ws.Do("one-way job");
Console.WriteLine("請求完成!");
Console.WriteLine("-------------------------------------------------------------");
Console.WriteLine("\r\n\r\n\r\n");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
void NormalCall()
{
try
{
Console.WriteLine("-----------------------Request/Reply Calls-----------------------");
INormalJob ws = new NormalJobClient();
string result = ws.Do("request/reply job");
Console.WriteLine("請求完成,返回結果:"+result);
Console.WriteLine("-------------------------------------------------------------");
Console.WriteLine("\r\n\r\n\r\n");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}