Asp.NET MVC 之心跳/长连接
0x01 在线用户类,我的用户唯一性由ID和类型识别(因为在不同的表里)
public class UserIdentity : IEqualityComparer<UserIdentity> { internal UserIdentity() { } public UserIdentity(int userId, int userType) { this.UserID = userId; this.UserType = userType; } public int UserID { get; private set; } public int UserType { get; private set; } public bool Equals(UserIdentity x, UserIdentity y) { return x.UserID == y.UserID && x.UserType == y.UserType; } public int GetHashCode(UserIdentity obj) { return obj.UserID * (obj.UserType + 10); } } public class AliveUser { private AliveUser() { } public AliveUser(string sessionID, int userID, int userType) { this.SessionID = sessionID; this.StartAt = DateTime.Now; this.User = new UserIdentity(userID, userType); } public void SetExpires(DateTime expires) { this.Expires = expires; } public string SessionID { get; private set; } public DateTime StartAt { get; private set; } public DateTime Expires { get; private set; } public UserIdentity User { get; private set; } }
0x02 会话管理,线程+字典
public class AliveSessionHelper { static AliveSessionHelper() { thread.IsBackground = true; thread.Start(); } public static int TimeoutInMinutes = 1; private static object syncObject = new object(); public static Dictionary<string, AliveUser> OnlineUsers = new Dictionary<string, AliveUser>(); public static Dictionary<UserIdentity, List<AliveUser>> GetOnlineUsers() { lock (syncObject) { var list = OnlineUsers.Values.ToList(); var grouping = list.GroupBy(x => x.User,new UserIdentity()); var dict = new Dictionary<UserIdentity, List<AliveUser>>(grouping.Count()); foreach (var item in grouping) { dict.Add(item.Key, item.ToList()); } return dict; } } public static void AddAliveUser(string sessionID, int userID, int userType) { AddAliveUser(new AliveUser(sessionID, userID, userType)); } public static void AddAliveUser(AliveUser user) { lock (syncObject) { var aliveUser = OnlineUsers.ContainsKey(user.SessionID) ? OnlineUsers[user.SessionID] : null; if (aliveUser == null) { OnlineUsers.Add(user.SessionID, user); } var expires = DateTime.Now.AddMinutes(TimeoutInMinutes); OnlineUsers[user.SessionID].SetExpires(expires); } } private static Thread thread = new Thread(ThreadStartBody); private static void ThreadStartBody() { while (true) { lock (syncObject) { var delList = new List<Guid>(); var list = OnlineUsers.Values.ToList(); foreach (var item in list) { if (item.Expires.Subtract(DateTime.Now).TotalSeconds < 5) { OnlineUsers.Remove(item.SessionID); } } } Thread.Sleep(29); } } }
0x03 控制器实现
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)] [Authorize] public class ApiController : AsyncController { public async Task<ActionResult> KeepAlive() { if (!User.Identity.IsAuthenticated) { return null; } AliveSessionHelper.AddAliveUser(Session.SessionID, AdminUserService.Instance.CurrentUserID, 0); return await Task.Delay(TimeSpan.FromSeconds(30)).ContinueWith(task => { return JavaScript("$.getScript('/admin/api/keepalive');"); }); } }
注意
1、[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]是必须的,否则刷新(重复调用)会锁死,原理熟手自己领会,新手照做即可。
2、/admin/api/keepalive 调用的Action是自身。
3、服务端控制请求的释放间隔。这里设置的30s。如果要更长时间,如超过1分钟可能导致Action超时异常,需要加AsyncTimeoutAttribute特性设置Action超时时间大于释放间隔。
0x04 前端代码,需要jQuery。就是触发首次调用,然后就靠服务端返回js自动执行。
$(function () { //KeepAlive $.getScript('/admin/api/keepalive'); }
是不是感觉代码很简单?注重简单实用好用是博主一贯的追求。
拷贝请注明来源博客园,道木先生。.Net软件交流群283590657