生产环境必须用 Microsoft.AspNetCore.WebSockets,因其内置 HTTP 升级、连接管理、超时控制与 DI 集成;裸用 System.Net.WebSockets 需手动处理握手、生命周期等,易出错且不支持高并发。
WebSocket 服务端用
还是
?
直接上结论:生产环境必须用
(即 ASP.NET Core 内置 WebSocket 中间件),而不是裸用
。后者只是底层协议封装,不处理 HTTP 升级、连接生命周期、并发调度、TLS 终止等关键环节——你得自己写握手、解析 Upgrade 头、管理 socket 池,极易出错且无法承载高并发。
ASP.NET Core 的
中间件已内置超时控制、缓冲区复用、连接限流(
可配
和
),且天然集成 DI、日志、中间件管道。
:避免 NAT/
防火墙
静默断连
:小包多频场景下比默认 4KB 更省内存
务必在
中调用
,且位置要在
之后、
之前
如何安全地广播消息给所有在线连接?别用静态集合存
对象不是线程安全的,且不可跨请求重用。常见错误是把
直接塞进
,然后在另一个线程里直接
——这会触发
,因为连接可能已在其他线程关闭或正被读取。
正确做法是:为每个连接分配唯一 ID(如 GUID),用
存储包装类,内部封装
+
+ 状态标记,并在发送前检查
。
}
阻塞模型怎么应对高并发读?必须配合
每个 WebSocket 连接默认独占一个后台线程执行
循环,若用
分配缓冲区,在万级连接下会引发 GC 压力暴增和内存碎片。实测 5000 连接持续收 1KB 消息时,Gen2 GC 频率从 2 分钟一次飙升至每秒多次。
Android 操作系统的介绍 中文WORD版
本文档主要讲述的是Android 操作系统的介绍;Android是基于Linux内核的操作系统,是Google公司在2007年11月5日公布的手机操作系统,早期由Google开发,后由开放手持设备联盟(Open Handset Alliance)开发。它采用了软件堆层(software stack,又名以软件叠层)的架构,主要分为三部分。底层Linux内核只提供基本功能;其他的应用软件则由各公司自行开发,部分程序以Java编写。希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
下载
解决方案:用
替代
,并在处理完后调用
归还缓冲区。注意
必须指向
,不能直接传
的
(会触发拷贝)。
缓冲区大小建议设为 2^n(如 4096、8192),匹配 MemoryPool 默认块大小
返回的
中
是实际接收
字节
数,不是缓冲区长度
必须用
包裹接收循环,否则连接断开时线程不会退出
为什么用了
还出现连接丢失?检查
异常捕获
很多人把 WebSocket 接入逻辑写在
里,但没包裹
。一旦
后的初始化代码(如鉴权、DB 查询)抛异常,连接会被静默关闭,客户端收到 1006 错误,而服务端日志里只有未捕获异常堆栈,找不到对应连接 ID。
必须确保整个 WebSocket 生命周期都在
内,且异常时主动调用
,再清理字典中对应项。
不要在
前做耗时操作(如查 DB),否则会阻塞 HTTP 升级响应
客户端重连间隔建议用指数退避(如 1s → 2s → 4s),避免雪崩式重连冲击
Kestrel 默认单连接最大请求体是 30MB,若需传大文件,要显式配置
真正难的不是写通 WebSocket,而是让成千上万个连接在内存、GC、线程调度、网络丢包、客户端异常断连这些边界条件下稳定跑满 7×24 小时。每个
实例背后都是
操作系统
socket、TLS 状态、.NET 线程池资源的精确配比,漏掉任意一环,压测时都会在凌晨三点给你发告警。
Microsoft.AspNetCore.WebSocketsSystem.Net.WebSocketsMicrosoft.AspNetCore.WebSocketsSystem.Net.WebSocketsUseWebSocketsWebSocketOptionsKeepAliveIntervalReceiveBufferSizeWebSocketOptions.KeepAliveInterval = TimeSpan.FromSeconds(30)WebSocketOptions.ReceiveBufferSize = 4 * 1024Startup.Configureapp.UseWebSockets(options => { ... })UseRoutingUseEndpointsWebSocketWebSocketWebSocketstatic ConcurrentDictionarySendAsyncInvalidOperationException: "The WebSocket is in an invalid state"ConcurrentDictionaryWebSocketCancellationTokenSourceState == WebSocketState.Openpublic class WebSocketConnection
{
public WebSocket Socket { get; }
public CancellationTokenSource CloseToken { get; } = new();
public WebSocketConnection(WebSocket socket) => Socket = socket;
public async Task SendAsync(byte[] data)
{
if (Socket.State != WebSocketState.Open) return;
try
{
await Socket.SendAsync(new ArraySegment(data),
WebSocketMessageType.Binary, true, CloseToken.Token);
}
catch (OperationCanceledException) { }
catch (WebSocketException) { /* 连接已断,后续清理 */ }
} ReceiveAsyncMemoryPoolReceiveAsyncnew byte[bufferSize]MemoryPool.Shared.Rent(8192) new byte[8192].Return()ArraySegmentMemory.Span MemoryPool.Rent().Memory ToArray()ReceiveAsyncWebSocketReceiveResultCountwhile (!token.IsCancellationRequested)ConcurrentDictionaryOnConnectedAsyncMapGet("/ws", async context => { ... })try/catchAcceptWebSocketAsync()try/catchwebsocket.CloseAsync(WebSocketCloseStatus.InternalServerError, "...", CancellationToken.None)AcceptWebSocketAsyncoptions.Limits.MaxRequestBodySize = nullWebSocket