相比较微软传统的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}×tap={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); } } }
使用之前,可以配上一个过滤器,我这里使用的就是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); } } }