我在移动应用程序中使用SignalR 2.0和.NET客户端,该应用程序需要处理各种类型的断开连接。有时SignalR客户端会自动重新连接,有时必须通过再次调用HubConnection.Start()
直接重新连接。
由于SignalR有时会神奇地自动重新连接,我想知道我是否缺少功能或配置设置
设置自动重新连接的客户端的最佳方法是什么?
我看到过处理Closed()
事件并在n秒后连接的javascript示例。有什么建议的方法吗?
我已经阅读了有关SignalR连接寿命的文档和几篇文章,但我仍然不清楚如何处理客户端重新连接。
我终于明白了。以下是我开始这个问题后学到的东西:
背景:我们正在使用Xamarin/Monotouch和.NET SignalR 2.0.3客户端构建iOS应用程序。我们使用默认的SignalR协议,而且它似乎使用SSE而不是web套接字。我还不确定是否有可能使用Xamarin/Monotouch的网络插座。所有内容都使用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跟踪日志阐明了底层协议的情况,但我认为没有办法在代码中处理传输级别的事件。(Closed()事件在场景B中30秒后触发,在场景C中立即触发;在这些重新连接等待期内,国有资产显示"已连接";无其他相关事件或方法)
解决方案:解决方案是显而易见的。我们不是在等待SignalR实现它的重新连接魔法。相反,当应用程序被激活或手机的网络连接恢复时,我们只需清理事件并取消对HubConnection的引用(无法处理它,因为它需要30秒,希望垃圾收集可以处理它),然后创建一个新实例。现在一切都很顺利。出于某种原因,我认为我们应该重用持久连接并重新连接,而不仅仅是创建一个新实例。
在断开连接的事件上设置计时器以自动尝试重新连接是我所知道的唯一方法。
在javascript中,它是这样做的:
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 5000); // Restart connection after 5 seconds.
});
这是文档中推荐的方法:
http://www.asp.net/signalr/overview/signalr-20/hubs-api/handling-connection-lifetime-events#clientdisconnect
由于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")
}
}
我为ibubi答案添加了一些更新。可能是有人需要它。我发现在某些情况下信号员不会站起来;闭合的";重新连接后的事件已停止。我使用事件";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;
}
重新连接的方法:
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;
}
}
您可以尝试在重新连接状态开始之前从android调用服务器方法,以防止出现神奇的重新连接问题。
SignalR集线器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);
});
}