Phương pháp hay nhất để kết nối lại máy khách SignalR 2.0 .NET với trung tâm máy chủ


86

Tôi đang sử dụng SignalR 2.0 với ứng dụng khách .NET trong một ứng dụng di động cần xử lý nhiều loại ngắt kết nối khác nhau. Đôi khi máy khách SignalR tự động kết nối lại - và đôi khi nó phải được kết nối lại trực tiếp bằng cách gọi HubConnection.Start()lại.

Vì SignalR đôi khi tự động kết nối lại một cách kỳ diệu, tôi tự hỏi liệu mình có thiếu tính năng hoặc cài đặt cấu hình không?

Cách tốt nhất để thiết lập ứng dụng tự động kết nối lại là gì?


Tôi đã thấy các ví dụ javascript xử lý Closed()sự kiện và sau đó Kết nối sau n-giây. Có cách tiếp cận nào được khuyến nghị không?

Tôi đã đọc tài liệu và một số bài viết về vòng đời của kết nối SignalR, nhưng tôi vẫn chưa rõ về cách xử lý kết nối lại máy khách.


Bạn có thể chia sẻ một số mã?
Pxaml

Câu trả lời:


71

Cuối cùng tôi đã tìm ra điều này. Đây là những gì tôi đã học được kể từ khi bắt đầu câu hỏi này:

Thông tin cơ bản: Chúng tôi đang xây dựng một ứng dụng iOS bằng Xamarin / Monotouch và ứng dụng khách .NET SignalR 2.0.3. Chúng tôi đang sử dụng các giao thức SignalR mặc định - và có vẻ như nó đang sử dụng SSE thay vì các ổ cắm web. Tôi không chắc liệu có thể sử dụng các ổ cắm web với Xamarin / Monotouch hay không. Mọi thứ được lưu trữ bằng các trang web Azure.

Chúng tôi cần ứng dụng nhanh chóng kết nối lại với máy chủ SignalR của mình, nhưng chúng tôi vẫn gặp sự cố trong đó kết nối không tự kết nối lại - hoặc quá trình kết nối lại mất đúng 30 giây (do giao thức cơ bản hết thời gian chờ).

Có ba tình huống mà chúng tôi đã kết thúc thử nghiệm:

Tình huống A - kết nối lần đầu tiên ứng dụng được tải. Điều này hoạt động hoàn hảo ngay từ ngày đầu tiên. Kết nối hoàn tất trong vòng chưa đầy 0,25 giây ngay cả trên kết nối di động 3G. (giả sử đài đã được bật)

Tình huống B - kết nối lại với máy chủ SignalR sau khi ứng dụng không hoạt động / đóng trong 30 giây. Trong trường hợp này, máy khách SignalR cuối cùng sẽ tự kết nối lại với máy chủ mà không cần thực hiện bất kỳ thao tác đặc biệt nào - nhưng dường như phải đợi chính xác 30 giây trước khi cố gắng kết nối lại. (quá chậm đối với ứng dụng của chúng tôi)

Trong khoảng thời gian chờ 30 giây này, chúng tôi đã thử gọi HubConnection.Start () mà không có tác dụng. Và việc gọi HubConnection.Stop () cũng mất 30 giây. Tôi đã tìm thấy một lỗi liên quan trên trang SignalR dường như đã được giải quyết , nhưng chúng tôi vẫn gặp sự cố tương tự trong v2.0.3.

Tình huống C - kết nối lại với máy chủ SignalR sau khi ứng dụng không hoạt động / đóng trong 120 giây hoặc lâu hơn. Trong trường hợp này, giao thức truyền tải SignalR đã hết thời gian chờ nên máy khách không bao giờ tự động kết nối lại. Điều này giải thích tại sao khách hàng đôi khi nhưng không phải lúc nào cũng tự kết nối lại. Tin tốt là việc gọi HubConnection.Start () hoạt động gần như ngay lập tức giống như kịch bản A.

Vì vậy, tôi đã mất một lúc để nhận ra rằng các điều kiện kết nối lại khác nhau dựa trên việc ứng dụng được đóng trong 30 giây so với 120 giây trở lên. Và mặc dù nhật ký theo dõi SignalR chiếu sáng những gì đang xảy ra với giao thức cơ bản, tôi không tin rằng có cách xử lý các sự kiện cấp độ truyền tải trong mã. (sự kiện Closed () kích hoạt sau 30 giây trong kịch bản B, ngay lập tức trong kịch bản C; thuộc tính Trạng thái cho biết "Đã kết nối" trong các khoảng thời gian chờ kết nối lại này; không có sự kiện hoặc phương thức liên quan nào khác)

Giải pháp: Giải pháp là hiển nhiên. Chúng tôi không chờ SignalR thực hiện phép thuật kết nối lại. Thay vào đó, khi ứng dụng được kích hoạt hoặc khi kết nối mạng của điện thoại được khôi phục, chúng tôi chỉ cần dọn dẹp các sự kiện và hủy tham chiếu HubConnection (không thể loại bỏ nó vì nó mất 30 giây, hy vọng việc thu gom rác sẽ xử lý được nó ) và tạo một phiên bản mới. Bây giờ mọi thứ đang hoạt động tuyệt vời. Vì lý do nào đó, tôi nghĩ chúng ta nên sử dụng lại một kết nối lâu dài và kết nối lại thay vì chỉ tạo một phiên bản mới.


