You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

问题分析与解决方案

核心原因

  1. 集合遍历与修改的并发冲突:GetOnlineDevices遍历SessionRegistry.GetKeys()返回的键集合时,若其他线程(如WebSocket连接关闭线程)从_sessions字典中移除了某个Guid,会导致遍历到该Guid时调用GetSession找不到对应项,触发异常。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 07:05:56