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。
解决步骤
- 动态生成连接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; }
- 修改重连逻辑
在每次进入连接循环时,调用GetFreshConnectionUri获取新URL,替换原有的固定_serverUri:
using (var clientWebSocket = new ClientWebSocket()) { var freshUri = GetFreshConnectionUri(); await clientWebSocket.ConnectAsync(freshUri, CancellationToken.None); // 后续接收逻辑保持不变 }
- 优化断开日志
首次连接断开未捕获异常,是因为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
相关产品推荐
相关产品推荐

