.NET Core中复用ClientWebSocket解决逆变器单客户端限制问题
解决方案
你的问题核心在于两点:一是ClientWebSocket本身不支持并发操作,多个请求同时调用Send/Receive会导致连接状态异常;二是当前控制器内直接new MyService的方式,会导致每个请求都创建新的MyService实例(控制器默认是Scoped生命周期),进而创建多个WebSocket连接,触发逆变器的单客户端限制。
下面是具体的修复步骤和代码示例:
1. 将MyService注册为单例,确保全局复用一个WebSocket实例
在Program.cs(.NET 6+)或Startup.cs中,把MyService注册为单例服务,这样整个应用只会有一个MyService实例,对应一个ClientWebSocket:
builder.Services.AddSingleton<MyService>();
同时修改控制器,通过构造函数注入MyService,而不是自己new:
private readonly MyService _myService; public YourController(MyService myService) { _myService = myService; } [HttpGet] [Route("GetEnergyParameters")] // 改成异步方法,避免用.Result导致死锁 public async Task<List<EnergyParameter>> GetEnergyParameters() { return await _myService.GetEnergyParametersAsync(); }
2. 给WebSocket操作加异步锁,保证串行执行
ClientWebSocket的SendAsync、ReceiveAsync以及连接操作都不是线程安全的,必须用异步锁(SemaphoreSlim)保证同一时间只有一个请求操作WebSocket:
修改后的MyService代码:
public class MyService { private ClientWebSocket _wsClient = new ClientWebSocket(); // 异步锁,允许同时1个操作执行 private readonly SemaphoreSlim _wsSemaphore = new SemaphoreSlim(1, 1); private readonly ILogger<MyService> _logger; // 构造函数注入日志服务 public MyService(ILogger<MyService> logger) { _logger = logger; } internal async Task<string> SendMessage(string message) { await _wsSemaphore.WaitAsync(); try { byte[] buffer = Encoding.UTF8.GetBytes(message); // 检查连接状态,无效则重建连接 if (_wsClient.State != WebSocketState.Open) { // 先清理旧连接 if (_wsClient.State != WebSocketState.Closed && _wsClient.State != WebSocketState.Aborted) { try { await _wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, "Reconnecting", CancellationToken.None); } catch { // 忽略关闭异常 } } _wsClient.Dispose(); _wsClient = new ClientWebSocket(); await ConnectAsync(); } // 发送消息 await _wsClient.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None); // 接收响应 buffer = new byte[2048 * 2]; var result = await _wsClient.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); // 处理服务器主动关闭的情况 if (result.MessageType == WebSocketMessageType.Close) { await _wsClient.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); return string.Empty; } return Encoding.UTF8.GetString(buffer, 0, result.Count); } catch (Exception ex) { await _logger.LogError(ex, $"SendMessage失败,消息:{message},WebSocket状态:{_wsClient.State}"); // 异常后重置WebSocket,避免后续请求复用无效连接 try { if (_wsClient.State != WebSocketState.Closed && _wsClient.State != WebSocketState.Aborted) { await _wsClient.CloseAsync(WebSocketCloseStatus.InternalServerError, "发生错误", CancellationToken.None); } _wsClient.Dispose(); _wsClient = new ClientWebSocket(); } catch { // 忽略重置时的异常 } return string.Empty; } finally { _wsSemaphore.Release(); } } // 你的连接逻辑,根据实际情况调整 private async Task ConnectAsync() { var inverterUri = new Uri("ws://你的逆变器WebSocket地址"); await _wsClient.ConnectAsync(inverterUri, CancellationToken.None); } public async Task<List<EnergyParameter>> GetEnergyParametersAsync() { // 这里调用SendMessage处理逻辑,比如发送查询指令、解析响应 var response = await SendMessage("查询能量参数的指令"); // 解析response为List<EnergyParameter> return ParseResponseToEnergyParameters(response); } private List<EnergyParameter> ParseResponseToEnergyParameters(string response) { // 实现你的解析逻辑 return new List<EnergyParameter>(); } }
3. 关键修复点说明
- 异步锁SemaphoreSlim:替代同步lock,因为lock内部不能使用await,SemaphoreSlim支持异步等待,保证WebSocket操作串行执行,避免并发状态冲突。
- 异常后重置WebSocket:当出现连接异常或状态无效时,主动销毁旧实例并创建新实例,防止后续请求复用异常连接。
- 控制器异步化:去掉.Result改用await,避免ASP.NET Core中的死锁问题,符合异步编程规范。
内容的提问来源于stack exchange,提问作者Hickori
相关产品推荐
相关产品推荐

