重新连接SignalR 2.0 .NET客户端到服务器中心的最佳实践

93

我正在移动应用程序中使用带有.NET客户端的SignalR 2.0,需要处理各种类型的断开连接。有时,SignalR客户端会自动重新连接-有时必须通过再次调用HubConnection.Start()来重新连接。

由于SignalR在某些情况下神奇地自动重新连接,我想知道是否缺少功能或配置设置?

最佳方法是如何设置可自动重新连接的客户端?


我看到JavaScript示例可以处理Closed()事件,然后在n秒后进行连接。 是否有推荐的方法?

我已经阅读了文档和几篇关于SignalR连接生命周期的文章,但仍不清楚如何处理客户端重新连接。

5个回答

74

我终于搞明白了。自从开始提出这个问题以来,我学到了以下内容:

背景:我们正在使用Xamarin/Monotouch和.NET SignalR 2.0.3客户端构建一个iOS应用程序。我们使用默认的SignalR协议-似乎是使用SSE而不是WebSockets。我还不确定在Xamarin / Monotouch中是否可以使用WebSockets。所有内容都是使用Azure网站托管的。

我们需要该应用程序能够快速地重新连接到我们的SignalR服务器,但我们一直遇到问题,即连接无法自动重新连接或重新连接恰好需要30秒(由于底层协议超时)。

我们最终测试了三种情况:

情况A - 第一次加载应用程序时连接。从第一天开始就完美地工作。即使使用3G移动连接,连接时间也不到0.25秒钟(假设广播已经开启)。

情况B - 在应用程序闲置/关闭30秒后重新连接到SignalR服务器。在此情况下,SignalR客户端最终会自动重新连接到服务器,无需任何特殊工作-但它似乎会等待恰好30秒才尝试重新连接(对于我们的应用程序来说太慢了)。

在这30秒的等待期间,我们尝试调用HubConnection.Start(),但没有任何效果。而且调用HubConnection.Stop()也需要30秒。我发现了SignalR网站上的相关错误,看起来问题已经得到解决,但我们在v2.0.3中仍然存在同样的问题。

情况C - 在应用程序闲置/关闭120秒或更长时间后重新连接到SignalR服务器。在此情况下,SignalR传输协议已超时,因此客户端永远无法自动重新连接。这就解释了为什么客户端有时会自动重新连接,但并不总是。好消息是,调用HubConnection.Start()几乎立即工作,就像情况A一样。

所以我花了一些时间才意识到,基于应用程序关闭的时长是30秒还是120+秒,重新连接条件是不同的。虽然SignalR跟踪日志阐明了底层协议正在发生什么,但我认为没有办法在代码中处理传输级别事件。(在场景B中,30秒后触发Closed()事件,在场景C中立即触发;在这些重新连接等待期间,State属性显示“Connected”,没有其他相关事件或方法)

解决方案: 解决方案很明显。我们不再等待SignalR的重新连接魔法。相反,当应用程序被激活或手机网络连接恢复时,我们只需清理事件并取消引用HubConnection(不能处置它,因为需要30秒,希望垃圾回收会处理它),并创建一个新实例。现在一切都很好。出于某种原因,我认为我们应该重用持久连接并重新连接,而不仅仅是创建一个新实例。


7
你能否发布一些代码?我想知道你是如何构建它的。我在一个 Xamarin 应用程序中的 PCL 中使用 Signalr 来制作聊天应用程序,效果非常好,但似乎无法在手机被关闭然后重新开启后实现重新连接。我发誓 IT Crowd 说过那就是我需要做的。 - Timothy Lee Russell
1
你好,Ender2050, 我发现一旦Android设备从服务器断开连接后,就再也无法重新连接了。所以我实现了一个闹钟,每隔5分钟运行一次,并检查与服务器中心的SignalR连接。在Alarm计时器事件中,我检查连接对象是否为空或连接ID是否为空,然后重新建立连接。但这并不起作用。用户需要杀死应用程序并重新打开它。 我使用的是java-client用于Android和C#.Net用于服务中心。 希望您能帮助我解决这个问题。 - jignesh
1
FYI,Mono没有WebSocket。这就是为什么你的Xamarin应用程序总是使用SSE。你可以编写一个控制台客户端。如果你在Mono上运行它,它将使用SSE。如果你在Windows上运行它(至少Windows 8,因为7也不支持WebSocket),它将使用WebSocket。 - daramasala
@Ender2050,请问您能否提供一些代码示例来详细说明您的解决方案? - mbx-mbx
我们在使用“SignalR Java客户端库”的Android应用程序和使用“SignalR Object C库”的iOS应用程序中遇到了与SignalR Hub(SignalR库版本2.2.2)的重新连接问题。两个平台上的客户端库已经有一段时间没有更新了。我认为问题是由于客户端和服务器之间的SignalR协议不兼容引起的。 - Nadim Hossain Sonet

49

