ASP.NET Core SignalR .NET 클라이언트

ASP.NET Core SignalR .NET 클라이언트 라이브러리를 사용하면 .NET 앱에서 SignalR 허브와 통신할 수 있습니다.

샘플 코드 보기 및 다운로드(다운로드 방법)

이 문서의 코드 샘플은 ASP.NET Core SignalR .NET 클라이언트를 사용하는 WPF 앱입니다.

SignalR .NET 클라이언트 패키지 설치

Microsoft.AspNetCore.SignalR.Client 패키지는 .NET 클라이언트에서 SignalR 허브에 연결하는 데 필요합니다.

클라이언트 라이브러리를 설치하려면 패키지 관리자 콘솔 창에서 다음 명령을 실행합니다.

Install-Package Microsoft.AspNetCore.SignalR.Client

허브에 연결

연결을 설정하려면 HubConnectionBuilder를 만들고 Build를 호출합니다. 연결을 빌드하는 동안 허브 URL, 프로토콜, 전송 유형, 로그 수준, 헤더 및 기타 옵션을 구성할 수 있습니다. 모든 HubConnectionBuilder 메서드를 Build에 삽입하여 필요한 옵션을 구성합니다. StartAsync로 연결을 시작합니다.

using System;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.AspNetCore.SignalR.Client;

namespace SignalRChatClient
{
    public partial class MainWindow : Window
    {
        HubConnection connection;
        public MainWindow()
        {
            InitializeComponent();

            connection = new HubConnectionBuilder()
                .WithUrl("http://localhost:53353/ChatHub")
                .Build();

            connection.Closed += async (error) =>
            {
                await Task.Delay(new Random().Next(0,5) * 1000);
                await connection.StartAsync();
            };
        }

        private async void connectButton_Click(object sender, RoutedEventArgs e)
        {
            connection.On<string, string>("ReceiveMessage", (user, message) =>
            {
                this.Dispatcher.Invoke(() =>
                {
                   var newMessage = $"{user}: {message}";
                   messagesList.Items.Add(newMessage);
                });
            });

            try
            {
                await connection.StartAsync();
                messagesList.Items.Add("Connection started");
                connectButton.IsEnabled = false;
                sendButton.IsEnabled = true;
            }
            catch (Exception ex)
            {
                messagesList.Items.Add(ex.Message);
            }
        }

        private async void sendButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                await connection.InvokeAsync("SendMessage", 
                    userTextBox.Text, messageTextBox.Text);
            }
            catch (Exception ex)
            {                
                messagesList.Items.Add(ex.Message);                
            }
        }
    }
}

손실된 연결 처리

자동으로 다시 연결

HubConnectionBuilder에서 WithAutomaticReconnect 메서드를 사용하여 자동으로 다시 연결되도록 HubConnection을 구성할 수 있습니다. 기본적으로 자동으로 다시 연결되지 않습니다.

HubConnection connection= new HubConnectionBuilder()
    .WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
    .WithAutomaticReconnect()
    .Build();

매개 변수를 사용하지 않으면 WithAutomaticReconnect()는 클라이언트가 각각 0초, 2초, 10초 및 30초를 기다린 후 각 다시 연결 시도를 시도하도록 구성합니다. 네 번의 시도 실패 후 중지됩니다.

다시 연결 시도를 시작하기 전에 HubConnectionHubConnectionState.Reconnecting 상태로 전환되고 Reconnecting 이벤트를 발생시킵니다. 이렇게 하면 연결이 손실되었음을 사용자에게 경고하고 UI 요소를 사용하지 않도록 설정할 수 있습니다. 비 대화형 앱은 메시지 큐 또는 삭제를 시작할 수 있습니다.

connection.Reconnecting += error =>
{
    Debug.Assert(connection.State == HubConnectionState.Reconnecting);

    // Notify users the connection was lost and the client is reconnecting.
    // Start queuing or dropping messages.

    return Task.CompletedTask;
};

클라이언트가 처음 네 번의 시도 내에서 성공적으로 다시 연결되면 HubConnectionConnected 상태로 다시 전환되고 Reconnected 이벤트를 발생시킵니다. 이렇게 하면 연결이 다시 설정되었음을 사용자에게 알리고 대기 중인 메시지의 큐를 해제할 수 있습니다.

연결은 서버에 완전히 새로운 것으로 보이므로 Reconnected 이벤트 처리기에 새 ConnectionId가 제공됩니다.

Warning

HubConnection협상 건너뛰기로 구성된 경우 Reconnected 이벤트 처리기의 connectionId 매개 변수는 Null이 됩니다.

