.net core 中使用 websocket

本文介绍了在.NET Core中使用WebSocket的基本步骤,包括创建保存WebSocket的类、管理WebSocket的基类、WebSocket中间件、管理子类、注入扩展的配置以及Startup.cs的设置。通过这个示例,可以搭建一个简单的聊天室功能。

原文地址: https://www.jeremyjone.com/782/, 转载请注明。


感觉放了好长时间的假期。贴个 websocket 的简单示例。

整体文件结构

- 项目(WebSockets.Test)
|-- Extensions
|      |-- SocketsExtension.cs
|-- Handlers
|      |-- WebSocketMessageHandler.cs
|-- SocketsManager
|      |-- SocketsHandler.cs
|      |-- SocketsManager.cs
|      |-- SocketsMiddleware.cs
|-- Program.cs
|-- Startup.cs

大体需要的文件是这些,这是最基本的示例,可以按需自行修改。

1、创建保存 WebSocket 的类

该类用于保存所有 WebSocket。

// 文件:SocketsManager/SocketsManager.cs

using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;

namespace WebSockets.Test.SocketsManager
{
   
   
    public class SocketsManager
    {
   
   
        private readonly ConcurrentDictionary<string, WebSocket> _connections =
            new ConcurrentDictionary<string, WebSocket>();

        /// <summary>
        /// 获取所有 sockets 的字典集合
        /// </summary>
        /// <returns></returns>
        public ConcurrentDictionary<string, WebSocket> GetAllConnections()
        {
   
   
            return _connections;
        }

        /// <summary>
        ///     获取指定 id 的 socket
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public WebSocket GetSocketById(string id)
        {
   
   
            return _connections.FirstOrDefault(x => x.Key == id).Value;
        }

        /// <summary>
        /// 根据 socket 获取其 id
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        public string GetId(WebSocket socket)
        {
   
   
            return _connections.FirstOrDefault(x => x.Value == socket).Key;
        }

        /// <summary>
        /// 删除指定 id 的 socket,并关闭该链接
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task RemoveSocketAsync(string id)
        {
   
   
            _connections.TryRemove(id, out var socket);
            await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "socket connection closed",
                CancellationToken.None);
        }

        /// <summary>
        /// 添加一个 socket
        /// </summary>
        /// <param name="socket"></param>
        public void AddSocket(WebSocket socket)
        {
   
   
            _connections.TryAdd(CreateId(), socket);
        }

        /// <summary>
        /// 创建 id
        /// </summary>
        /// <returns></returns>
        private string CreateId()
        {
   
   
            return Guid.NewGuid().ToString("N");
        }
    }
}

2、创建管理和操作 WebSocket 的基类

该类旨在处理 socket 的连接和断连,以及接收和发送消息,属于基类。

// 文件: SocketsManager/SocketsHandle.cs

