0

0

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

星降

星降

发布时间:2026-01-13 11:37:44

|

376人浏览过

|

来源于php中文网

原创

生产环境必须用 Microsoft.AspNetCore.WebSockets,因其内置 HTTP 升级、连接管理、超时控制与 DI 集成;裸用 System.Net.WebSockets 需手动处理握手、生命周期等,易出错且不支持高并发。

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

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 可配 KeepAliveIntervalReceiveBufferSize),且天然集成 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 ArraySegmentzuojiankuohaophpcnbyteyoujiankuohaophpcn(data), 
            WebSocketMessageType.Binary, true, CloseToken.Token);
    }
    catch (OperationCanceledException) { }
    catch (WebSocketException) { /* 连接已断,后续清理 */ }
}

}

ReceiveAsync 阻塞模型怎么应对高并发读?必须配合 MemoryPool

每个 WebSocket 连接默认独占一个后台线程执行 ReceiveAsync 循环,若用 new byte[bufferSize] 分配缓冲区,在万级连接下会引发 GC 压力暴增和内存碎片。实测 5000 连接持续收 1KB 消息时,Gen2 GC 频率从 2 分钟一次飙升至每秒多次。

mallcloud商城
mallcloud商城

mallcloud商城基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba并采用前后端分离vue的企业级微服务敏捷开发系统架构。并引入组件化的思想实现高内聚低耦合,项目代码简洁注释丰富上手容易,适合学习和企业中使用。真正实现了基于RBAC、jwt和oauth2的无状态统一权限认证的解决方案,面向互联网设计同时适合B端和C端用户,支持CI/CD多环境部署,并提

下载

解决方案:用 MemoryPool.Shared.Rent(8192) 替代 new byte[8192],并在处理完后调用 .Return() 归还缓冲区。注意 ArraySegment 必须指向 Memory.Span,不能直接传 MemoryPool.Rent().MemoryToArray()(会触发拷贝)。

  • 缓冲区大小建议设为 2^n(如 4096、8192),匹配 MemoryPool 默认块大小
  • ReceiveAsync 返回的 WebSocketReceiveResultCount 是实际接收字节数,不是缓冲区长度
  • 必须用 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 线程池资源的精确配比,漏掉任意一环,压测时都会在凌晨三点给你发告警。

相关专题

更多
什么是中间件
什么是中间件

中间件是一种软件组件,充当不兼容组件之间的桥梁,提供额外服务,例如集成异构系统、提供常用服务、提高应用程序性能,以及简化应用程序开发。想了解更多中间件的相关内容,可以阅读本专题下面的文章。

177

2024.05.11

Golang 中间件开发与微服务架构
Golang 中间件开发与微服务架构

本专题系统讲解 Golang 在微服务架构中的中间件开发,包括日志处理、限流与熔断、认证与授权、服务监控、API 网关设计等常见中间件功能的实现。通过实战项目,帮助开发者理解如何使用 Go 编写高效、可扩展的中间件组件,并在微服务环境中进行灵活部署与管理。

212

2025.12.18

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

231

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

435

2024.03.01

counta和count的区别
counta和count的区别

Count函数用于计算指定范围内数字的个数,而CountA函数用于计算指定范围内非空单元格的个数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

197

2023.11.20

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

84

2023.09.25

登录token无效
登录token无效

登录token无效解决方法:1、检查token的有效期限,如果token已经过期,需要重新获取一个新的token;2、检查token的签名,如果签名不正确,需要重新获取一个新的token;3、检查密钥的正确性,如果密钥不正确,需要重新获取一个新的token;4、使用HTTPS协议传输token,建议使用HTTPS协议进行传输 ;5、使用双因素认证,双因素认证可以提高账户的安全性。

6082

2023.09.14

登录token无效怎么办
登录token无效怎么办

登录token无效的解决办法有检查Token是否过期、检查Token是否正确、检查Token是否被篡改、检查Token是否与用户匹配、清除缓存或Cookie、检查网络连接和服务器状态、重新登录或请求新的Token、联系技术支持或开发人员等。本专题为大家提供token相关的文章、下载、课程内容,供大家免费下载体验。

800

2023.09.14

php与html混编教程大全
php与html混编教程大全

本专题整合了php和html混编相关教程,阅读专题下面的文章了解更多详细内容。

3

2026.01.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

相关下载

更多

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Rust 教程
Rust 教程

共28课时 | 4.4万人学习

PostgreSQL 教程
PostgreSQL 教程

共48课时 | 7.1万人学习

Git 教程
Git 教程

共21课时 | 2.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号