connection.Reconnected += connectionId =>
{
    Debug.Assert(connection.State == HubConnectionState.Connected);

    // Notify users the connection was reestablished.
    // Start dequeuing messages queued while reconnecting if any.

    return Task.CompletedTask;
};

WithAutomaticReconnect()는 초기 시작 오류를 다시 시도하도록 HubConnection을 구성하지 않으므로 시작 실패를 수동으로 처리해야 합니다.

public static async Task<bool> ConnectWithRetryAsync(HubConnection connection, CancellationToken token)
{
    // Keep trying to until we can start or the token is canceled.
    while (true)
    {
        try
        {
            await connection.StartAsync(token);
            Debug.Assert(connection.State == HubConnectionState.Connected);
            return true;
        }
        catch when (token.IsCancellationRequested)
        {
            return false;
        }
        catch
        {
            // Failed to connect, trying again in 5000 ms.
            Debug.Assert(connection.State == HubConnectionState.Disconnected);
            await Task.Delay(5000);
        }
    }
}

클라이언트가 처음 네 번의 시도 내에서 성공적으로 다시 연결되지 않으면 HubConnectionDisconnected 상태로 전환되고 Closed 이벤트를 발생시킵니다. 이를 통해 연결을 수동으로 다시 시작하거나 연결이 영구적으로 손실되었음을 사용자에게 알릴 수 있습니다.

connection.Closed += error =>
{
    Debug.Assert(connection.State == HubConnectionState.Disconnected);

    // Notify users the connection has been closed or manually try to restart the connection.

    return Task.CompletedTask;
};

연결을 끊거나 다시 연결 시간을 변경하기 전에 사용자 지정 다시 연결 시도 횟수를 구성하기 위해 WithAutomaticReconnect는 각 다시 연결 시도를 시작하기 전에 대기할 지연 시간(밀리초)을 나타내는 일련의 숫자를 수락합니다.

HubConnection connection= new HubConnectionBuilder()
    .WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
    .WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.Zero, TimeSpan.FromSeconds(10) })
    .Build();

    // .WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30) }) yields the default behavior.

앞의 예제에서는 연결이 끊긴 후 즉시 다시 연결 시도를 시작하도록 HubConnection을 구성합니다. 기본 구성의 경우에도 마찬가지입니다.

첫 번째 다시 연결 시도가 실패하면 두 번째 다시 연결 시도도 기본 구성에서와 같이 2초 동안 대기하는 대신 즉시 시작됩니다.

두 번째 다시 연결 시도가 실패하면 세 번째 다시 연결 시도는 기본 구성처럼 10초 후에 시작됩니다.

그런 다음, 세 번째 다시 연결 시도가 실패한 후 사용자 지정 동작을 중지하여 기본 동작에서 다시 벗어납니다. 기본 구성에서는 30초 내에 다시 한 번 더 다시 연결하려고 시도합니다.

자동 다시 연결 시도의 타이밍과 수를 더 많이 제어하려는 경우 WithAutomaticReconnectNextRetryDelay라는 단일 메서드가 있는 IRetryPolicy 인터페이스를 구현하는 개체를 허용합니다.

NextRetryDelayRetryContext 형식의 단일 인수를 사용합니다. RetryContext에는 각각 long, TimeSpanException인 세 가지 속성 PreviousRetryCount, ElapsedTimeRetryReason이 있습니다. 첫 번째 다시 연결 시도 전에 PreviousRetryCountElapsedTime은 모두 0이 되고 RetryReason은 연결이 끊어지도록 하는 예외가 됩니다. 실패한 각 재시도 후에는 PreviousRetryCount가 1씩 증가하고, 지금까지 다시 연결하는 데 걸린 시간을 반영하도록 ElapsedTime이 업데이트되며, RetryReason은 마지막 다시 연결 시도의 실패를 초래한 예외가 됩니다.

HubConnection이 다시 연결을 중지해야 하는 경우 NextRetryDelay는 다음 다시 연결 시도까지 대기할 시간을 나타내는 TimeSpan 또는 null을 반환해야 합니다.

public class RandomRetryPolicy : IRetryPolicy
{
    private readonly Random _random = new Random();

    public TimeSpan? NextRetryDelay(RetryContext retryContext)
    {
        // If we've been reconnecting for less than 60 seconds so far,
        // wait between 0 and 10 seconds before the next reconnect attempt.
        if (retryContext.ElapsedTime < TimeSpan.FromSeconds(60))
        {
            return TimeSpan.FromSeconds(_random.NextDouble() * 10);
        }
        else
        {
            // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
            return null;
        }
    }
}
HubConnection connection = new HubConnectionBuilder()
    .WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
    .WithAutomaticReconnect(new RandomRetryPolicy())
    .Build();

