SignalR 2.0 .NET 클라이언트를 서버 허브에 다시 연결하는 모범 사례


86

다양한 유형의 연결 해제를 처리해야하는 모바일 애플리케이션에서 .NET 클라이언트와 함께 SignalR 2.0을 사용하고 있습니다. SignalR 클라이언트가 자동으로 다시 연결되는 경우도 있으며, 다시 호출 HubConnection.Start()하여 직접 다시 연결해야하는 경우도 있습니다 .

SignalR이 가끔 마법처럼 자동으로 다시 연결되기 때문에 기능이나 구성 설정이 누락되었는지 궁금합니다.

자동으로 다시 연결되는 클라이언트를 설정하는 가장 좋은 방법은 무엇입니까?


Closed()이벤트 를 처리하고 n 초 후에 연결 하는 자바 스크립트 예제를 보았습니다 . 권장되는 접근 방식이 있습니까?

내가 읽은 문서 와 SignalR 연결의 수명에 대한 여러 기사를,하지만 난 여전히 클라이언트 재 연결을 처리하는 방법에 대한 불분명 해요.

답변:


71

나는 마침내 이것을 알아 냈다. 이 질문을 시작한 이후로 배운 내용은 다음과 같습니다.

배경 : Xamarin / Monotouch 및 .NET SignalR 2.0.3 클라이언트를 사용하여 iOS 앱을 빌드하고 있습니다. 우리는 기본 SignalR 프로토콜을 사용하고 있으며 웹 소켓 대신 SSE를 사용하는 것 같습니다. 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에서 즉시 발생합니다. State 속성은 이러한 재 연결 대기 기간 동안 "연결됨"으로 표시됩니다. 다른 관련 이벤트 나 메서드는 없음)

해결책 : 해결책은 분명합니다. SignalR이 재 연결 마법을 수행하기를 기다리지 않습니다. 대신 앱이 활성화되거나 휴대 전화의 네트워크 연결이 복원 될 때 이벤트를 정리하고 HubConnection을 참조 해제합니다 (30 초가 걸리므로 삭제할 수 없습니다. 가비지 수집이 처리되기를 바랍니다. ) 및 새 인스턴스 생성. 이제 모든 것이 잘 작동합니다. 어떤 이유로 새 인스턴스를 만드는 대신 지속 연결을 다시 사용하고 다시 연결해야한다고 생각했습니다.


5
코드를 게시 하시겠습니까? 어떻게 구성했는지 궁금합니다. Xamarin 앱의 PCL 내에서도 채팅 앱에서 Signalr를 사용하고 있습니다. 전화를 껐다가 다시 켠 후에 재 연결 마법이 작동하지 않는 것 외에는 정말 훌륭하게 작동합니다. IT Crowd가 그게 내가해야 할 전부라고 말했습니다.
Timothy Lee Russell

1
안녕하세요 Ender2050, 일단 안드로이드 장치가 서버에서 연결이 끊긴 후 다시 연결되지 않는다는 것을 알게되었습니다. 그래서 5 분마다 실행되는 알람을 구현하고 서버 허브와의 signalR 연결을 확인했습니다. 알람 틱 이벤트에서 연결 개체가 null인지 확인했습니다. connectionId가 비어 있고 다시 연결을 설정했지만 제대로 작동하지 않습니다. 사용자는 앱을 종료하고 다시 열어야합니다. 나는 안드로이드 용 자바 클라이언트와 서브 허브 용 C # .Net을 사용했다. 이 문제를 해결하기 위해 도움을 찾고 있습니다.
jignesh

1
참고로 Mono에는 웹 소켓이 없습니다. 이것이 Xamarin 앱이 항상 SSE를 사용하는 이유입니다. 콘솔 클라이언트를 작성할 수 있습니다. Mono에서 실행하면 SSE를 사용합니다. Windows에서 실행하는 경우 (7도 웹 소켓을 지원하지 않기 때문에 적어도 Windows 8) 웹 소켓을 사용합니다.
daramasala

@ Ender2050 몇 가지 코드 예제로 솔루션을 확장 할 수 있습니까?
mbx-mbx

"SignalR Java Client 라이브러리"를 사용하는 Android 앱과 "SignalR Object C 라이브러리"를 사용하는 iOS 앱에서 SignalR Hub (SignalR 라이브러리 버전 2.2.2)와 재 연결 문제가 발생했습니다. 두 플랫폼의 클라이언트 라이브러리는 한동안 업데이트되지 않았습니다. 문제는 클라이언트와 서버 간의 SignalR 프로토콜 비 호환성 때문이라고 생각합니다.
Nadim Hossain Sonet

44

연결이 끊긴 이벤트에 타이머를 설정하여 자동으로 다시 연결을 시도하는 것이 내가 아는 유일한 방법입니다.

자바 스크립트에서는 다음과 같이 수행됩니다.

$.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

1
@KingOfHypocrites reconnecting허브가 연결이 끊어지면 시작되는 이벤트를 구독하고 해당 변수 (예 :) 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

.NET 클라이언트를 요구하는 OP 이후 (아래의 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

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

매직 재 연결 문제를 방지하기 위해 재 연결 상태가 시작되기 전에 안드로이드에서 서버 메소드를 호출 할 수 있습니다.

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);
    });
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.