asp.net core中websocket服务端核心是避免假死、漏关和上线崩溃,必须严格按顺序配置userouting→usewebsockets→useendpoints,终结点内先校验iswebsocketrequest再accept,接收循环必须依赖closestatus退出而非state判断,nginx需透传upgrade/connection头并启用http/1.1。

用 System.Net.WebSockets 在 ASP.NET Core 里写 WebSocket 服务端,核心不是“怎么连上”,而是“怎么不假死、不漏关、不上线就崩”。跳过中间件顺序、IsWebSocketRequest 校验、CloseStatus 检查这三步中的任意一步,服务端在弱网、NAT、页面刷新或代理环境下大概率静默卡死或连接堆积。
UseWebSockets() 必须紧挨着 UseRouting() 放
光在 Program.cs 里调 AddWebSockets() 不起作用。中间件链断了,请求根本进不到 WebSocket 处理逻辑里。
-
app.UseRouting()后必须立刻接app.UseWebSockets() -
app.UseWebSockets()必须在app.UseEndpoints()之前,不能穿插其他中间件(比如UseAuthentication()或自定义日志中间件) - 顺序错位会导致
context.WebSockets.IsWebSocketRequest永远为false,后续AcceptWebSocketAsync()直接抛InvalidOperationException
终结点里必须先判断 IsWebSocketRequest 再 Accept
直接在 app.Map("/ws", async context => { ... }) 里无条件调 AcceptWebSocketAsync() 是高危操作——普通 HTTP GET 请求(比如浏览器手误访问 /ws)也会触发该终结点,导致异常炸掉整个请求管道。
- 务必写成:
if (context.WebSockets.IsWebSocketRequest) { ... },否则返回400或提前return - 握手成功后拿到的
WebSocket实例是独占的,context不可再读写,后续所有 I/O 只能走它自己的ReceiveAsync()和SendAsync() - 别在终结点里复用
context.Response,也别试图从context.Request里再读 Body —— 握手完成那一刻,HTTP 生命周期就结束了
接收循环必须靠 CloseStatus 退出,不能信 State
while (ws.State == WebSocketState.Open) 是最常见、最隐蔽的假活陷阱。网络中断、客户端强退、NAT 超时后,ws.State 可能长期卡在 CloseReceived 或 Open,但 ReceiveAsync() 既不返回也不报错,后台任务彻底 hang 住。
- 每次
await ws.ReceiveAsync(...)返回后,第一件事就是检查result.CloseStatus.HasValue - 有值就立刻调
await ws.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, ct),不能只Dispose()或等 GC - 服务端主动关闭也必须走
CloseAsync(),否则前端监听的是error而非close,没法做优雅降级 - 缓冲区建议用
MemoryPool<byte>.Shared.Rent(4 * 1024)</byte>分配,收完记得buffer.Return(),避免 GC 压力
Nginx 反向代理必须透传 Upgrade 和 Connection 头
本地 ws://localhost:5000 跑得好好的,一上生产连不上?90% 是 Nginx 把 WebSocket 当普通 HTTP 处理了,直接返回 426 Upgrade Required 或 502 Bad Gateway,连握手都进不去。
- Nginx 配置里必须显式加两行:
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade"; - 还缺一行:
proxy_http_version 1.1;,否则Connection: upgrade不生效 - 漏掉任一字段,服务端收不到
Upgrade: websocket或Connection: Upgrade,IsWebSocketRequest就永远为false
真正难的不是写通第一帧,而是让连接在 NAT 穿透失败、手机切后台、Wi-Fi 切 4G、Nginx 重启这些真实场景下,还能被及时发现、干净释放、不堆积句柄。所有“能连不能收”“发几次就崩”“上线后连接数暴涨”的问题,几乎都出在这几个检查点上,而不是协议理解或编码本身。











