跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

c# WebSocket 和高并发双向通信 C# 实现

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

相关文章