또는 수동으로 다시 연결에 설명된 대로 클라이언트를 수동으로 다시 연결하는 코드를 작성할 수 있습니다.

수동으로 다시 연결

Warning

3.0 이전에는 SignalR용 .NET 클라이언트가 자동으로 다시 연결되지 않습니다. 클라이언트를 수동으로 다시 연결하는 코드를 작성해야 합니다.

Closed 이벤트를 사용하여 손실된 연결에 응답합니다. 예를 들어 다시 연결을 자동화할 수 있습니다.

Closed 이벤트에는 Task를 반환하는 대리자가 필요합니다. async void를 사용하지 않고 비동기 코드를 실행할 수 있습니다. 동기적으로 실행되는 Closed 이벤트 처리기에서 대리자 시그니처를 만족시키려면 Task.CompletedTask를 반환합니다.

connection.Closed += (error) => {
    // Do your close logic.
    return Task.CompletedTask;
};

비동기 지원을 위한 주요 이유는 연결을 다시 시작할 수 있도록 하는 것입니다. 연결 시작은 비동기 작업입니다.

연결을 다시 시작하는 Closed 처리기에서 다음 예제와 같이 서버 오버로드를 방지하기 위해 임의 지연이 발생할 때까지 대기하는 것이 좋습니다.

connection.Closed += async (error) =>
{
    await Task.Delay(new Random().Next(0,5) * 1000);
    await connection.StartAsync();
};

클라이언트에서 허브 메서드 호출

InvokeAsync는 허브에서 메서드를 호출합니다. 허브 메서드에 정의된 허브 메서드 이름 및 인수를 InvokeAsync에 전달합니다. SignalR은 비동기이므로 호출하는 경우 asyncawait를 사용합니다.

await connection.InvokeAsync("SendMessage", 
    userTextBox.Text, messageTextBox.Text);

InvokeAsync 메서드는 서버 메서드가 반환될 때 완료되는 Task를 반환합니다. 반환 값(있는 경우)은 Task의 결과로 제공됩니다. 서버에서 메서드에 의해 throw되는 모든 예외는 오류 Task를 생성합니다. await 구문을 사용하여 서버 메서드가 완료될 때까지 기다리거나 try...catch 구문을 사용하여 오류를 처리합니다.

SendAsync 메서드는 메시지가 서버로 전송될 때 완료되는 Task를 반환합니다. 이 Task는 서버 메서드가 완료될 때까지 기다리지 않으므로 반환 값이 제공되지 않습니다. 메시지를 보내는 동안 클라이언트에서 throw되는 모든 예외는 오류 Task를 생성합니다. awaittry...catch 구문을 사용하여 보내기 오류를 처리합니다.

참고 항목

클라이언트에서 허브 메서드를 호출하는 것은 기본 모드에서 Azure Service를 사용하는 경우에만 지원됩니다.SignalR 자세한 내용은 질문과 대답(azure-signalr GitHub 리포지토리)을 참조하세요.

허브에서 클라이언트 메서드 호출

빌드한 후 연결을 시작하기 전에 connection.On을 사용하여 허브에서 호출하는 메서드를 정의합니다.

connection.On<string, string>("ReceiveMessage", (user, message) =>
{
    this.Dispatcher.Invoke(() =>
    {
       var newMessage = $"{user}: {message}";
       messagesList.Items.Add(newMessage);
    });
});

connection.On의 이전 코드는 서버 쪽 코드가 SendAsync 메서드를 사용하여 호출할 때 실행됩니다.

public async Task SendMessage(string user, string message)
{
    await Clients.All.SendAsync("ReceiveMessage", user,message);
}

참고 항목

연결의 허브 쪽에서 강력한 형식의 메시징을 지원하지만 클라이언트는 메서드 이름으로 제네릭 메서드 HubConnection.On을 사용하여 등록해야 합니다. 예제는 백그라운드 서비스의 호스트 ASP.NET Core SignalR를 참조하세요.

오류 처리 및 로깅

try-catch 문으로 오류를 처리합니다. Exception 개체를 검사하여 오류가 발생한 후 수행할 적절한 작업을 결정합니다.

try
{
    await connection.InvokeAsync("SendMessage", 
        userTextBox.Text, messageTextBox.Text);
}
catch (Exception ex)
{                
    messagesList.Items.Add(ex.Message);                
}

추가 리소스