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
相关产品推荐
相关产品推荐

