【问题标题】:Server-side SignalR connection fails after significant uptime服务器端 SignalR 连接在正常运行时间较长后失败
【发布时间】:2020-09-23 00:15:53
【问题描述】:

我在 StackOverflow 上搜索了许多与 SignalR 连接相关的其他问题,但似乎没有一个适用于我的具体案例。

我有一个使用 SignalR 集线器的应用程序。客户端可以使用 2 种方法连接到集线器:

  1. 通过使用底层客户端连接到集线器的 .NET Core API
  2. 直接连接到集线器的 URL

我遇到的问题是使用 .NET Core API 进行连接(方法 1)。当服务器端应用程序运行很长时间(可能是 2 周)时,API 使用的 SignalR 连接失败。与 SignalR 集线器的直接连接(方法 2)继续有效。

以下是通过 API 进行连接的方式:

.NET Core Web API

[Route("~/api/heartbeat")]
[HttpPost]
public async Task SendHeartbeat(nodeId) {
    await SignalRClient.SendHeartbeat(nodeId);
    ...
}

SignalRClient

public static class SignalRClient
{

    private static HubConnection _hubConnection;

    /// <summary>
    /// Static SignalRHub client - to ensure that a single connection to the SignalRHub is re-used,
    /// and to prevent excessive connections that cause SignalR to fail
    /// </summary>
    static SignalRClient()
    {
        string signalRHubUrl = "...someUrl";

        _hubConnection = new HubConnectionBuilder()
        .WithUrl(signalRHubUrl)
        .Build();

        _hubConnection.Closed += async (error) =>
        {
            Log.Error("SignalR hub connection was closed - reconnecting. Error message - " + error.Message);

            await Task.Delay(new Random().Next(0, 5) * 1000);
            try
            {
                Log.Error("About to reconnect");
                await _hubConnection.StartAsync();
                Log.Error("Reconnect now requested");
            }
            catch (Exception ex)
            {
                Log.Error("Failed to restart connection to SignalR hub, following a disconnection: " + ex.Message);
            }
        };

        InitializeConnection();
    }

    private static async void InitializeConnection()
    {
        try
        {
            Log.Information("Checking hub connection status");
            if (_hubConnection.State == HubConnectionState.Disconnected)
            {
                Log.Information($"Starting SignalRClient using signalRHubUrl");
                await _hubConnection.StartAsync();
                Log.Information("SignalRClient started successfully");
            }
        }
        catch (Exception ex)
        {
            Log.Error("Failed to start connection to SignalRClient : " + ex.Message + ", " + ex.InnerException.Message);
        }
    }

    public static async Task SendHeartbeat(string nodeId)
    {
        try
        {
            Log.Information("Attempting to send heartbeat to SignalRHub");
            await _hubConnection.InvokeAsync("SendNodeHeartbeatToMonitors", nodeId);
        }
        catch (Exception ex)
        {
            Log.Error($"Error when sending heartbeat to SignalRClient  for NodeId: {nodeId}. Error: {ex.Message}");
        }
    }

在正常运行大约 2 周后,连接失败并且没有恢复,我可以在日志中看到一个错误:

Error when sending transaction to SignalRClient from /api/heartbeat: The 'InvokeCoreAsync' method cannot be called if the connection is not active

我不明白这是怎么回事,因为我使用SignalRClient 中的_hubConnection.Closed 方法来处理连接关闭时的情况,然后执行await _hubConnection.StartAsync(); 以重新启动连接,如如上面的代码所示。

连接定期由于某种原因被关闭(每 30 分钟一次),但它通常会恢复连接,并且我在日志中看到以下错误:

SignalR hub connection was closed - reconnecting. Error message - The remote party closed the WebSocket connection without completing the close handshake.

这表明代码已成功进入_hubConnection.Closed 方法(因为这是我记录该消息的地方),因此看起来连接通常已成功重新启动。

那么,为什么有时连接完全失败,但又无法重新启动?我想知道我是否以合理的方式连接到 SignalR 集线器(特别是,我想知道为 SignalRClient 使用静态类是否是一个很好的模式)。我想知道我的实际问题是否是所有这些The remote party closed the WebSocket connection without completing the close handshake. 错误?如果是这种情况,可能是什么原因造成的?

非常感谢任何为我指明正确方向的建议。

【问题讨论】:

    标签: c# asp.net asp.net-core asp.net-core-webapi signalr-hub


    【解决方案1】:

    几年前我遇到了同样的问题,当时我通过将所有对 StartAsync 的调用放在他们自己的任务中解决了这个问题。虽然我可能对此有误,但我自己的实验表明 HubConnection 本身不可重用,因此也需要在断开连接后重新创建。

    所以本质上,我有一个名为“CreateHubConnection”的函数,它可以满足您的期望,并且我有一个异步方法来启动服务器连接,如下所示:

    private async Task ConnectToServer()
    {
        // keep trying until we manage to connect
        while (true)
        {
            try
            {
                await CreateHubConnection();
                await this.Connection.StartAsync();
                return; // yay! connected
            }
            catch (Exception e) { /* bugger! */}
        }
    }
    

    我的初始连接在一个新任务中运行它:

    this.Cancel = new CancellationTokenSource();
    Task.Run(async () => await ConnectToServer(), this.Cancel.Token);
    

    而且 Connection.Closed 处理程序也会在新任务中启动它:

    this.Connection.Closed += async () => 
    {
        try
        {
            await Task.Delay(1000); // don't want to hammer the network
            this.Cancel = new CancellationTokenSource();
            await Task.Run(async () => await ConnectToServer(), this.Cancel.Token);
        }
        catch (Exception _e) { /* give up */ }
    }
    

    我不知道为什么这是必要的,但是直接从 Closed 处理程序调用 StartAsync 似乎会在 SignalR 库中创建某种死锁。我从来没有找到确切的原因......这可能是因为我最初对 StartAsync 的调用是由 GUI 线程调用的。将连接放在它们自己的线程中,每次都创建新的 HubConnections,并处理不再需要的旧 HubConnections 来修复它。

    如果对此有更多了解的人有更好/更简单的解决方案,我会非常感兴趣。

    【讨论】:

    • 感谢@Mark Feldman。在详尽的记录和分析之后,我确定死锁几乎肯定是确切的原因。我可以看到实际的连接模式,表明出现了死锁。您的修复工作完美。
    • 这篇文章必须在官方 signalr how-to 文档中进行。我猜为什么这是必要的,是连接在某个时候终止,并且您可能会遇到线程崩溃,例如“System.InvalidOperationException:如果连接不活动,则无法调用'InvokeCoreAsync'方法blabla”并且整个引擎停止.虽然您的解决方案只有一个独立的异步线程崩溃并且引擎继续运行 - 重新连接等。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-22
    • 2020-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-07
    相关资源
    最近更新 更多