重新连接 SignalR 2.0. NET 客户机到服务器集线器的最佳实践

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

由于 SignalR 在某些时候会神奇地自动重新连接,我想知道我是否缺少一个特性或配置设置?

设置自动重新连接的客户端的最佳方式是什么?


我见过一些 javascript 例子,它们处理 Closed()事件,然后在 n 秒后连接。有什么推荐的方法吗?

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

99685 次浏览

Setting a timer on the disconnected event to automatically attempt reconnect is the only method I am aware of.

In javascript it is done like so:

$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 5000); // Restart connection after 5 seconds.
});

This is the recommended approach in the documentation:

http://www.asp.net/signalr/overview/signalr-20/hubs-api/handling-connection-lifetime-events#clientdisconnect

I finally figured this out. Here's what I've learned since starting this question:

Background: We're building an iOS app using Xamarin / Monotouch and the .NET SignalR 2.0.3 client. We're using the default SignalR protocols - and it seems to be using SSE instead of web sockets. I'm not sure yet if it's possible to use web sockets with Xamarin / Monotouch. Everything is hosted using Azure websites.

We needed the app to reconnect to our SignalR server quickly, but we kept having problems where the connection didn't reconnect on its own - or the reconnect took exactly 30 seconds (due to an underlying protocol timeout).

There were three scenarios we ended up testing for:

Scenario A - connecting the first time the app was loaded. This worked flawlessly from day one. The connection completes in less than .25 seconds even over 3G mobile connections. (assuming the radio is already on)

Scenario B - reconnecting to the SignalR server after the app was idle/closed for 30 seconds. In this scenario, the SignalR client will eventually reconnect to the server on its own without any special work - but it seems to wait exactly 30 seconds before attempting to reconnect. (way too slow for our app)

During this 30 second waiting period, we tried calling HubConnection.Start() which had no effect. And calling HubConnection.Stop() also takes 30 seconds. I found a related bug on the SignalR site that appears to be resolved, but we're still having the same problem in v2.0.3.

Scenario C - reconnecting to the SignalR server after the app was idle/closed for 120 seconds or longer. In this scenario, the SignalR transport protocol has already timed out so the client never automatically reconnects. This explains why the client was sometimes but not always reconnecting on its own. The good news is, calling HubConnection.Start() works almost instantly like scenario A.

So it took me a while to realize that the reconnect conditions were different based on whether the app was closed for 30 seconds vs 120+ seconds. And although the SignalR tracing logs illuminate what's going on with the underlying protocol, I don't believe there's a way to handle the transport level events in code. (the Closed() event fires after 30 seconds in scenario B, instantly in scenario C; the State property says "Connected" during these reconnect waiting periods; no other relevant events or methods)

Solution: The solution is obvious. We're not waiting for SignalR to do its reconnection magic. Instead, when the app is activated or when the phone's network connection is restored, we're simply cleaning up the events and de-referencing the HubConnection (can't dispose it because it takes 30 seconds, hopefully garbage collection will take care of it) and creating a new instance. Now everything is working great. For some reason, I thought we should be reusing a persisted connection and reconnecting instead of just creating a new instance.

You might try to invoke server method from your android before reconnect state start to prevent magic reconnect problem.

SignalR Hub C#

 public class MyHub : Hub
{
public void Ping()
{
//ping for android long polling
}
}

In 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);
});
}

Since the OP asking for a .NET client (a winform implementation below),

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")
}
}

I add some update for ibubi answer. May be somebody need it. I found that in some case signalr don't rise "closed" event after reconnecting stopped. I solved it using event "StateChanged". Method which connect to SignalR server:

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();
}
}

Method of endless attempts to connect to the server (Also I use this method for creating the fist connection):

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