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

时间:2014-04-29 20:53:38

标签: c# .net signalr-hub signalr.client signalr-2

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

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

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


我见过javascript示例处理Closed()事件,然后在n秒后连接。有没有推荐的方法?

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

6 个答案:

答案 0 :(得分:63)

我终于弄明白了。这是我自开始提出这个问题以来所学到的:

背景:我们正在使用Xamarin / Monotouch和.NET SignalR 2.0.3客户端构建iOS应用。我们使用默认的SignalR协议 - 它似乎使用SSE而不是Web套接字。我还不确定是否可以使用带有Xamarin / Monotouch的网络插座。一切都使用Azure网站托管。

我们需要应用程序快速重新连接到我们的SignalR服务器,但是我们一直遇到连接没有自行重新连接的问题 - 或者重新连接只花了30秒(由于底层协议超时)。 / p>

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

场景A - 第一次加载应用时连接。从第一天开始就完美无缺。即使通过3G移动连接,连接也可在不到0.25秒的时间内完成。 (假设收音机已经开启)

场景B - 在应用程序空闲/关闭30秒后重新连接到SignalR服务器。在这种情况下,SignalR客户端最终将自行重新连接到服务器,而无需任何特殊工作 - 但它似乎在尝试重新连接之前等待了30秒。 (对我们的应用来说太慢了)

在这30秒的等待期间,我们尝试调用没有效果的HubConnection.Start()。并且调用HubConnection.Stop()也需要30秒。我找到a related bug on the SignalR site that appears to be resolved,但我们仍然在v2.0.3中遇到同样的问题。

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

因此我花了一段时间才意识到重新连接条件因应用程序是否关闭30秒而不是120秒而不同。虽然SignalR跟踪日志说明了底层协议的用途,但我不相信有一种方法可以处理代码中的传输级别事件。 (Closed()事件在方案B中30秒后触发,在方案C中立即触发; State属性显示"已连接"在这些重新连接等待期间;没有其他相关事件或方法)

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

答案 1 :(得分:39)

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

在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

答案 2 :(得分:10)

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

答案 3 :(得分:0)

Multiple query string
self.hubConnection = $.hubConnection();
self.hubConnection.qs = { param1: value, param2: value };

来自:

Signalr - Can't read query strings on server

答案 4 :(得分: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;
            }
        }

答案 5 :(得分:-2)

您可能会尝试在重新连接状态启动之前从您的android调用服务器方法,以防止魔法重新连接问题。

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