You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 06:12:41