發布——訂閱
通俗一點的解釋,就是推送。由服務端發布消息,所有訂閱了這條消息的客戶端,都會收到服務端廣播的這條消息。
WCF的雙工機制的運行方式如下
客戶端發起請求->服務端收到請求並對客戶端發起回調->客戶端回復回調->服務端回復客戶端請求
發布訂閱其實是雙工的變種,它的原理大概是這樣的
客戶端發起請求->服務端調用回調->服務端調用回調.....
基本原理就這些,我們來用代碼說話吧,現在瞅瞅,代碼比說話都親切....
首先我們定義一個基礎的WCF服務契約

/// <summary> /// 推送服務契約 /// /// Tips: /// 契約提供兩個服務,一個是訂閱,一個是退訂。 /// 服務端會向訂閱的客戶端發布消息 /// </summary> [ServiceContract(CallbackContract = typeof(IPushCallback))] public interface IPushService { /// <summary> /// 訂閱服務 /// </summary> [OperationContract(IsOneWay = true)] void Regist(); /// <summary> /// 退訂服務 /// </summary> [OperationContract(IsOneWay = true)] void UnRegist(); }
在這里定義了兩個行為,訂閱和退訂。
同時還有一個回調契約如下

/// <summary> /// 回調接口 IOC思想的體現 /// </summary> public interface IPushCallback { [OperationContract(IsOneWay = true)] void NotifyMessage(string message); }
在這里,我們對每一個行為都標記為OneWay,表示客戶端在調用完服務之后不需要等待服務的回復,同理回調時服務端只管廣播,同樣不需要理會客戶端
廣播的過程,就是服務端調用每一個連接的回調通道進行操作的過程,因此這里需要注意2點。
1、有一個集合,用於維護回調通道。
2、客戶端與服務端建立的通道不能在調用之后就被回收,而是需要保持通道的正常狀態
在這里,我們建立一個ChannelManager類型,其中維護了一個回調通道的列表,並且提供了服務端操作的幾個行為。這個類型以單例模式實現,保證唯一的通道列表。

/// <summary> /// 通道管理 /// </summary> public class ChannelManager { #region Fields /// <summary> /// 回調通道列表 /// </summary> private List<IPushCallback> callbackChannelList = new List<IPushCallback>(); /// <summary> /// 用於互斥鎖的對象 /// </summary> public static readonly object SyncObj = new object(); #endregion #region Single private static readonly Lazy<ChannelManager> instance = new Lazy<ChannelManager>(() => new ChannelManager()); public static ChannelManager Instance { get { return instance.Value; } } protected ChannelManager() { } #endregion #region Methods /// <summary> /// 將回調通道加入到通道列表中進行管理 /// </summary> /// <param name="callbackChannel"></param> public void Add(IPushCallback callbackChannel) { if (callbackChannelList.Contains(callbackChannel)) { Console.WriteLine("已存在重復通道"); } else { lock (SyncObj) { callbackChannelList.Add(callbackChannel); Console.WriteLine("添加了新的通道"); } } } /// <summary> /// 從通道列表中移除對一個通道的管理 /// </summary> /// <param name="callbackChannel"></param> public void Remove(IPushCallback callbackChannel) { if (!callbackChannelList.Contains(callbackChannel)) { Console.WriteLine("不存在待移除通道"); } else { lock (SyncObj) { callbackChannelList.Remove(callbackChannel); Console.WriteLine("移除了一個通道"); } } } /// <summary> /// 廣播消息 /// </summary> /// <param name="message"></param> public void NotifyMessage(string message) { if (callbackChannelList.Count > 0) { //避免對callbackChannelList的更改對廣播造成的影響 IPushCallback[] callbackChannels = callbackChannelList.ToArray(); foreach (var channel in callbackChannels) { try { //廣播消息 channel.NotifyMessage(message); } catch { //對異常的通道進行處理 callbackChannelList.Remove(channel); } } } } #endregion }
下面就是我們服務的實現了,服務的實現很簡單,僅僅是捕獲到客戶端的回調通道,對集合進行操作。這里需要注意的是ServiceBehavior標記的InstanceContextMode屬性的設置。我們需要為每一個單獨的通道創建新的實例,但是在調用玩服務后,不對通道立刻進行回收,因此我們需要設置為InstanceContextModel.Single。

/// <summary> /// 服務的實現 /// Tips: /// 實現發布訂閱,要注意:每個信道在調用后不要回收,否則會在回調時報錯 /// </summary> [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)] public class PushService : IPushService { public void Regist() { IPushCallback callbackChannel = OperationContext.Current.GetCallbackChannel<IPushCallback>(); //添加到管理列表中 ChannelManager.Instance.Add(callbackChannel); } public void UnRegist() { IPushCallback callbackChannel = OperationContext.Current.GetCallbackChannel<IPushCallback>(); //從管理列表中移除 ChannelManager.Instance.Remove(callbackChannel); } }
這樣我們的服務基本就完成了,置於客戶端的調用,需要創建一個雙工通道對象,並且將實現了回調契約的類型,傳給InstanceContext屬性。
這篇文章很簡單,沒有涉及到對產生異常的通道的處理,也沒有考慮到Silverlight調用時遇到的跨域問題,同時不支持HttpGet。
文章的目的只有一個,就是盡可能簡潔的體現發布訂閱服務的原理。
有興趣的讀者,可以對服務進行擴展,對服務在廣播時可能產生的各種異常進行處理。
由於文章的例子是基於TCP/IP的通迅方式,使用的是netTcpBinding的全雙工模式,因此使用HttpGet的時候需要進行一些額外的設置。
並且由於是自托管服務,寄宿在控制台應用程序中,因此跨域文件的提供方式也不太相同。
下面提供完整的代碼:
另外提供一個Silverlight調用服務的例子,涉及HttpGet及跨域文件的提供
這兩個例子都是基於Vs2010 .Net4.0開發,Silverlight版本為5.0