ASP.NET正常的WebSocket生物识别设备连接在MVC中报错:无效会话ID
问题描述
我们通过WebSocket连接生物识别设备的功能在ASP.NET环境中正常运行,但将同一类库迁移到MVC项目时,出现**"Invalid session ID. The session may have been already closed"**错误。
DeviceLoginManager 类(报错位置)
public static OnlineDevice[] GetOnlineDevices() //error { List<OnlineDevice> online = new List<OnlineDevice>(); foreach(Guid guid in SessionRegistry.GetKeys()) { if (SessionRegistry.GetSession(guid).loggedIn) { online.Add(new OnlineDevice { session = SessionRegistry.GetSession(guid) }); } const int KeepAliveTimeout = 10 * 60 * 1000; SBWebSocketHandler aSession = SessionRegistry.GetSession(guid); if (aSession.keepAliveMsgSupported && aSession.keepAliveRecvedTime < Environment.TickCount - KeepAliveTimeout) { aSession.Close(); } } return online.ToArray(); }
SessionRegistry 类
internal static object _monitor = new object(); internal static Dictionary<Guid, SBWebSocketHandler> _sessions = new Dictionary<Guid, SBWebSocketHandler>(); private static RandomNumberGenerator _random = RandomNumberGenerator.Create(); /// <summary> /// Retrieves an open session from its ID. /// </summary> /// <param name="guid">Session ID</param> /// <returns>A session object if matching ID was found; otherwise null</returns> public static SBWebSocketHandler GetSession(Guid guid) { lock (_monitor) { SBWebSocketHandler result; if (_sessions.TryGetValue(guid, out result)) return result; else { // return null; throw new WebDeviceException("Invalid session ID. The session may have been already closed."); } } } internal static Guid AddSession(SBWebSocketHandler handler) { lock (_monitor) { byte[] guidBytes = new byte[16]; Guid guid; do { _random.GetBytes(guidBytes); guid = new Guid(guidBytes); } while (_sessions.ContainsKey(guid)); _sessions.Add(guid, handler); return guid; } } public static Dictionary<Guid, SBWebSocketHandler>.KeyCollection GetKeys() { lock (_monitor) { return _sessions.Keys; } }
SBWebSocketHandler 类
private IDeviceLoginManager _loginManager; public Guid SessionId { get { return m_guid; } } public override void OnOpen() { _loginManager = (IDeviceLoginManager) Type.GetType(ConfigurationManager.AppSettings["smackbio.websocketsdk.deviceLoginManager"]) .GetConstructor(new Type[0]) .Invoke(new object[0]); m_guid = SessionRegistry.AddSession(this); registerMsgRecved = false; loggedIn = false; keepAliveMsgSupported = false; }
问题分析与解决方案
核心原因
- 集合遍历与修改的并发冲突:
GetOnlineDevices遍历SessionRegistry.GetKeys()返回的键集合时,若其他线程(如WebSocket连接关闭线程)从_sessions字典中移除了某个Guid,会导致遍历到该Guid时调用GetSession找不到对应项,触发异常。 - MVC与ASP.NET宿主差异:MVC的请求处理模型线程调度更频繁,Session生命周期管理逻辑与原ASP.NET环境不同,放大了并发问题。
修复方案
方案1:遍历前复制键集合
先将键集合复制到独立数组,避免遍历过程中原集合被修改:
public static OnlineDevice[] GetOnlineDevices() { List<OnlineDevice> online = new List<OnlineDevice>(); Guid[] sessionGuids; // 加锁复制键集合,避免遍历中集合变更 lock (SessionRegistry._monitor) { sessionGuids = SessionRegistry.GetKeys().ToArray(); } foreach(Guid guid in sessionGuids) { SBWebSocketHandler session = null; // 单次获取Session,减少锁竞争 lock (SessionRegistry._monitor) { SessionRegistry._sessions.TryGetValue(guid, out session); } if (session == null) continue; if (session.loggedIn) { online.Add(new OnlineDevice { session = session }); } const int KeepAliveTimeout = 10 * 60 * 1000; if (session.keepAliveMsgSupported && session.keepAliveRecvedTime < Environment.TickCount - KeepAliveTimeout) { session.Close(); } } return online.ToArray(); }
方案2:修改GetSession返回null而非抛出异常
将SessionRegistry.GetSession的异常逻辑改回返回null,遇到已关闭Session时直接跳过:
public static SBWebSocketHandler GetSession(Guid guid) { lock (_monitor) { SBWebSocketHandler result; if (_sessions.TryGetValue(guid, out result)) return result; else { return null; // 替换抛出异常逻辑 // throw new WebDeviceException("Invalid session ID. The session may have been already closed."); } } }
同时在GetOnlineDevices中增加null判断:
foreach(Guid guid in SessionRegistry.GetKeys()) { var session = SessionRegistry.GetSession(guid); if (session == null) continue; if (session.loggedIn) { online.Add(new OnlineDevice { session = session }); } const int KeepAliveTimeout = 10 * 60 * 1000; if (session.keepAliveMsgSupported && session.keepAliveRecvedTime < Environment.TickCount - KeepAliveTimeout) { session.Close(); } }
方案3:补充Session关闭时的移除逻辑
检查SBWebSocketHandler的OnClose方法,确保Session关闭时从注册表中移除:
public override void OnClose() { lock (SessionRegistry._monitor) { SessionRegistry._sessions.Remove(m_guid); } // 其他关闭逻辑... }
内容的提问来源于stack exchange,提问作者Time Attendance System
相关产品推荐
相关产品推荐

