ASP.NET C# WebSocket发送关闭消息触发SocketException及持续通信问题
问题
问题1
为什么向WebSocket发送关闭消息时会崩溃并抛出异常System.Net.Sockets.SocketException: 'The I/O operation has been aborted because of either a thread exit or an application request.'?
问题2
如何实现WebSocket在接收客户端消息后持续发送消息,同时在其他任务/线程中监听关闭消息?
已尝试方案
- 使用
CloseOutputAsync、CloseAsync方法,以及单独任务监听关闭操作,均触发相同异常。
更新后的代码
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } #if NoOptions // <snippet_UseWebSockets> app.UseWebSockets(); // </snippet_UseWebSockets> #endif #if UseOptions // <snippet_UseWebSocketsOptions> var webSocketOptions = new WebSocketOptions() { KeepAliveInterval = TimeSpan.FromSeconds(120), }; app.UseWebSockets(webSocketOptions); // </snippet_UseWebSocketsOptions> #endif #if UseOptionsAO // <snippet_UseWebSocketsOptionsAO> var webSocketOptions = new WebSocketOptions() { KeepAliveInterval = TimeSpan.FromSeconds(120), }; webSocketOptions.AllowedOrigins.Add("https://client.com"); webSocketOptions.AllowedOrigins.Add("https://www.client.com"); app.UseWebSockets(webSocketOptions); // </snippet_UseWebSocketsOptionsAO> #endif ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13; redis = ConnectionMultiplexer.Connect(Configuration["RedisConnect"]); db = redis.GetDatabase(); endPoint = redis.GetEndPoints(); server = redis.GetServer(endPoint[0], Configuration["RedisConnect"]); // <snippet_AcceptWebSocket> app.Use(async (context, next) => { if (context.Request.Path == "/ws") { if (context.WebSockets.IsWebSocketRequest) { using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync()) { await Echo(context, webSocket); } } else { context.Response.StatusCode = (int)HttpStatusCode.BadRequest; } } else { await next(); } app.UseFileServer(); }); private async Task Echo(HttpContext context,WebSocket webSocket) { var buffer=new byte[1024*4]; var result=await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), System.Threading.CancellationToken.None); if (result != null) { while (!result.CloseStatus.HasValue) { string msg=Encoding.UTF8.GetString(new ArraySegment<byte> (buffer,0,result.Count)); await webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("servrSays")),result.MessageType,result.EndOfMessage,System.Threading.CancellationToken.None); result=await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), System.Threading.CancellationToken.None); } } await webSocket.CloseAsync(result.CloseStatus.Value,result.CloseStatusDescription,System.Threading.CancellationToken.None); }
解答
问题1:异常原因分析
这个异常本质是代码对已失效的WebSocket连接执行了IO操作,结合你的代码场景,具体触发点包括:
- 客户端主动断开连接后,
ReceiveAsync还未检测到关闭状态,此时调用CloseAsync会因为连接已终止抛出异常。 using块包裹WebSocket对象,当Echo方法执行完毕后,using会自动释放WebSocket资源,若此时还有未完成的IO操作,就会触发“IO被中止”的错误。- 循环结束后直接调用
CloseAsync,但此时客户端可能已经关闭连接,服务器端重复执行关闭操作会导致冲突。
问题2:持续发送+监听关闭的实现方案
核心思路是将接收消息(监听关闭)和发送消息拆分为两个独立异步任务,用CancellationToken协调任务生命周期,避免单线程阻塞导致无法及时响应关闭请求。
修正后的代码示例
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } #if UseOptions var webSocketOptions = new WebSocketOptions() { KeepAliveInterval = TimeSpan.FromSeconds(120), }; app.UseWebSockets(webSocketOptions); #endif ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13; redis = ConnectionMultiplexer.Connect(Configuration["RedisConnect"]); db = redis.GetDatabase(); endPoint = redis.GetEndPoints(); server = redis.GetServer(endPoint[0], Configuration["RedisConnect"]); app.Use(async (context, next) => { if (context.Request.Path == "/ws") { if (context.WebSockets.IsWebSocketRequest) { using var webSocket = await context.WebSockets.AcceptWebSocketAsync(); var cts = new CancellationTokenSource(); // 启动两个独立任务:接收消息+监听关闭、持续发送消息 var receiveTask = ListenForCloseAndMessages(webSocket, cts.Token); var sendTask = ContinuousSendMessages(webSocket, cts.Token); // 等待任一任务完成(比如客户端关闭连接) var completedTask = await Task.WhenAny(receiveTask, sendTask); // 取消另一个未完成的任务 cts.Cancel(); try { // 等待剩余任务完成,避免资源泄漏 await Task.WhenAll(receiveTask, sendTask); } catch (OperationCanceledException) { // 任务被取消属于预期情况,无需处理 } finally { // 仅在连接仍处于有效状态时执行关闭操作 if (webSocket.State == WebSocketState.Open || webSocket.State == WebSocketState.CloseReceived) { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by server", CancellationToken.None); } } } else { context.Response.StatusCode = (int)HttpStatusCode.BadRequest; } } else { await next(); } }); app.UseFileServer(); } private async Task ListenForCloseAndMessages(WebSocket webSocket, CancellationToken token) { var buffer = new byte[1024 * 4]; while (!token.IsCancellationRequested) { WebSocketReceiveResult result; try { result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), token); } catch (OperationCanceledException) { break; } if (result.CloseStatus.HasValue) { // 检测到客户端关闭请求,触发任务取消 token.ThrowIfCancellationRequested(); break; } // 处理客户端消息逻辑 string msg = Encoding.UTF8.GetString(new ArraySegment<byte>(buffer, 0, result.Count)); // 可根据业务需求扩展消息处理逻辑 } } private async Task ContinuousSendMessages(WebSocket webSocket, CancellationToken token) { while (!token.IsCancellationRequested) { try { // 模拟持续发送消息(示例为每秒发送心跳) await webSocket.SendAsync( new ArraySegment<byte>(Encoding.UTF8.GetBytes("serverSays: heartbeat")), WebSocketMessageType.Text, endOfMessage: true, token); await Task.Delay(1000, token); } catch (OperationCanceledException) { break; } catch (WebSocketException) { // 连接已断开,触发任务取消 token.ThrowIfCancellationRequested(); break; } } }
关键改进点
- 任务拆分:将接收和发送逻辑分离为独立任务,避免单线程阻塞导致无法及时响应关闭请求。
- 取消令牌:用
CancellationTokenSource协调任务生命周期,任一任务完成后立即取消另一任务。 - 状态检查:关闭连接前先判断WebSocket状态,避免对已失效连接执行IO操作。
- 异常处理:捕获预期的取消异常和WebSocket连接异常,避免程序崩溃。
问题1的额外修复建议
在原代码的CloseAsync调用前增加状态检查:
if (webSocket.State == WebSocketState.Open || webSocket.State == WebSocketState.CloseReceived) { await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); }
内容的提问来源于stack exchange,提问作者a.mak
相关产品推荐
相关产品推荐

