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

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

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

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


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

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



답변

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

배경 : 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 초가 걸리므로 삭제할 수 없습니다. 가비지 수집이 처리되기를 바랍니다. ) 및 새 인스턴스 생성. 이제 모든 것이 잘 작동합니다. 어떤 이유로 새 인스턴스를 만드는 대신 지속 연결을 다시 사용하고 다시 연결해야한다고 생각했습니다.


답변

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

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

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


답변

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


답변

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


답변

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

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


답변