5
Bạn có sẵn sàng đăng một số mã không? Chỉ tò mò về cách bạn cấu trúc nó. Tôi cũng đang sử dụng Signalr trong ứng dụng trò chuyện từ PCL trong ứng dụng Xamarin. Nó hoạt động thực sự tuyệt vời ngoại trừ tôi dường như không thể làm cho phép thuật kết nối lại hoạt động sau khi điện thoại đã tắt và bật lại. Tôi thề là Đám đông CNTT nói rằng đó là tất cả những gì tôi phải làm.
Timothy Lee Russell

1
Xin chào Ender2050, tôi đã nhận thấy rằng sau khi thiết bị Android ngắt kết nối khỏi máy chủ thì không bao giờ kết nối lại được nữa. Vì vậy, tôi đã triển khai cảnh báo chạy cứ sau 5 phút và kiểm tra kết nối signalR với trung tâm máy chủ. connectionId trống sau đó thiết lập lại kết nối nhưng cách này không hoạt động tốt, người dùng cần phải hủy ứng dụng và mở lại. Tôi đã sử dụng java-client cho android và C # .Net cho trung tâm phục vụ. Tìm kiếm sự giúp đỡ của bạn để giải quyết vấn đề này.
jignesh

1
FYI, Mono không có ổ cắm web. Đây là lý do tại sao các ứng dụng Xamarin của bạn luôn sử dụng SSE. Bạn có thể viết một ứng dụng khách bảng điều khiển. Nếu bạn chạy nó trên Mono, nó sẽ sử dụng SSE. Nếu bạn chạy nó trên Windows (ít nhất là Windows 8 vì 7 cũng không hỗ trợ web socket), nó sẽ sử dụng web socket.
daramasala

@ Ender2050 Vui lòng bạn có thể mở rộng giải pháp của mình với một số ví dụ về mã không?
Magrangs

Chúng tôi đang gặp sự cố kết nối lại với SignalR Hub (thư viện SignalR phiên bản 2.2.2) từ Ứng dụng Android đang sử dụng "Thư viện ứng dụng khách SignalR Java" và Ứng dụng iOS đang sử dụng "thư viện SignalR Object C". Các thư viện máy khách trên cả hai nền tảng đã không được cập nhật trong một thời gian. Tôi nghĩ rằng vấn đề là do giao thức SignalR không tương thích giữa máy khách và máy chủ.
Nadim Hossain Sonet

44

Đặt bộ hẹn giờ cho sự kiện bị ngắt kết nối để tự động thử kết nối lại là phương pháp duy nhất tôi biết.

Trong javascript nó được thực hiện như vậy:

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

Đây là cách tiếp cận được đề xuất trong tài liệu:

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


1
một gợi ý - hãy đảm bảo rằng bạn thực hiện bất kỳ chức năng hoàn chỉnh nào khi bắt đầu vì nếu không bạn sẽ kết nối lại với các trung tâm.
MikeBaz - MSFT

1
Tôi nhận thấy rằng với ứng dụng khách .NET, nếu bạn đăng ký sự kiện đã đóng trước khi gọi hub.Start (), nếu ban đầu không kết nối được thì trình xử lý sự kiện đã đóng của bạn sẽ được gọi và cố gắng gọi lại hub.Start () , khiến hub.Start () ban đầu không bao giờ hoàn tất. Giải pháp của tôi là chỉ đăng ký Đóng sau khi Bắt đầu () thành công và hủy đăng ký Đã đóng ngay lập tức trong lần gọi lại.
Oran Dennison

3
@MikeBaz Tôi nghĩ bạn muốn kết nối lại với các nhóm
Simon_Weaver,

1
@KingOfHypocrites Tôi đã đăng ký reconnectingsự kiện, sự kiện này sẽ được kích hoạt khi trung tâm mất kết nối và đặt biến đó (ví dụ shouldReconnect:) thành true. Vì vậy, tôi đã điều chỉnh ví dụ của bạn để kiểm tra biến đó. Nó trông đẹp.
Alisson

2
Tôi đã làm một số ngẫu nhiên trong khoảng từ 10 đến 60 giây. Chúng tôi có quá nhiều khách hàng nên chỉ cần đặt 5 giây, chúng tôi sẽ tự DDoS. $ .connection.hub.disconnected (function () {setTimeout (function () {$ .connection.hub.start ();}, (Math.floor (Math.random () * 50) + 10) * 1000); });
Brain2000

17

Vì OP yêu cầu ứng dụng khách .NET (triển khai winform bên dưới),

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

Tôi thấy trong SignalR 2.3.0 rằng nếu tôi đợi kết nối trong sự kiện Closed () thì đôi khi nó sẽ không kết nối. Tuy nhiên, nếu tôi gọi thủ công Wait () trên sự kiện có thời gian chờ, chẳng hạn như 10 giây, nó sẽ tự động gọi lại Closed () sau mỗi 10 giây và sau đó kết nối lại sẽ hoạt động.
Brain2000

0

Tôi thêm một số cập nhật cho câu trả lời ibubi . Có thể ai đó cần nó. Tôi thấy rằng trong một số trường hợp, signalr không tăng sự kiện "đã đóng" sau khi dừng kết nối lại. Tôi đã giải quyết nó bằng cách sử dụng sự kiện "StateChanged". Phương thức kết nối với máy chủ 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;
        }

Phương pháp kết nối lại:

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

Phương pháp cố gắng vô tận để kết nối với máy chủ (Ngoài ra, tôi sử dụng phương pháp này để tạo kết nối nắm tay):

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

-3

Bạn có thể thử gọi phương thức máy chủ từ Android của mình trước khi bắt đầu trạng thái kết nối lại để ngăn sự cố kết nối lại ma thuật.

Trung tâm SignalR C #

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

Trong 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);
    });
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.