C#自定義Session


相比較微軟傳統的Session有諸多限制,Session設置InProc就會在IIS進程中,收到IIS進程影響,Session之間不能交互。

自定義Session就可以讓我們實現,Session之間的交互,現實點的功能是,消息推送,A要發送消息給B,B要在網頁里顯示有最新的消息,並顯示內容,普通的方式,是用Jq不停的去查,但這樣就有可能給數據庫造成比較大的壓力,但如果把最新消息存於Session中,這樣就比較容易了,因為Session數據是在內存中的,內存讀取速度快,還有就是取得比較方便,對CPU資源損耗也比較小,這樣也能有更高的用戶體驗。

自定義的Session代碼如下:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Threading;

namespace KKCMS.Frame.SessionCommon
{
    /// <summary>
    /// 表示Session創建的工廠
    /// </summary>
    public class SessionFactory
    {
        private const string COOKIE_CODE_FORMAT = "id={0}&timestap={1}";

        private const string IP_USER_AGENT = "ip={0}&userAgent={1}&cookieCode={2}";

        private const string X_TWO = "x2";
        class QueueCookie
        {
            public bool IsOpen { get; set; }

            public bool StackCheck { get; set; }

            public DateTime LastExpTime { get; set; }

            public QueueCookie()
            {
            }
        }

        /// <summary>
        /// 表示Session建立引發的事件
        /// </summary>
        public event EventHandler Session_Start;

        /// <summary>
        /// 表示Session結束引發的事件
        /// </summary>
        public event EventHandler Session_End;

        private long id = 0;

        private bool close = false;

        private QueueCookie idCookie = new QueueCookie();

        private QueueCookie qCookie = new QueueCookie();

        private ConcurrentDictionary<string, Session> sessionDic = new ConcurrentDictionary<string, Session>();

        private Queue<Session> expSession = new Queue<Session>();

        private ConcurrentDictionary<long, ConcurrentQueue<string>> expKeyDic = new ConcurrentDictionary<long, ConcurrentQueue<string>>();

        private Thread idThread = null;

        private Thread holdThead = null;

        public SessionFactory()
        {
            #region 維持Id計數的線程
            idThread = new Thread(IdCheckRun);
            idThread.IsBackground = true;
            idThread.SetApartmentState(ApartmentState.MTA);
            idThread.Start();
            #endregion
            #region 管理session的線程
            holdThead = new Thread(KeepSession);
            holdThead.IsBackground = true;
            holdThead.SetApartmentState(ApartmentState.MTA);
            holdThead.Start();
            #endregion
        }
        ~SessionFactory()
        {
            try
            {
                //線程釋放
                close = true;
                idThread.Interrupt();
                holdThead.Interrupt();
            }
            catch (Exception)
            {

            }
        }

        [MTAThread()]
        private void IdCheckRun()
        {
            //id值每隔三秒重置一次,相當於Session里的LICD
            int sleeptime = 1000 * 3600 * 15;
            while (!close)
            {
                Thread.Sleep(sleeptime);
                lock (idCookie)
                {
                    idCookie.IsOpen = true;
                    id = 0;//重置id
                    idCookie.IsOpen = false;
                }
            }
        }

        /// <summary>
        /// SHA512加密
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        private string ConvertToSHA512(string code)
        {
            SHA512 sha = new SHA512CryptoServiceProvider();
            byte[] data = sha.ComputeHash(Encoding.UTF8.GetBytes(code));
            StringBuilder sb = new StringBuilder();
            foreach (byte b in data)
            {
                sb.Append(b.ToString(X_TWO));
            }
            return sb.ToString();
        }

        /// <summary>
        /// SHA256加密
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        private string ConvertToSHA256(string code)
        {
            SHA256 sha2 = new SHA256CryptoServiceProvider();
            byte[] data = sha2.ComputeHash(Encoding.UTF8.GetBytes(code));
            StringBuilder sb = new StringBuilder();
            foreach (byte b in data)
            {
                sb.Append(b.ToString(X_TWO));
            }
            return sb.ToString();
        }

       
        /// <summary>
        /// 管理Session的方法執行(線程)
        /// </summary>
        [MTAThread()]
        private void KeepSession()
        {
            while (!close)
            {
                //每隔一秒查一次
                Thread.Sleep(1000);
                lock(qCookie)
                {
                    if (qCookie.StackCheck && DateTime.Now.CompareTo(qCookie.LastExpTime) == 1)
                    {
                        //先判斷是否要執行Session清理
                        DateTime currentTime = qCookie.LastExpTime;
                        bool isout= false;
                        while (expSession.Count > 0&&!isout)
                        {
                            Session session = expSession.Dequeue();
                            sessionDic.TryRemove(session.Id, out session);
                            if (Session_End != null)
                            {
                                Session_End(session, new EventArgs());
                            }
                            if (expSession.Count!=0&&
                                DateTime.Now.CompareTo(expSession.Peek().ExpTime)<1)
                            {
                                //判斷此時隊列頭上的Session是否過期
                                isout = true;
                            }
                        }
                        if (expSession.Count != 0)
                        {
                            //更新下一個Session的過期時間
                            qCookie.LastExpTime = expSession.Peek().ExpTime;
                        }
                    }
                }
            }
        }



