WCF實現長連接


     由於WCF的機制,連接池會在連接建立一定時間后超時,即使設置了超時時間非常長,也可能被服務端系統主動回收。之前做項目時碰到了這個問題,所以項目上考慮采用長連接,自動管理連接池,當連接超時后,自動重建,保持會話,這樣在業務層就不需要再去處理連接超時的問題。具體的思路是,在程序啟動時,先將需要使用長連接的連接放到長連接容器中,並設置連接的最大數量,在使用時,輪詢使用連接,當使用時捕獲到異常時,自動切換到下一個連接,並重建上一個連接。代碼如下:

    AliveConnection類功能是保持連接,具體執行WCF調用,在檢測到連接不可用時進行重建。

 

class AliveConnection<T> where T : System.ServiceModel.ICommunicationObject, new()
    {
        private string _endpointConfigName = string.Empty;
        private T _instance = default(T);
        /// <summary>
        /// 正在執行其他過程時,設置為正忙。執行完成后閑置
        /// 連接出錯后,正在重新連接創建時設置為正忙,解除正忙狀態有倆種情況:
        /// 1.第一次重建連接成功后;
        /// 2.在線程中重試成功后;
        /// </summary>
        public bool IsBusy { get; set; }

        internal AliveConnection(string endpointConfigName)
        {
            if (string.IsNullOrEmpty(endpointConfigName.Trim())) throw new ArgumentException("終結點不能配置為空。");
            _endpointConfigName = endpointConfigName;
            //_instance = CreateConnection();
        }

        internal bool Execute(Action<T> expression)
        {
            try
            {
                Open();
                expression(_instance);
                return true;
            }
            catch (System.ServiceModel.CommunicationException e)
            {
                return false;
            }
        }

        internal bool Execute<TResult>(Func<T, TResult> expression,out TResult result)
        {
            result = default(TResult);
            try
            {
                Open();
                result = expression(_instance);
                return true;
            }
            catch (System.ServiceModel.CommunicationException e)
            {
                return false;
            }
        }

        private void Open()
        {
            if (_instance == null)//使用時才創建
            {
                _instance = CreateConnection();
                _instance.Faulted += Faulted;
            }
            if (_instance.State != System.ServiceModel.CommunicationState.Opened)
                _instance.Open();
        }

        private void Faulted(object sender, EventArgs e)
        {
            lock (_instance)
            {
                IsBusy = true;
                //失敗后鎖住並重新建立連接
                _instance = CreateConnection();
            }
        }

        private T CreateConnection()
        {
            try
            {
                var instance = (T)Activator.CreateInstance(typeof(T), _endpointConfigName);
                IsBusy = false;
                return instance;
            }
            catch (Exception e)
            {
                IsBusy = true;
                RetryWhenFailed();
                throw new Exception("創建連接失敗,請檢測終結點配置和服務。", e);
            }
        }
        //創建一個線程來不間斷重試創建連接
        private void RetryWhenFailed()
        {
            int retryTimes = 0;
            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    //如果拋出異常,表示創建失敗,繼續重試,如果睡眠時間大於60秒,則睡眠時間不再增加
                    try
                    {
                        retryTimes++;
                        _instance = (T)Activator.CreateInstance(typeof(T), _endpointConfigName);
                        IsBusy = false;
                        break;
                    }
                    catch
                    {
                        int sleepMillionSeconds = retryTimes * 5 * 1000;
                        sleepMillionSeconds = sleepMillionSeconds > 60 * 1000 ? 60 * 1000 : sleepMillionSeconds;
                        System.Threading.Thread.Sleep(sleepMillionSeconds);
                        continue;
                    }
                }
            });
        }
    }

