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

C# ClientWebSocket断开(关闭)检测问题求助

解决WPF中ClientWebSocket连接状态无法检测断开的问题

我明白你现在的困境——ClientWebSocket在断网后不会主动通知你连接断开,导致你的定时器误以为连接还活着。核心问题在于你目前只在连接时设置了connected标记,但没有持续监听连接的状态变化,也没有处理连接异常断开的情况。下面给你一步步的解决方案:

问题根源分析

你的代码里connected变量仅在connect_socket()成功执行后设为true,但:

  • 当网络断开、服务器主动关闭连接时,ClientWebSocket不会自动更新这个变量
  • 你没有持续调用ReceiveAsync来监听连接状态——WebSocket的连接状态变化(包括断开)通常是在接收/发送操作时暴露的,没有消息接收的话,程序无法感知连接已断

解决方案步骤

1. 实现持续的连接监听循环

连接成功后,必须启动一个后台任务持续调用ReceiveAsync,这样才能捕获到连接断开的异常或者服务器发送的关闭帧。

2. 线程安全地维护连接状态

因为定时器和监听任务运行在不同线程,connected变量需要保证线程安全,用volatile关键字或者线程同步机制来避免竞态条件。

3. 完善异常处理与资源清理

在监听循环中捕获连接相关的异常(比如WebSocketException、IOException),一旦触发就标记连接断开,并清理WebSocket实例。

4. 确保会话创建成功才标记连接有效

只有在收到session_id确认会话创建成功后,才把连接状态设为true,而不是连接服务器后立刻标记。


修改后的代码示例

改进后的Class1(WebSocket操作类)

class Class1
{
    private ClientWebSocket _webSocket;
    // 用volatile保证多线程下的状态可见性
    private volatile bool _connected = false;
    // 提供外部只读的连接状态
    public bool Connected => _connected;

    public async Task<bool> ConnectSocketAsync()
    {
        if (_connected)
            return true;

        try
        {
            _webSocket = new ClientWebSocket();
            _webSocket.Options.SetRequestHeader("headername", "headerValue");
            await _webSocket.ConnectAsync(new Uri("你的WebSocket服务器地址"), CancellationToken.None);

            // 先创建会话
            await CreateSessionAsync();

            // 启动后台监听任务,持续检测连接状态和接收消息
            _ = StartListeningAsync();

            return _connected;
        }
        catch (Exception ex)
        {
            // 连接失败时清理资源
            CleanupWebSocket();
            Debug.WriteLine($"连接失败: {ex.Message}");
            return false;
        }
    }

    private async Task CreateSessionAsync()
    {
        var byteReceived = new ArraySegment<byte>(new byte[1024]);
        var receiveResult = await _webSocket.ReceiveAsync(byteReceived, CancellationToken.None);
        
        if (receiveResult.MessageType == WebSocketMessageType.Close)
        {
            await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
            CleanupWebSocket();
            return;
        }

        var stringNotify = Encoding.UTF8.GetString(byteReceived.Array, 0, receiveResult.Count);
        var jsonNotify = JsonConvert.DeserializeObject<Dictionary<string, object>>(stringNotify);
        
        if (jsonNotify.TryGetValue("result", out var resultObj))
        {
            var result = JsonConvert.DeserializeObject<Dictionary<string, string>>(resultObj.ToString());
            if (result.TryGetValue("session_id", out var sessionId) && !string.IsNullOrEmpty(sessionId))
            {
                Debug.WriteLine($"session_id = {sessionId}");
                // 只有拿到session_id才标记连接成功
                _connected = true;
            }
        }
    }

    private async Task StartListeningAsync()
    {
        var buffer = new ArraySegment<byte>(new byte[1024]);
        while (_connected && _webSocket.State == WebSocketState.Open)
        {
            try
            {
                var receiveResult = await _webSocket.ReceiveAsync(buffer, CancellationToken.None);

                // 处理服务器主动关闭连接的情况
                if (receiveResult.MessageType == WebSocketMessageType.Close)
                {
                    await _webSocket.CloseAsync(receiveResult.CloseStatus.Value, receiveResult.CloseStatusDescription, CancellationToken.None);
                    _connected = false;
                    break;
                }

                // 这里可以处理收到的其他消息,如果不需要可以忽略
                var message = Encoding.UTF8.GetString(buffer.Array, 0, receiveResult.Count);
                Debug.WriteLine($"收到消息: {message}");
            }
            catch (WebSocketException ex)
            {
                // 捕获WebSocket相关异常,标记连接断开
                Debug.WriteLine($"WebSocket异常: {ex.Message}");
                _connected = false;
                break;
            }
            catch (IOException ex)
            {
                // 网络断开等IO异常,标记连接断开
                Debug.WriteLine($"网络异常: {ex.Message}");
                _connected = false;
                break;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"未知异常: {ex.Message}");
                _connected = false;
                break;
            }
        }

        // 循环结束后清理资源
        CleanupWebSocket();
    }

    private void CleanupWebSocket()
    {
        _connected = false;
        _webSocket?.Dispose();
        _webSocket = null;
    }
}

改进后的Class2(WPF中使用类)

class Class2
{
    private readonly Class1 _webSocketClient;
    private readonly System.Threading.Timer _timer;

    public Class2()
    {
        InitializeComponent();
        _webSocketClient = new Class1();
        // 定时器首次延迟0秒,之后每隔1分钟触发
        _timer = new System.Threading.Timer(TimerCallback, null, 0, 60000);
    }

    private async void TimerCallback(object state)
    {
        // 避免重复连接
        if (!_webSocketClient.Connected)
        {
            Debug.WriteLine("尝试重新连接...");
            await _webSocketClient.ConnectSocketAsync();
        }
    }
}

关键改进点说明

  1. 持续监听循环:StartListeningAsync后台任务一直调用ReceiveAsync,一旦连接断开,这个方法会抛出异常或者收到关闭帧,此时我们就能及时更新_connected状态。
  2. 线程安全的状态标记:用volatile修饰_connected,保证多线程下的状态可见性,避免定时器和监听任务看到不一致的状态。
  3. 资源清理:每次连接断开后都调用CleanupWebSocket释放ClientWebSocket资源,避免内存泄漏。
  4. 异步调用优化:定时器回调用async void(适合定时器这种场景),并使用await代替Wait(),避免WPF UI线程死锁。
  5. 会话确认逻辑:只有成功拿到session_id后才标记连接成功,确保连接状态的准确性。

这样修改后,当网络断开时,StartListeningAsync里的ReceiveAsync会抛出IOException,_connected会被设为false,下一次定时器触发时就会尝试重新连接了。

内容的提问来源于stack exchange,提问作者J-Go

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:05:19