        /// <summary>
        /// 嘗試通過傳入的cookieCode,ip,UserAgent來獲取session
        /// </summary>
        /// <param name="cookieCode">cookieCode</param>
        /// <param name="ipAddress">ip</param>
        /// <param name="userAgent">UserAgent</param>
        /// <param name="session">返回session</param>
        /// <returns></returns>
        public bool TryGetSession(string cookieCode, string ipAddress, string userAgent, out Session session)
        {
            //cookieCode是一部分
            //另一部分需要結合ipAdress,userAgent,cookieCode用SHA512加密得到
            string userCode = ConvertToSHA512(string.Format(IP_USER_AGENT, ipAddress, userAgent, cookieCode)).ToUpper();
            string sessionId = cookieCode + userCode;
            if (sessionDic.TryGetValue(sessionId, out session))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 創建session
        /// </summary>
        /// <param name="ipAddress"></param>
        /// <param name="timestap"></param>
        /// <param name="expTime"></param>
        /// <param name="userAgent"></param>
        /// <param name="idCookies"></param>
        /// <returns></returns>
        public Session CreateSession(string ipAddress, DateTime timestap, int expTime, string userAgent, out string idCookies)
        {
            long time = (long)(new TimeSpan(timestap.Ticks).TotalMilliseconds);
            long mId = 0;
            //id加1
            lock (idCookie)
            {
                idCookie.IsOpen = true;
                id++;
                idCookie.IsOpen = false;
                mId = id;
            }
            //Session過期時間
            DateTime dexpTime = timestap.AddMilliseconds(expTime);
            #region 采用不對稱驗證方式
            //idCookies是存入瀏覽器的,驗證時,將cookie里的值和IP加UserAgent值連接,才能找到對應的SessionId
            //這樣可以防止偽造SessionId,因為傳入的IP雖可以偽造但確定不了哪個IP是與cookie里的值對應的,UserAgent也是一樣
            //另一方面采用這種方式,可以使得idCookies+userCode產生的結果重復性非常小
            idCookies = ConvertToSHA256(string.Format(COOKIE_CODE_FORMAT, mId, time)).ToUpper();
            //idCookies += time.ToString(X_TWO).ToUpper();
            string userCode = ConvertToSHA512(string.Format(IP_USER_AGENT, ipAddress, userAgent, idCookies)).ToUpper();
            #endregion
            string sessionId = idCookies + userCode;
            Session session = new Session(sessionId, dexpTime, this);
            if (!sessionDic.TryAdd(sessionId, session))
            {
                throw new Exception("創建會話時出現異常!");
            }
            else
            {
                //觸發SessionStart事件
                if (Session_Start != null)
                {
                    Session_Start(session, new EventArgs());
                }
                lock(qCookie)
                {
                    //這是用來清理Session的,StackCheck是一個標志位,表示初始Session放入
                    qCookie.StackCheck = true;
                    qCookie.LastExpTime = session.ExpTime;
                    expSession.Enqueue(session);
                }
            }
            return session;
        }

        /// <summary>
        /// 結束當前session
        /// </summary>
        /// <param name="sessionId"></param>
        public void ClearSession(string sessionId)
        {
            Session session = null;
            sessionDic.TryRemove(sessionId, out session);
        }


    }

    /// <summary>
    /// 表示一個當前會話
    /// </summary>
    public sealed class Session : Dictionary<string, object>
    {
        /// <summary>
        /// 獲取SessionId
        /// </summary>
        public string Id { get; private set; }

        /// <summary>
        /// 獲取Session過期時間
        /// </summary>
        public DateTime ExpTime { get; private set; }

        private SessionFactory BaseFactory { get; set; }

        public Session(string sessionId, DateTime expTime, SessionFactory factory)
        {
            Id = sessionId;
            ExpTime = expTime;
            BaseFactory = factory;
        }

        /// <summary>
        /// 取消當前會話
        /// </summary>
        public void Cancel()
        {
            BaseFactory.ClearSession(Id);
        }
    }
}
View Code

使用之前,可以配上一個過濾器,我這里使用的就是MVC過濾器

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.IO;

namespace KKCMS
{
    public class CommonFilter:ActionFilterAttribute
    {
        private static readonly KKCMS.Frame.SessionCommon.SessionFactory Factory=new KKCMS.Frame.SessionCommon.SessionFactory();
        private const string REMOTE_ADDRESS = "REMOTE_ADDR";
        private const string SESSION_ID_COOKIE_NAME = "MY_SESSIONID";

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var context = filterContext.HttpContext;
            var request = context.Request;
            var response = context.Response;
            string ip = request.ServerVariables[REMOTE_ADDRESS];
            int expTime = 7200000;//過期時間為兩個小時
            DateTime timestap = context.Timestamp;
            string userAgent = request.UserAgent;
            KKCMS.Frame.SessionCommon.Session resultSession = null;//表示獲取的Session
            string cookieCode = request.Cookies?[SESSION_ID_COOKIE_NAME]?.Value;//?是空傳播符,C#6.0才有的,非6,0可以加上非空判斷
            if (cookieCode == null || !Factory.TryGetSession(cookieCode, ip, userAgent, out resultSession))
            {
                resultSession =Factory.CreateSession(ip, timestap, expTime, userAgent, out cookieCode);
                response.SetCookie(new HttpCookie(SESSION_ID_COOKIE_NAME, cookieCode));//設置SessionId
            }
            base.OnActionExecuting(filterContext);
        }

    }
}

  


免責聲明!

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



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