using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WebSockets.Test.SocketsManager
{
   
   
    public abstract class SocketsHandler
    {
   
   
        protected SocketsHandler(SocketsManager sockets)
        {
   
   
            Sockets = sockets;
        }

        public SocketsManager Sockets {
   
    get;<
<think>我们正在诊断.NETCoreWebSocket内存泄漏问题。根据引用[4]的内容,内存泄漏的原因可能是:分配内存的速度快于垃圾回收的速度,或者对象存在引用导致无法被回收。因此,我们需要重点关注WebSocket相关对象是否被正确释放以及是否存在意外的引用。以下为诊断步骤:###1.**使用内存分析工具**-**工具推荐**:-**VisualStudioDiagnosticTools**:适用于开发环境,可进行内存快照对比。-**dotMemory**(JetBrains):独立内存分析工具,支持生产环境分析。-**.NETCore内置计数器**:通过`dotnet-counters`实时监控内存分配。-**操作步骤**:1.运行应用程序并模拟WebSocket负载。2.使用工具捕获多个内存快照(例如:负载前、负载后、负载结束后)。3.比较快照,重点关注:-`WebSocket`实例数量是否持续增加。-未释放的缓冲区(如`byte[]`数组)。-事件订阅者(如`WebSocket`事件未取消订阅)[^4]。###2.**检查资源释放代码**-**关键代码审查点**:```csharp//错误示例:未关闭WebSocketpublicasyncTaskHandleConnection(WebSocketwebSocket){varbuffer=newbyte[1024];while(webSocket.State==WebSocketState.Open){varresult=awaitwebSocket.ReceiveAsync(buffer,CancellationToken.None);//处理数据...}//缺少webSocket.CloseAsync()和Dispose()}```**修复方案**:```csharppublicasyncTaskHandleConnection(WebSocketwebSocket){try{varbuffer=ArrayPool<byte>.Shared.Rent(1024);try{while(webSocket.State==WebSocketState.Open){varresult=awaitwebSocket.ReceiveAsync(buffer,CancellationToken.None);//处理数据...}}finally{ArrayPool<byte>.Shared.Return(buffer);}}finally{if(webSocket.State!=WebSocketState.Closed)awaitwebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure,"Close",CancellationToken.None);webSocket.Dispose();//显式释放资源}}```-**注意事项**:-使用`ArrayPool`复用缓冲区(避免频繁分配内存)[^4]。-确保`CloseAsync`和`Dispose`在`finally`块中执行(即使发生异常)。###3.**检测事件和引用泄漏**-**常见陷阱**:-将`WebSocket`对象存入静态集合(如连接管理器),但未及时移除。-外部对象(如控制器)持有`WebSocket`引用。-**诊断方法**:-在内存快照中查找`WebSocket`对象的引用链,定位未释放的根对象。-检查是否注册了事件(如`WebSocket`的生命周期事件),并在关闭时取消注册。###4.**压力测试与监控**-**步骤**:1.使用工具(如ApacheBench、WebSocketStressTest)模拟高并发连接。2.监控进程内存:```bashdotnet-countersmonitor--process-id<PID>System.Runtime```3.重点关注指标:-`GCHeapSize`:托管堆大小是否持续增长。-`Gen0/1/2Collections`:垃圾回收频率。-`NumberofActiveTimers`:未释放的`Timer`可能关联WebSocket心跳。###5.**代码静态分析**-**规则检查**:-使用Roslyn分析器检测`IDisposable`对象未释放:```csharp//警告示例varwebSocket=newWebSocket();//未调用Dispose或CloseAsync```-检查异步方法中是否遗漏`await`(导致任务未完成,对象无法释放)。---###诊断案例>某服务在WebSocket连接关闭后,内存中残留大量`byte[]`缓冲区。>**分析**:快照显示缓冲区来自未返回的`ArrayPool`。>**解决**:在`finally`块中调用`ArrayPool.Shared.Return()`[^4]。---###§§相关问题§§1.如何用`dotMemory`分析.NETCore内存泄漏?2.WebSocket中间件中如何安全管理连接集合?3.如何编写自动化测试检测WebSocket内存泄漏?4.`ArrayPool`的使用有哪些注意事项?5.为什么`WebSocket`关闭后垃圾回收仍不释放内存?</think>### .NET Core WebSocket 内存泄漏诊断方法 诊断WebSocket内存泄漏需结合工具分析和代码审查,以下是系统化诊断流程: --- #### **1. 内存分析工具使用** [^4] - **dotMemory 诊断步骤**: 1. 捕获内存快照: ```bash dotMemory get-snapshot <PID> --save-to=snapshot.dmw ``` 2. 分析对象保留路径: - 筛选`WebSocket`实例,检查其引用链 - 重点关注未释放的`byte[]`缓冲区 3. 比较多个快照: - 模拟操作前/后各捕获一次快照 - 检查`WebSocket`实例数量是否持续增长 - **Visual Studio 诊断工具**: - 内存使用率图表:观察内存是否阶梯式增长 - 对象分配跟踪:筛选`System.Net.WebSockets`命名空间 --- #### **2. 代码审查关键点** [^4] ```csharp // 检查点1:是否确保WebSocket关闭 public async Task Handle(WebSocket webSocket) { try { /* ... */ } finally { // 必须显式关闭和释放 await webSocket.CloseAsync(...); webSocket.Dispose(); // 缺少此句会导致泄漏 } } // 检查点2:缓冲区管理 var buffer = new byte[4096]; // 错误:每次创建新缓冲区 // 应改为: var buffer = ArrayPool<byte>.Shared.Rent(4096); try { /* ... */ } finally { ArrayPool<byte>.Shared.Return(buffer); } ``` **常见泄漏模式**: - 未取消事件订阅(如`WebSocket`状态变更事件) - 静态集合持有WebSocket引用未清理 - 异步操作未正确处理异常导致资源未释放 --- #### **3. 运行时监控** [^2] - **dotnet-counters 实时监控**: ```bash dotnet-counters monitor --name <进程名> \ System.Runtime \ Microsoft.AspNetCore.Http.Connections ``` 关键指标: - `gc-heap-size`:托管堆大小变化 - `alloc-rate`:内存分配速率 - `active-connections`:WebSocket连接数 - **GC 行为分析**: ```bash dotnet-gcmonitor <PID> --output gcstats.log ``` 检查: - Gen2回收频率是否异常升高 - 回收后内存是否未回落 --- #### **4. 压力测试诊断** [^3] 使用`TestServer`模拟高负载: ```csharp // 创建测试服务器 var builder = WebHost.CreateDefaultBuilder() .UseStartup<Startup>(); using var server = new TestServer(builder); // 模拟并发连接 Parallel.For(0, 1000, async i => { var client = server.CreateWebSocketClient(); using var socket = await client.ConnectAsync(...); // 执行操作... }); // 检查内存变化 var memory = GC.GetTotalMemory(true); Console.WriteLine($"内存使用: {memory / 1024} KB"); ``` **诊断点**: - 连接释放后内存是否回落 - 连接数峰值与内存增长关系 --- #### **5. 泄漏场景验证表** | 场景 | 验证方法 | 泄漏特征 | |---------------------|----------------------------|----------------------------| | 连接未关闭 | 统计`WebSocket`实例数量 | 实例数=历史连接总数 | | 缓冲区未回收 | 分析`byte[]`分配来源 | 大尺寸数组持续增长 | | 事件订阅泄漏 | 检查事件处理程序引用 | `WebSocket`被事件源强引用 | | 异步操作阻塞 | 线程转储分析 | 大量线程卡在`ReceiveAsync` | --- #### **诊断报告示例** > 案例:某服务内存持续增长至2GB后崩溃 > **分析过程**: > 1. dotMemory快照显示500个`WebSocket`实例存活(实际活跃连接仅50个) > 2. 引用链追踪发现静态`ConnectionManager`未移除断开连接 > 3. 每个实例关联416KB未释放缓冲区 > **解决**: > - 添加连接心跳检测自动清理 > - 改用`ArrayPool`分配缓冲区[^4] > 优化后内存稳定在200MB以下 --- ###
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值