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

Azure Web PubSub ClientWebSocket重连持续报401状态码问题咨询

问题描述

我在Azure中使用Web PubSub Service资源,通过ClientWebSocket实现了基础客户端。初始连接正常,能接收消息,但运行约36小时后连接断开,之后重连反复报错:

"The server returned status code '401' when status code '101' was expected."

客户端是Blazor应用的单例服务,持续运行。重启Blazor应用后可正常连接。

客户端代码

public class AzurePubSubWebsocketClient
{
    private readonly Uri _serverUri;
    private CancellationTokenSource _cancellationTokenSource;
    private bool _isRunning = false;
    private object _runningLockObject = new object();

    int reconnectDelaySeconds = 5; // Adjust the delay (in seconds) between reconnection attempts.

    public event Action<string> OnLogMessage;
    public event Action<string> OnMessageReceived;

    public AzurePubSubWebsocketClient(Uri serverUri)
    {
        this._serverUri = serverUri;
    }

    public async Task ConnectAndReceiveMessages()
    {
        while (true)
        {
            try
            {
                OnLogMessage?.Invoke("Connecting to WebSocket...");

                using (var clientWebSocket = new ClientWebSocket())
                {
                    await clientWebSocket.ConnectAsync(_serverUri, CancellationToken.None);
                    OnLogMessage?.Invoke("Connected to the WebSocket server.");

                    lock (_runningLockObject)
                    {
                        _isRunning = true;
                    }

                    // Receive messages from the server in a loop.
                    byte[] receiveBuffer = new byte[4096];
                    WebSocketReceiveResult receiveResult;

                    while (clientWebSocket.State == WebSocketState.Open)
                    {
                        receiveResult = await clientWebSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);

                        if (receiveResult.MessageType == WebSocketMessageType.Text)
                        {
                            string message = Encoding.UTF8.GetString(receiveBuffer, 0, receiveResult.Count);
                            OnLogMessage?.Invoke($"Received message: {message}");

                            try
                            {
                                OnMessageReceived?.Invoke(message);
                            }
                            catch (Exception exception)
                            {
                                OnMessageReceived?.Invoke(exception.Message);
                            }
                        }
                    }

                    lock (_runningLockObject)
                    {
                        _isRunning = false;
                    }
                }
            }
            catch (Exception ex)
            {
                lock (_runningLockObject)
                {
                    _isRunning = false;
                }

                OnLogMessage?.Invoke($"Error: {ex.Message}");
            }              

            // Reconnection delay to avoid frequent connection attempts.
            OnLogMessage?.Invoke($"Connection lost. Retrying in {reconnectDelaySeconds} seconds...");
            await Task.Delay(TimeSpan.FromSeconds(reconnectDelaySeconds));
        }
    }

    public bool IsRunning()
    {
        lock (_runningLockObject)
        {
            return _isRunning;
        }
    }
}

相关日志

[03/08/23 11:18:16.104] Connection lost. Retrying in 5 seconds...
[03/08/23 11:18:21.103] Connecting to WebSocket...
[03/08/23 11:18:21.148] Error: The server returned status code '401' when status code '101' was expected.
[03/08/23 11:18:21.148] Connection lost. Retrying in 5 seconds...
[03/08/23 11:18:26.150] Connecting to WebSocket...
[03/08/23 11:18:26.163] Error: The server returned status code '401' when status code '101' was expected.
[03/08/23 11:18:26.163] Connection lost. Retrying in 5 seconds...
[03/08/23 11:18:31.166] Connecting to WebSocket...
[03/08/23 11:18:31.209] Error: The server returned status code '401' when status code '101' was expected.
[03/08/23 11:18:31.209] Connection lost. Retrying in 5 seconds...
[03/08/23 11:18:36.198] Connecting to WebSocket...
[03/08/23 11:18:36.256] Error: The server returned status code '401' when status code '101' was expected.
[03/08/23 11:18:36.256] Connection lost. Retrying in 5 seconds...
[03/08/23 11:18:41.263] Connecting to WebSocket...
问题分析与解决

核心原因

401错误的本质是连接URL中的认证令牌过期。当前代码在初始化时固定了_serverUri,这个URL包含的Azure Web PubSub认证令牌有有效期(默认通常1小时,最长不超过24小时)。首次连接成功后,令牌在36小时后早已过期,重连时使用旧令牌会被服务端拒绝,返回401。重启应用后能正常连接,是因为重启时重新生成了带有效令牌的URL。

解决步骤

  1. 动态生成连接URL
    不要在客户端初始化时固定_serverUri,每次重连前通过Azure SDK实时生成带有效令牌的WebSocket连接URL。示例代码片段:
// 注入WebPubSubServiceClient(单例)
private readonly WebPubSubServiceClient _pubSubClient;
private readonly string _hubName;

public AzurePubSubWebsocketClient(WebPubSubServiceClient pubSubClient, string hubName)
{
    _pubSubClient = pubSubClient;
    _hubName = hubName;
}

// 每次重连前调用获取新URL
private Uri GetFreshConnectionUri()
{
    // 生成有效期1小时的令牌(可调整expiresIn参数)
    var uri = _pubSubClient.GetClientAccessUri(hub: _hubName, expiresIn: TimeSpan.FromHours(1));
    return uri;
}
  1. 修改重连逻辑
    在每次进入连接循环时,调用GetFreshConnectionUri获取新URL,替换原有的固定_serverUri:
using (var clientWebSocket = new ClientWebSocket())
{
    var freshUri = GetFreshConnectionUri();
    await clientWebSocket.ConnectAsync(freshUri, CancellationToken.None);
    // 后续接收逻辑保持不变
}
  1. 优化断开日志
    首次连接断开未捕获异常,是因为clientWebSocket.State变为非Open状态后直接退出循环,建议添加状态日志便于排查:
while (clientWebSocket.State == WebSocketState.Open)
{
    // 接收消息逻辑
}
OnLogMessage?.Invoke($"Connection closed. Current state: {clientWebSocket.State}");

额外建议

  • 实现指数退避重连:将固定5秒延迟改为指数退避(如5秒→10秒→20秒,上限60秒),避免频繁重试给服务端造成压力。
  • 添加令牌过期预警:生成令牌时记录过期时间,提前5分钟触发主动重连,避免因令牌过期导致连接断开。

内容的提问来源于stack exchange,提问作者percentum

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 18:29:58