使用WCF雙工通訊實現發布訂閱


發布——訂閱

 

通俗一點的解釋,就是推送。由服務端發布消息,所有訂閱了這條消息的客戶端,都會收到服務端廣播的這條消息。

 

WCF的雙工機制的運行方式如下

 

客戶端發起請求->服務端收到請求並對客戶端發起回調->客戶端回復回調->服務端回復客戶端請求

 

發布訂閱其實是雙工的變種,它的原理大概是這樣的

 

客戶端發起請求->服務端調用回調->服務端調用回調.....

 

基本原理就這些,我們來用代碼說話吧,現在瞅瞅,代碼比說話都親切....

 

首先我們定義一個基礎的WCF服務契約

 

IPushService
/// <summary>
    /// 推送服務契約
    /// 
    /// Tips:
    /// 契約提供兩個服務,一個是訂閱,一個是退訂。
    /// 服務端會向訂閱的客戶端發布消息
    /// </summary>
    [ServiceContract(CallbackContract = typeof(IPushCallback))]
    public interface IPushService
    {
        /// <summary>
        /// 訂閱服務
        /// </summary>
        [OperationContract(IsOneWay = true)]
        void Regist();

        /// <summary>
        /// 退訂服務
        /// </summary>
        [OperationContract(IsOneWay = true)]
        void UnRegist();
    }

 

在這里定義了兩個行為,訂閱和退訂。

 

同時還有一個回調契約如下

IPushCallback
 /// <summary>
    /// 回調接口 IOC思想的體現
    /// </summary>
    public interface IPushCallback
    {
        [OperationContract(IsOneWay = true)]
        void NotifyMessage(string message);
    }

 

在這里,我們對每一個行為都標記為OneWay,表示客戶端在調用完服務之后不需要等待服務的回復,同理回調時服務端只管廣播,同樣不需要理會客戶端

 

廣播的過程,就是服務端調用每一個連接的回調通道進行操作的過程,因此這里需要注意2點。

1、有一個集合,用於維護回調通道。

2、客戶端與服務端建立的通道不能在調用之后就被回收,而是需要保持通道的正常狀態

 

在這里,我們建立一個ChannelManager類型,其中維護了一個回調通道的列表,並且提供了服務端操作的幾個行為。這個類型以單例模式實現,保證唯一的通道列表。

 

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。

 

PushService
 /// <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的時候需要進行一些額外的設置。

並且由於是自托管服務,寄宿在控制台應用程序中,因此跨域文件的提供方式也不太相同。

 

下面提供完整的代碼:

SimplePush.rar

 

另外提供一個Silverlight調用服務的例子,涉及HttpGet及跨域文件的提供

Chat.rar

 

這兩個例子都是基於Vs2010 .Net4.0開發,Silverlight版本為5.0


免責聲明!

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



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