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


86

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

由于SignalR有时会神奇地自动重新连接,因此我想知道是否缺少功能或配置设置?

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


我看过处理Closed()事件的javascript示例,然后在n秒后连接。有什么建议的方法吗?

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


您可以共享一些代码吗?
Pxaml

Answers:


71

我终于想通了。这是我开始提出此问题后所学到的:

背景:我们正在使用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跟踪日志阐明了底层协议的功能,但我不相信有一种方法可以处理代码中的传输级别事件。(在方案B中30秒后立即在方案C中触发Closed()事件;在这些重新连接等待期间,State属性显示为“已连接”;没有其他相关事件或方法)

解决方案: 解决方案很明显。我们不等待SignalR做其重新连接魔术。相反,当应用程序被激活或手机的网络连接恢复时,我们只是清除事件并取消引用HubConnection(无法处理它,因为它需要30秒钟,希望垃圾收集可以解决它。 )并创建一个新实例。现在一切正常。由于某些原因,我认为我们应该重用持久连接并重新连接,而不是仅仅创建一个新实例。


5
您愿意发布一些代码吗?只是对您的结构感到好奇。我也在Xamarin应用程序的PCL内部的聊天应用程序中使用Signalr。它的确运行得很好,只是在关闭手机后再打开后我似乎无法使重新连接魔术正常工作。我发誓,IT人群说那是我要做的。
Timothy Lee Russell

1
嗨Ender2050,我注意到一旦android设备从服务器断开然后再也没有重新连接。所以我实现了每5分钟运行一次的警报,并检查与服务器集线器的signalR连接。在警报滴答事件中,我检查了连接对象是否为null或connectionId为空然后重新建立连接。但这无法正常工作。用户需要杀死该应用程序并重新打开它。我已经为Android使用Java客户端,为服务中心使用了C#.Net。寻找您的帮助以解决此问题。
jignesh

1
仅供参考,Mono没有网络套接字。这就是Xamarin应用程序始终使用SSE的原因。您可以编写一个控制台客户端。如果您在Mono上运行它,它将使用SSE。如果在Windows上运行它(至少Windows 8,因为7也不支持Web套接字),它将使用Web套接字。
daramasala

@ Ender2050请问您可以通过一些代码示例来扩展您的解决方案吗?
马朗斯

我们遇到了使用“ SignalR Java客户端库”的Android App和使用“ SignalR Object C库”的iOS App的SignalR Hub(SignalR库版本2.2.2)重新连接的问题。这两个平台上的客户端库已有一段时间没有更新。我认为问题是由于客户端和服务器之间的SignalR协议不兼容造成的。
纳迪姆·侯赛因·索内特

44

我知道,在断开连接的事件上设置计时器以自动尝试重新连接是唯一的方法。

在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


1
一个提示-确保您在启动时就完成了启动的所有功能,否则可能会重新连接到集线器。
MikeBaz-MSFT 2014年

1
我发现在.NET客户端中,如果您调用hub.Start()之前预订了Closed事件,如果最初连接失败,则将调用Closed事件处理程序,然后尝试再次调用hub.Start() ,导致原始hub.Start()无法完成。我的解决方案是仅在Start()成功后订阅Closed,然后在回调中立即取消订阅Closed。
Oran Dennison 2014年

3
@MikeBaz我认为您的意思是重新连接组
Simon_Weaver 2015年

1
我订阅了@KingOfHypocritesreconnecting事件,一旦集线器失去连接并将该变量(例如shouldReconnect)设置为true ,就会触发该事件。因此,我修改了您的示例以检查该变量。看起来很好。
Alisson

2
我在10到60秒之间做了一个随机数。我们有太多的客户,只花了5秒钟,我们会自己进行DDoS。$ .connection.hub.disconnected(function(){setTimeout(function(){$ .connection.hub.start();},(Math.floor(Math.random()* 50)+ 10)* 1000); });
Brain2000

17

由于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()事件中等待连接,它有时将无法连接。但是,如果我在超时的事件(例如10秒)上调用了手动Wait(),它将每隔10秒一次又一次自动调用Closed(),然后重新连接就可以了。
Brain2000 '18

0

我为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;
            }
        }

-3

您可能会尝试在重新连接状态开始之前从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);
    });
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.