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

ASP.NET Core 2.0 WebSocket问题:Socket连接总是已释放

解决ASP.NET Core 2.0中WebSocket连接移交单例服务的ObjectDisposedException问题

这个问题我之前在做ASP.NET Core WebSocket项目时踩过坑,核心问题出在WebSocket实例的生命周期绑定到了中间件的HttpContext上——当中间件走完请求流程后,ASP.NET Core会自动释放HttpContext,连带把里面的WebSocket对象也Dispose掉,这时候你的单例服务再去访问就会抛出ObjectDisposedException。下面是我验证过的解决方案,一步步来:

1. 实现线程安全的WebSocket连接管理单例服务

首先需要一个单例服务来统一管理WebSocket连接,确保连接的添加、移除和消息发送都是线程安全的:

public interface IWebSocketConnectionManager
{
    void AddConnection(string connectionId, WebSocket webSocket);
    void RemoveConnection(string connectionId);
    Task SendMessageAsync(string connectionId, string message);
}

public class WebSocketConnectionManager : IWebSocketConnectionManager
{
    // 用ConcurrentDictionary保证多线程下的安全操作
    private readonly ConcurrentDictionary<string, WebSocket> _activeConnections = new ConcurrentDictionary<string, WebSocket>();

    public void AddConnection(string connectionId, WebSocket webSocket)
    {
        _activeConnections.TryAdd(connectionId, webSocket);
    }

    public void RemoveConnection(string connectionId)
    {
        if (_activeConnections.TryRemove(connectionId, out var webSocket))
        {
            // 只有当连接被移除时才手动释放,避免提前Dispose
            webSocket.Dispose();
        }
    }

    public async Task SendMessageAsync(string connectionId, string message)
    {
        if (!_activeConnections.TryGetValue(connectionId, out var webSocket))
        {
            return;
        }

        // 先检查连接状态,避免操作已关闭的连接
        if (webSocket.State != WebSocketState.Open)
        {
            RemoveConnection(connectionId);
            return;
        }

        var messageBuffer = Encoding.UTF8.GetBytes(message);
        var segment = new ArraySegment<byte>(messageBuffer);
        await webSocket.SendAsync(segment, WebSocketMessageType.Text, endOfMessage: true, CancellationToken.None);
    }
}

然后在Startup.cs的ConfigureServices中注册这个单例:

services.AddSingleton<IWebSocketConnectionManager, WebSocketConnectionManager>();

2. 修改WebSocket中间件,接管连接生命周期

中间件的关键是不要让HttpContext自动释放WebSocket,我们需要启动一个后台任务来监听连接状态,保持WebSocket实例的引用存活:

public class WebSocketMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IWebSocketConnectionManager _connectionManager;

    public WebSocketMiddleware(RequestDelegate next, IWebSocketConnectionManager connectionManager)
    {
        _next = next;
        _connectionManager = connectionManager;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (!context.WebSockets.IsWebSocketRequest)
        {
            // 非WebSocket请求,交给下一个中间件处理
            await _next(context);
            return;
        }

        var webSocket = await context.WebSockets.AcceptWebSocketAsync();
        var connectionId = Guid.NewGuid().ToString();

        try
        {
            _connectionManager.AddConnection(connectionId, webSocket);
            
            // 启动后台任务监听连接状态,即使不需要读取消息也要做
            // 因为WebSocket的连接状态需要通过ReceiveAsync来更新,否则会误判
            await MonitorConnectionStatus(webSocket, connectionId);
        }
        catch (Exception ex)
        {
            // 异常时清理连接
            _connectionManager.RemoveConnection(connectionId);
            // 这里可以根据需要记录日志
        }
    }

    private async Task MonitorConnectionStatus(WebSocket webSocket, string connectionId)
    {
        var buffer = new byte[1024 * 4];
        try
        {
            while (webSocket.State == WebSocketState.Open)
            {
                // 即使不处理收到的消息,也要调用ReceiveAsync检测连接是否关闭
                var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                
                if (result.MessageType == WebSocketMessageType.Close)
                {
                    await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Connection closed by server", CancellationToken.None);
                    break;
                }
            }
        }
        finally
        {
            // 连接关闭后清理资源
            _connectionManager.RemoveConnection(connectionId);
        }
    }
}

在Startup.cs的Configure中注册中间件:

app.UseMiddleware<WebSocketMiddleware>();

3. 在API控制器中注入单例服务发送消息

现在你可以在任何控制器中注入IWebSocketConnectionManager,直接调用发送方法:

[ApiController]
[Route("api/websocket")]
public class WebSocketApiController : ControllerBase
{
    private readonly IWebSocketConnectionManager _connectionManager;

    public WebSocketApiController(IWebSocketConnectionManager connectionManager)
    {
        _connectionManager = connectionManager;
    }

    [HttpPost("send/{connectionId}")]
    public async Task<IActionResult> SendMessageToClient(string connectionId, [FromBody] string message)
    {
        await _connectionManager.SendMessageAsync(connectionId, message);
        return Ok(new { Message = "Message sent successfully" });
    }
}

关键注意事项

  • 必须监听连接状态:即使你不需要处理客户端发送的消息,也要调用ReceiveAsync,否则WebSocket的状态不会更新,你无法知道连接是否已关闭,而且HttpContext可能会提前释放WebSocket实例。
  • 线程安全优先:用ConcurrentDictionary管理连接,避免多线程操作时出现异常。
  • 手动管理Dispose:只有当连接确认关闭时,才从管理器中移除并释放WebSocket,不要依赖HttpContext的自动释放。

内容的提问来源于stack exchange,提问作者vaindil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:06:11