正常运行时间过长后,服务器端SignalR连接失败

时间:2020-06-04 05:26:39

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

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

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

  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

我不知道这是怎么发生的,因为我正在使用_hubConnection.Closed中的SignalRClient方法来处理关闭连接后再执行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.错误?如果是这样,可能是什么原因造成的?

任何向我指出正确方向的建议都将受到赞赏。

1 个答案:

答案 0 :(得分: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线程调用的。将连接置于自己的线程中,每次创建新的HubConnection,并处理不再需要的旧HubConnection。

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

相关问题