細說WCF中的會話模式
大家都知道WCF會話模式有幾個要求:1、會話契約;2、綁定支持;3、實例模式為PerSession。這幾個要素是WCF支持的必要條件。
- 會話契約:由服務端提供實現,客戶端調用時只持有契約定義,所以需要通過契約定義告知客戶端,服務端是支持會話的。
- 綁定:會話沒有綁定的支持也就無從談起了。
- InstanceContextMode為PerSession。通過它可以保證在會話期間,服務實例不會被銷毀。
較為復雜的問題在于ServiceContract中SessionMode的設置。SessionMode定義如下:
// 指定可用于指示支持協定需要或支持的可靠會話的值。
public enum SessionMode
{
// 摘要:
// 指定當傳入綁定支持會話時,協定也支持會話。
Allowed = 0,
//
// 摘要:
// 指定協定需要會話綁定。如果綁定并未配置為支持會話,則將引發異常。
Required = 1,
//
// 摘要:
// 指定協定永不支持啟動會話的綁定。
NotAllowed = 2,
}
通過以上SessionMode的枚舉定義可知:Required肯定是強制啟用會話;NotAllowed強制不之處會話;Allowed允許啟用會話。最麻煩的要數Allowed。首先Allowed是他是支持會話的,其次:它允許并不意味著客戶端與服務端的信息交互一定是會話模式的。那么在什么情況下我們將契約定義為允許會話,服務端與客戶端之間的通訊是會話模式?什么情況下又是非會話模式呢?會話模式與可靠會話之間有沒有什么關系?在啟用會話模式時,客戶端的SessionId與服務端的SessionId一定是完全匹配的嗎?帶著這些問題,來進行一些說明。
1、綁定
先從綁定說起:basicHttpBinding它不能在信息頭中嵌入SessionId,客戶端與服務端之間基于Http的通訊也就不可能維持會話了;Msmq模式下,服務端與客戶端可以是基于離線模式的,因此也不支持會話。對于Tcp類型的綁定,如NetTcpBinding與NetNamedPipeBinding由于使用的是帶連接的Tcp作為傳輸協議,所以它是能支持會話的。
2、SessionMode下的SessionId
可以通過OperationContext的SessionId屬性來訪問SessionId。
服務端訪問SessionId:
在客戶端,需要先初始化OperationContextScope,后才能訪問SessionId。如下:
using (OperationContextScope contextScope = new OperationContextScope(contextChannel))
{
string sessionId=OperationContext.Current.SessionId);
}
3、SessionMode=SessionMode.Allowed下服務端與客戶端的SessionId匹配程度
討論之前,先給出契約以及服務實現。如下:
public interface IAdd
{
[OperationContract]
int Add(int x,int y);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class AddService : IAdd,IDisposable
{
#region IAdd Members
public int Add(int x, int y)
{
if (null != OperationContext.Current.SessionId)
{
Console.WriteLine("SessionId is {0}", OperationContext.Current.SessionId);
}
else
{
Console.WriteLine("SessionId is null");
}
return x + y;
}
#endregion
public void Dispose()
{
Console.WriteLine("Dispose Thread Id is {0}",System.Threading.Thread.CurrentThread.ManagedThreadId);
}
}
3.1、NetTcpBinding的會話
3.1.1、啟用可靠會話
在配置文件中進行配置即可。如下:
調用之后訪問SessionId:
{
IAdd proxy = channelFactory.CreateChannel();
Console.WriteLine("result is {0}", proxy.Add(1, 2));
IContextChannel contextChannel = proxy as IContextChannel;
using (OperationContextScope contextScope = new OperationContextScope(contextChannel))
{
Console.WriteLine("operationContextScope hashcode is {0}", contextScope.GetHashCode());
if (null == OperationContext.Current.SessionId)
{
Console.WriteLine("SessionId is null");
}
else
{
Console.WriteLine("Session is {0}", OperationContext.Current.SessionId);
}
}
}
服務端輸出:
客戶端輸出:
在客戶端調用之前獲取SessionId:
服務端輸出如下:
客戶端輸出如下:

如果在調用服務之前,手動打開代理,情況怎樣呢?
將客戶端的代碼做如下修改:
(proxy as ICommunicationObject).Open();
IContextChannel contextChannel = proxy as IContextChannel;
using (OperationContextScope contextScope = new OperationContextScope(contextChannel))
{
Console.WriteLine("operationContextScope hashcode is {0}", contextScope.GetHashCode());
if (null == OperationContext.Current.SessionId)
{
Console.WriteLine("SessionId is null");
}
else
{
Console.WriteLine("Session is {0}", OperationContext.Current.SessionId);
}
}
Console.WriteLine("result is {0}", proxy.Add(1, 2));
服務端輸出:
客戶端輸出:

3.1.2 禁用可靠會話(NetTcpBinding默認)
服務端輸出:

客戶端輸出:

小結:在NetTcpBinding綁定下,如果啟用可靠會話傳輸,則服務端與客戶端的SessionId是相同的。如果禁用可靠會話,則兩者SessionId是不一樣的。另外,在進行第一次調用前,客戶端獲取不到SessionId。除非在調用之前手動打開代理。
3.2、ws*綁定
在ws-*綁定中,WCF通過在消息頭中加入SessionId,通過SessionId來識別客戶端(準確來說是代理)。以下使用WsHttpBinding進行說明。
3.2.1、禁用可靠會話(調用之前手動打開代理)
服務端輸出:
客戶端輸出:
3.2.2、啟用可靠會話
服務端輸出:

客戶端輸出:
3.2.3、禁用會話(不手工打開代理,調用之前獲取SessionId)
服務端輸出:

小結:可靠會話對客戶端SessionId有影響。在開啟可靠會話時,如客戶端在調用之前手動打開代理,則客戶端與服務端的SessionId相同;如果調用之前不手動打開代理,則客戶端獲取不到SessionId,只有在第一次調用后才能獲取到SessionId;在禁用可靠會話時,客戶端在不手動打開代理的情況下調用服務會發生異常。
另外說明:使用Tcp作為傳輸協議,通過三次握手,它通過超時、丟包重傳的機制保證客戶端到服務端的消息傳輸成功,但與WCF中消息的可靠會話是有本質區別的。WCF中通過自身機制保證從RM源到目標源消息的發送以及確認機制、以及服務端中消息從目標源到最終交付對象之間消息的交付機制。使用NetTcpBinding時,我們可以通過它的默認構造函數看看構成的綁定元素有哪些。如下代碼:
BindingElementCollection collection = binding.CreateBindingElements();
foreach (BindingElement bindingElement in collection)
{
Console.WriteLine(bindingElement.GetType());
}
Console.WriteLine("--------使用構造函數,設置NetTcpBinding允許可靠會話--------");
var binding2 = new NetTcpBinding(SecurityMode.None,true);
BindingElementCollection collection2 = binding2.CreateBindingElements();
foreach (BindingElement bindingElement in collection2)
{
Console.WriteLine(bindingElement.GetType());
}
輸出如下:
上圖中ReliableSessionBindingElement就是創建可開會話信道的。
總結:不同類型的協議對于SessionId的獲取是有影響的。無論對于TCP協議還是ws-* 協議,如果客戶端調用之前不手動打開代理,則調用之前客戶端是獲取不到SessionId的;在進行第一次調用之后,客戶端才能獲取到與服務端相同的SessionId(因為進行調用時,會自動打開代理).
是否啟用會話也會影響到SessionId。對于TCP協議來說,禁用可靠會話,客戶端獲取到SessionId與服務端是不一樣的;對于ws-* 協議而言:如果禁用可靠會話,調用之前如果不手動代開代理,則調用會發生異常。
浙公網安備 33010602011771號