另外我們需要一個類,來做連接的初始化,並做輪詢,並且暴露執行的函數。

 /// <summary>
    /// WCF長連接容器
    /// </summary>
    /// <typeparam name="T">待創建的WCF服務類型</typeparam>
    public class AliveConnectionContainer<T>
        where T : System.ServiceModel.ICommunicationObject, new()
    {
        #region fields
        private List<AliveConnection<T>> _connections = null;//所有連接
        private int _currentConnectionIndex = 0;//當前使用的連接的索引
        #endregion

        #region Octor
        /// <summary>
        /// 通過終結點配置名稱,創建長連接。如果終結點數不等於連接數,則輪詢跟節點配置列表,最終創建達到連接數的連接。
        /// </summary>
        /// <param name="maxConnection">需要創建的長連接數</param>
        /// <param name="endpointConfigNames">所有的終結點配置名稱,對應配置節點里bind的name</param>
        /// <exception>如果終結點配置為空,則拋出異常,如果創建失敗,也會拋出異常</exception>
        public AliveConnectionContainer(int maxConnection, params string[] endpointConfigNames)
        {
            _connections = new List<AliveConnection<T>>(maxConnection);

            int tmpIndex = 0;
            for (int index = 0; index < maxConnection; index++)
            {
                if (tmpIndex >= endpointConfigNames.Count()) tmpIndex = 0;
                _connections.Add(new AliveConnection<T>(endpointConfigNames[tmpIndex]));
            }
        }
        #endregion

        #region public method
        /// <summary>
        /// 執行服務調用,會一直輪詢執行直到執行成功
        /// </summary>
        /// <param name="expression">需要調用的處理方法</param>
        public void Execute(Action<T> expression)
        {
            Func<bool> executeExpression = () => Instance.Execute(expression);
            Execute(executeExpression);
        }    
        /// <summary>
        /// 執行服務調用,會一直輪詢執行直到執行成功
        /// </summary>
        /// <param name="expression">需要調用的處理方法</param>
        public TResult Execute<TResult>(Func<T, TResult> expression)
        {
            TResult result = default(TResult);
            Func<bool> executeExpression = () => Instance.Execute(expression,out result);
            Execute(executeExpression);
            return result;
        }

        private void Execute(Func<bool> expression)
        {
            bool success = false;
            int failedCount = 0;
            try
            {
                while (true)
                {
                    success = expression();
                    if (!success) failedCount++;
                    else break;

                    if (failedCount >= _connections.Count) throw new Exception("沒有可用的服務,請檢測服務運行狀態。");
                }
            }
            catch (Exception e)
            {
                throw new Exception("執行WCF服務調用失敗。", e);
            }
        }

        #endregion

        #region private method
        private AliveConnection<T> Instance
        {
            get
            {
                if (_connections == null || _connections.Count == 0) throw new Exception("沒有可用的連接,請先設置連接。");
                AliveConnection<T> result;
                while (!(result = GetInstance()).IsBusy) break;//輪詢直到找到空閑的連接
                return result;
            }
        }

        private AliveConnection<T> GetInstance()
        {
            if (_currentConnectionIndex >= _connections.Count) _currentConnectionIndex = 0;
            return _connections[_currentConnectionIndex++];
        }
        #endregion
    }

使用靜態類,做全局的WCF連接注冊和檢索使用

/// <summary>
    /// 長連接服務的管理類
    /// </summary>
    /// <example>
    /// AliveConnectionManager.Register<Service>(endpoints,10);
    /// var client = AliveConnectionManager.Resolve<Service>();
    /// List<Movie> movies;
    /// client.Execute(service => movies = service.GetMovies());
    /// </example>
    public static class AliveConnectionManager
    {
        private static Dictionary<Type, object> _container = new Dictionary<Type, object>();
        /// <summary>
        /// 根據輸入的終結點列表,在app.config文件中查找對應的終結點,並建立連接
        /// </summary>
        /// <typeparam name="T">要注冊的WCF的服務類型</typeparam>
        /// <param name="maxConnection">連接數</param>
        /// <param name="endpointConfigNames">配置的終結點列表</param>
        public static void Register<T>(int maxConnection, params string[] endpointConfigNames)
            where T : System.ServiceModel.ICommunicationObject, new()
        {
            if (_container.ContainsKey(typeof(T))) throw new ArgumentException(string.Format("容器中已經添加過{0}的長連接服務。", typeof(T)));
            _container.Add(typeof(T), new AliveConnectionContainer<T>(maxConnection, endpointConfigNames));
        }

        /// <summary>
        /// 獲取類型為T的長連接服務
        /// </summary>
        /// <typeparam name="T">待獲取的WCF的服務類型</typeparam>
        /// <returns>對應類型為T的服務</returns>
        public static AliveConnectionContainer<T> Resolve<T>() where T : System.ServiceModel.ICommunicationObject, new()
        {
            Type type = typeof(T);
            if (!_container.ContainsKey(type)) throw new ArgumentException(string.Format("沒有找到類型為{0}的長連接服務。", type));
            return _container[type] as AliveConnectionContainer<T>;
        }
    }

服務注冊代碼

 AliveConnectionManager.Register<Service>(endpoints,10);

調用服務

var client = AliveConnectionManager.Resolve<Service>();
 List<Movie> movies;
client.Execute(service => movies = service.GetMovies());

Service即我們在客戶端引入的WCF服務


免責聲明!

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



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