1
请确保在启动时执行任何启动完成功能,例如重新连接到中心。 - MikeBaz - MSFT
1
我发现使用.NET客户端时,如果在调用hub.Start()之前订阅Closed事件,如果最初连接失败,则会调用您的Closed事件处理程序并尝试再次调用hub.Start(),导致原始的hub.Start()无法完成。我的解决方案是仅在Start()成功后订阅Closed,并在回调中立即取消订阅Closed。 - Oran Dennison
3
@MikeBaz 我想你的意思是重新加入群组。 - Simon_Weaver
1
@KingOfHypocrites 我订阅了 reconnecting 事件,该事件在中心失去连接时触发,并将变量(例如 shouldReconnect)设置为 true。因此,我修改了您的示例以检查该变量。看起来很不错。 - Alisson Reinaldo Silva
2
我产生了一个在10到60秒之间的随机数。我们有太多的客户端,不能只等待5秒钟,否则我们会遭受 DDoS 攻击。$.connection.hub.disconnected(function() { setTimeout(function() { $.connection.hub.start(); }, (Math.floor(Math.random() * 50) + 10) * 1000); }); - Brain2000
显示剩余2条评论

20

因为OP要求使用.NET客户端(以下是Winform实现),

private async Task<bool> ConnectToSignalRServer()
{
    bool connected = false;
    try
    {
        Connection = new HubConnection("server url");
        Hub = Connection.CreateHubProxy("MyHub");
        await Connection.Start();

        //See @Oran Dennison's comment on @KingOfHypocrites's answer
        if (Connection.State == ConnectionState.Connected)
        {
            connected = true;
            Connection.Closed += Connection_Closed;
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
    return connected;
}

private async void Connection_Closed()
{   // A global variable being set in "Form_closing" event 
    // of Form, check if form not closed explicitly to prevent a possible deadlock.
    if(!IsFormClosed) 
    {
        // specify a retry duration
        TimeSpan retryDuration = TimeSpan.FromSeconds(30);
        DateTime retryTill = DateTime.UtcNow.Add(retryDuration);

        while (DateTime.UtcNow < retryTill)
        {
            bool connected = await ConnectToSignalRServer();
            if (connected)
                return;
        }
        Console.WriteLine("Connection closed")
    }
}

我在SignalR 2.3.0中发现,如果我在Closed()事件中等待连接,有时它不会连接。但是,如果我对事件进行手动的Wait()调用,并设置超时时间,例如10秒,它会自动每10秒再次调用Closed(),然后重新连接就可以正常工作了。 - Brain2000

1

我为ibubi的答案添加了一些更新。也许有人需要它。我发现在某些情况下,当重新连接停止时,Signalr不会触发“closed”事件。我使用“StateChanged”事件解决了这个问题。连接到SignalR服务器的方法:

private async Task<bool> ConnectToSignalRServer()
        {
            bool connected = false;
            try
            {
                var connection = new HubConnection(ConnectionUrl);
                var proxy = connection.CreateHubProxy("CurrentData");
                await connection.Start();

                if (connection.State == ConnectionState.Connected)
                {
                    await proxy.Invoke("ConnectStation");

                    connection.Error += (ex) =>
                    {
                        Console.WriteLine("Connection error: " + ex.ToString());
                    };
                    connection.Closed += () =>
                    {
                        Console.WriteLine("Connection closed");
                    };
                    connection.StateChanged += Connection_StateChanged;
                    Console.WriteLine("Server for Current is started.");
                    connected = true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
            return connected;
        }

Method for reconnecting:

private async void Connection_StateChanged(StateChange obj)
        {
            if (obj.NewState == ConnectionState.Disconnected)
            {
                await RestartConnection();
            }
        }

无限尝试连接服务器的方法(我也使用这种方法来创建第一个连接):

public async Task RestartConnection()
        {
            while (!ApplicationClosed)
            {
                bool connected = await ConnectToSignalRServer();
                if (connected)
                    return;
            }
        }

-2
你可以在重新连接状态开始之前,尝试从你的安卓设备调用服务器方法,以防止魔法重新连接问题。 SignalR Hub C#
 public class MyHub : Hub
    {
        public void Ping()
        {
            //ping for android long polling
        }
 }

在Android中

private final int PING_INTERVAL = 10 * 1000;

private boolean isConnected = false;
private HubConnection connection;
private ClientTransport transport;
private HubProxy hubProxy;

private Handler handler = new Handler();
private Runnable ping = new Runnable() {
    @Override
    public void run() {
        if (isConnected) {
            hubProxy.invoke("ping");
            handler.postDelayed(ping, PING_INTERVAL);
        }
    }
};

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    System.setProperty("http.keepAlive", "false");

    .....
    .....

    connection.connected(new Runnable() {
        @Override
        public void run() {
            System.out.println("Connected");
            handler.postDelayed(ping, PING_INTERVAL);
    });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接