ASP.NET Core SignalR JavaScript 클라이언트

작성자: Rachel Appel

ASP.NET Core SignalR JavaScript 클라이언트 라이브러리를 사용하면 개발자가 서버 쪽 SignalR 허브 코드를 호출할 수 있습니다.

SignalR 클라이언트 패키지를 설치합니다.

SignalR JavaScript 클라이언트 라이브러리는 npm 패키지로 전달됩니다. 다음 섹션에서는 클라이언트 라이브러리를 설치하는 다양한 방법을 간략하게 설명합니다.

npm을 사용하여 설치

패키지 관리자 콘솔에서 다음 명령을 실행합니다.

npm init -y
npm install @microsoft/signalr

npm은 node_modules\@microsoft\signalr\dist\browser 폴더에 패키지 콘텐츠를 설치합니다. wwwroot/lib/signalr 폴더를 만듭니다. signalr.js 파일을 wwwroot\lib\signalr 폴더에 복사합니다.

<script> 요소에서 SignalR JavaScript 클라이언트를 참조합니다. 예시:

<script src="~/lib/signalr/signalr.js"></script>

CDN(콘텐츠 배달 네트워크) 사용

npm 필수 조건 없이 클라이언트 라이브러리를 사용하려면 클라이언트 라이브러리의 CDN 호스팅 복사본을 참조합니다. 예시:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>

클라이언트 라이브러리는 다음 CDN에서 사용할 수 있습니다.

LibMan을 통해 설치

LibMan은 CDN 호스팅 클라이언트 라이브러리에서 특정 클라이언트 라이브러리 파일을 설치하는 데 사용할 수 있습니다. 예를 들어 축소된 JavaScript 파일만 프로젝트에 추가합니다. 이 접근 방식에 대한 자세한 내용은 SignalR 클라이언트 라이브러리 추가를 참조하세요.

허브에 연결

다음 코드는 연결을 만들고 시작합니다. 허브의 이름은 대소문자를 구분하지 않습니다.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Start the connection.
start();

원본 간 연결(CORS)

일반적으로 브라우저는 요청된 페이지와 동일한 도메인에서 연결을 로드합니다. 그러나 다른 도메인에 대한 연결이 필요한 경우도 있습니다.

도메인 간 요청을 할 때 클라이언트 코드는 상대 URL 대신 절대 URL을 사용해야 합니다. 도메인 간 요청의 경우 .withUrl("/chathub").withUrl("https://{App domain name}/chathub")로 변경합니다.

악의적인 사이트가 다른 사이트에서 중요한 데이터를 읽지 못하도록 하려면 기본적으로 원본 간 연결은 사용하지 않도록 설정됩니다. 원본 간 요청을 허용하려면 CORS를 사용하도록 설정합니다.

using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        builder =>
        {
            builder.WithOrigins("https://example.com")
                .AllowAnyHeader()
                .WithMethods("GET", "POST")
                .AllowCredentials();
        });
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

// UseCors must be called before MapHub.
app.UseCors();

app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");

app.Run();

MapHub를 호출하기 전에 UseCors를 호출해야 합니다.

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

JavaScript 클라이언트는 HubConnectioninvoke 메서드를 통해 허브에서 공용 메서드를 호출합니다. invoke 메서드는 다음을 허용합니다.

  • 허브 메서드의 이름.
  • 허브 메서드에 정의된 인수.

다음 강조 표시된 코드에서 허브의 메서드 이름은 SendMessage입니다. invoke에 전달된 두 번째 및 세 번째 인수는 허브 메서드의 usermessage 인수에 매핑됩니다.

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

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

invoke 메서드는 JavaScript Promise를 반환합니다. Promise는 서버의 메서드가 반환될 때 반환 값(있는 경우)으로 확인됩니다. 서버의 메서드가 오류를 throw하는 경우 Promise는 오류 메시지와 함께 거부됩니다. asyncawait 또는 Promisethencatch 메서드를 사용하여 이러한 사례를 처리합니다.

JavaScript 클라이언트는 HubConnectionsend 메서드를 통해 허브에서 공용 메서드를 호출합니다. invoke 메서드와 달리 send 메서드는 서버의 응답을 기다리지 않습니다. send 메서드는 JavaScript Promise를 반환합니다. Promise는 메시지가 서버로 전송될 때 확인됩니다. 메시지를 보내는 동안 오류가 발생하면 오류 메시지와 함께 Promise가 거부됩니다. asyncawait 또는 Promisethencatch 메서드를 사용하여 이러한 사례를 처리합니다.

send를 사용하면 서버에서 메시지를 받을 때까지 기다리지 않습니다. 따라서 서버에서 데이터 또는 오류를 반환할 수 없습니다.

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

허브에서 메시지를 받으려면 HubConnectionon 메서드를 사용하여 메서드를 정의합니다.

  • JavaScript 클라이언트 메서드의 이름.
  • 허브가 메서드에 전달하는 인수.

다음 예제에서 메서드 이름은 ReceiveMessage입니다. 인수 이름은 usermessage입니다.

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

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

using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs;

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

SignalR은 SendAsyncconnection.on에 정의된 메서드 이름과 인수를 일치시켜 호출할 클라이언트 메서드를 결정합니다.

가장 좋은 방법on 다음 HubConnection에서 start 메서드를 호출하는 것입니다. 이렇게 하면 메시지를 받기 전에 처리기가 등록됩니다.

오류 처리 및 로깅

클라이언트에서 메시지를 연결하거나 보낼 수 없는 경우 console.error을 사용하여 브라우저 콘솔에 오류를 출력합니다.

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

연결이 설정되면 기록할 이벤트 유형 및 로거를 전달하여 클라이언트 쪽 로그 추적을 설정합니다. 메시지는 지정된 로그 수준 이상으로 기록됩니다. 사용 가능한 로그 수준은 다음과 같습니다.

  • signalR.LogLevel.Error: 오류 메시지. Error 메시지만 기록합니다.
  • signalR.LogLevel.Warning: 잠재적 오류에 대한 경고 메시지입니다. WarningError 메시지를 기록합니다.
  • signalR.LogLevel.Information: 오류 없는 상태 메시지입니다. Information, WarningError 메시지를 기록합니다.
  • signalR.LogLevel.Trace: 추적 메시지. 허브와 클라이언트 간에 전송되는 데이터를 포함하여 모든 것을 기록합니다.

HubConnectionBuilderconfigureLogging 메서드를 사용하여 로그 수준을 구성합니다. 메시지가 브라우저 콘솔에 기록됩니다.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

클라이언트 다시 연결

자동으로 다시 연결

HubConnectionBuilderWithAutomaticReconnect 메서드를 사용하여 자동으로 다시 연결하도록 SignalR에 대한 JavaScript 클라이언트를 구성할 수 있습니다. 기본적으로 자동으로 다시 연결되지 않습니다.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

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

다시 연결 시도를 시작하기 전에 HubConnection에서 다음을 수행합니다.

  • HubConnectionState.Reconnecting 상태로 전환하고 onreconnecting 콜백을 실행합니다.
  • Disconnected 상태로 전환하지 않고 자동 다시 연결이 구성되지 않은 HubConnection와 같은 onclose 콜백을 트리거합니다.

다시 연결 방법은 다음을 수행할 수 있는 기회를 제공합니다.

  • 연결이 끊어졌는지 사용자에게 경고합니다.
  • UI 요소를 사용하지 않도록 설정합니다.
connection.onreconnecting(error => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messageList").appendChild(li);
});

클라이언트가 처음 네 번의 시도 내에서 성공적으로 다시 연결되면 HubConnectionConnected 상태로 다시 전환되고 onreconnected 콜백을 실행합니다. 이렇게 하면 연결이 다시 구성되었다는 것을 사용자에게 알릴 수 있습니다.

연결은 서버에 완전히 새로운 것으로 보이므로 onreconnected 콜백에 새 connectionId가 제공됩니다.

HubConnection건너뛰기 협상으로 구성된 경우 onreconnected 콜백의 connectionId 매개 변수가 정의되지 않습니다.

connection.onreconnected(connectionId => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messageList").appendChild(li);
});

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

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("SignalR Connected.");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

클라이언트가 처음 네 번의 시도 내에서 성공적으로 다시 연결되지 않으면 HubConnectionDisconnected 상태로 전환되고 onclose 콜백을 실행합니다. 이렇게 하면 사용자에게 다음을 알릴 수 있습니다.

  • 연결이 영구적으로 끊겼습니다.
  • 페이지를 새로 고침해 보세요.
connection.onclose(error => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messageList").appendChild(li);
});

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

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

앞의 예제에서는 연결이 끊긴 후 즉시 다시 연결 시도를 시작하도록 HubConnection을 구성합니다. 기본 구성은 다시 연결을 시도하기 위해 0초 동안 대기합니다.

첫 번째 다시 연결 시도가 실패하면 두 번째 다시 연결 시도도 기본 구성을 사용하여 2초 동안 대기하는 대신 즉시 시작됩니다.

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

구성된 다시 연결 타이밍은 30초 후에 다시 연결 시도를 한 번 더 시도하는 대신 세 번째 다시 연결 시도 실패 후 중지하여 기본 동작과 다릅니다.

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

nextRetryDelayInMillisecondsRetryContext 형식의 단일 인수를 사용합니다. RetryContext에는 각각 number, numberError인 세 가지 속성 previousRetryCount, elapsedMillisecondsretryReason이 있습니다. 첫 번째 다시 연결 시도 전에 previousRetryCountelapsedMilliseconds는 모두 0이 되고 retryReason은 연결이 끊어지도록 하는 오류가 됩니다. 실패한 각 재시도 후에는 previousRetryCount가 1씩 증가하고, 지금까지 다시 연결하는 데 걸린 시간(밀리초)을 반영하도록 elapsedMilliseconds가 업데이트되며, retryReason은 마지막 다시 연결 시도의 실패를 초래한 오류입니다.

HubConnection이 다시 연결을 중지해야 하는 경우 nextRetryDelayInMilliseconds는 다음 다시 연결 시도까지 대기할 시간(밀리초)을 나타내는 숫자 또는 null을 반환해야 합니다.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

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

수동으로 다시 연결

다음 코드는 일반적인 수동 다시 연결 방법을 설명합니다.

  1. 연결을 시작하는 함수(이 경우 start 함수)가 만들어집니다.
  2. 연결의 onclose 이벤트 처리기에서 start 함수를 호출합니다.
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

프로덕션 구현은 일반적으로 지수 백오프를 사용하거나 지정된 횟수를 다시 시도합니다.

브라우저 절전 모드 탭

일부 브라우저에는 비활성 탭에 대한 컴퓨터 리소스 사용량을 줄이기 위한 탭 고정 또는 절전 모드 기능이 있습니다. 이로 인해 SignalR 연결이 닫힐 수 있으며 원치 않는 사용자 환경이 발생할 수 있습니다. 브라우저는 추론을 사용하여 다음과 같이 탭을 절전 모드로 유지해야 하는지 파악합니다.

  • 오디오 재생
  • 웹 잠금 유지
  • IndexedDB 잠금 유지
  • USB 디바이스에 연결
  • 비디오 또는 오디오 캡처
  • 미러되는 중
  • 창 또는 디스플레이 캡처

브라우저 추론은 시간이 지남에 따라 변경되고 브라우저 간에 다를 수 있습니다. 지원 매트릭스를 확인하고 시나리오에 가장 적합한 방법을 알아보세요.

앱을 절전 모드로 두지 않으려면 앱이 브라우저에서 사용하는 추론 중 하나를 트리거해야 합니다.

다음 코드 예제에서는 웹 잠금을 사용하여 탭을 끊은 상태로 유지하고 예기치 않은 연결 닫기를 방지하는 방법을 보여 줍니다.

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

이전 코드 예제에서:

  • 웹 잠금은 실험적입니다. 조건부 검사는 브라우저가 웹 잠금을 지원하는지 확인합니다.
  • 약속 확인자인 lockResolver는 탭이 절전 모드로 있을 수 있을 때 잠금을 해제할 수 있도록 저장됩니다.
  • 연결을 닫으면 lockResolver()를 호출하여 잠금이 해제됩니다. 잠금이 해제되면 탭이 절전 모드로 있을 수 있습니다.

추가 리소스

작성자: Rachel Appel

ASP.NET Core SignalR JavaScript 클라이언트 라이브러리를 사용하면 개발자가 서버 쪽 허브 코드를 호출할 수 있습니다.

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

SignalR 클라이언트 패키지를 설치합니다.

SignalR JavaScript 클라이언트 라이브러리는 npm 패키지로 전달됩니다. 다음 섹션에서는 클라이언트 라이브러리를 설치하는 다양한 방법을 간략하게 설명합니다.

npm을 사용하여 설치

Visual Studio의 경우 루트 폴더에 있는 동안 패키지 관리자 콘솔에서 다음 명령을 실행합니다. Visual Studio Code의 경우 통합 터미널에서 다음 명령을 실행합니다.

npm init -y
npm install @microsoft/signalr

npm은 node_modules\@microsoft\signalr\dist\browser 폴더에 패키지 콘텐츠를 설치합니다. wwwroot\lib 폴더 아래에 signalr라는 새 폴더를 만듭니다. signalr.js 파일을 wwwroot\lib\signalr 폴더에 복사합니다.

<script> 요소에서 SignalR JavaScript 클라이언트를 참조합니다. 예시:

<script src="~/lib/signalr/signalr.js"></script>

CDN(콘텐츠 배달 네트워크) 사용

npm 필수 조건 없이 클라이언트 라이브러리를 사용하려면 클라이언트 라이브러리의 CDN 호스팅 복사본을 참조합니다. 예시:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.js"></script>

클라이언트 라이브러리는 다음 CDN에서 사용할 수 있습니다.

LibMan을 통해 설치

LibMan은 CDN 호스팅 클라이언트 라이브러리에서 특정 클라이언트 라이브러리 파일을 설치하는 데 사용할 수 있습니다. 예를 들어 축소된 JavaScript 파일만 프로젝트에 추가합니다. 이 접근 방식에 대한 자세한 내용은 SignalR 클라이언트 라이브러리 추가를 참조하세요.

허브에 연결

다음 코드는 연결을 만들고 시작합니다. 허브의 이름은 대소문자를 구분하지 않습니다.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Start the connection.
start();

원본 간 연결

일반적으로 브라우저는 요청된 페이지와 동일한 도메인에서 연결을 로드합니다. 그러나 다른 도메인에 대한 연결이 필요한 경우도 있습니다.

Important

클라이언트 코드는 상대 URL 대신 절대 URL을 사용해야 합니다. .withUrl("/chathub").withUrl("https://myappurl/chathub")로 바꿉니다.

악의적인 사이트가 다른 사이트에서 중요한 데이터를 읽지 못하도록 하려면 기본적으로 원본 간 연결은 사용하지 않도록 설정됩니다. 원본 간 요청을 허용하려면 Startup 클래스에서 사용하도록 설정합니다.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SignalRChat.Hubs;

namespace SignalRChat
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddSignalR();

            services.AddCors(options =>
            {
                options.AddDefaultPolicy(builder =>
                {
                    builder.WithOrigins("https://example.com")
                        .AllowCredentials();
                });
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();
            app.UseRouting();

            app.UseCors();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapHub<ChatHub>("/chathub");
            });
        }
    }
}

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

JavaScript 클라이언트는 HubConnectioninvoke 메서드를 통해 허브에서 공용 메서드를 호출합니다. invoke 메서드는 다음을 허용합니다.

  • 허브 메서드의 이름.
  • 허브 메서드에 정의된 인수.

다음 예제에서 허브의 메서드 이름은 SendMessage입니다. invoke에 전달된 두 번째 및 세 번째 인수는 허브 메서드의 usermessage 인수에 매핑됩니다.

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

참고 항목

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

invoke 메서드는 JavaScript Promise를 반환합니다. Promise는 서버의 메서드가 반환될 때 반환 값(있는 경우)으로 확인됩니다. 서버의 메서드가 오류를 throw하는 경우 Promise는 오류 메시지와 함께 거부됩니다. asyncawait 또는 Promisethencatch 메서드를 사용하여 이러한 사례를 처리합니다.

JavaScript 클라이언트는 HubConnectionsend 메서드를 통해 허브에서 공용 메서드를 호출합니다. invoke 메서드와 달리 send 메서드는 서버의 응답을 기다리지 않습니다. send 메서드는 JavaScript Promise를 반환합니다. Promise는 메시지가 서버로 전송될 때 확인됩니다. 메시지를 보내는 동안 오류가 발생하면 오류 메시지와 함께 Promise가 거부됩니다. asyncawait 또는 Promisethencatch 메서드를 사용하여 이러한 사례를 처리합니다.

참고 항목

send를 사용하면 서버에서 메시지를 받을 때까지 기다리지 않습니다. 따라서 서버에서 데이터 또는 오류를 반환할 수 없습니다.

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

허브에서 메시지를 받으려면 HubConnectionon 메서드를 사용하여 메서드를 정의합니다.

  • JavaScript 클라이언트 메서드의 이름.
  • 허브가 메서드에 전달하는 인수.

다음 예제에서 메서드 이름은 ReceiveMessage입니다. 인수 이름은 usermessage입니다.

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

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

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

SignalR은 SendAsyncconnection.on에 정의된 메서드 이름과 인수를 일치시켜 호출할 클라이언트 메서드를 결정합니다.

참고 항목

가장 좋은 방법은 on 다음 HubConnection에서 start 메서드를 호출하는 것입니다. 이렇게 하면 메시지를 받기 전에 처리기가 등록됩니다.

오류 처리 및 로깅

asyncawait와 함께 trycatch를 사용하거나 Promisecatch 메서드를 사용하여 클라이언트 쪽 오류를 처리합니다. console.error를 사용하여 브라우저의 콘솔에 오류를 출력합니다.

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

연결이 설정되면 기록할 이벤트 유형 및 로거를 전달하여 클라이언트 쪽 로그 추적을 설정합니다. 메시지는 지정된 로그 수준 이상으로 기록됩니다. 사용 가능한 로그 수준은 다음과 같습니다.

  • signalR.LogLevel.Error: 오류 메시지. Error 메시지만 기록합니다.
  • signalR.LogLevel.Warning: 잠재적 오류에 대한 경고 메시지입니다. WarningError 메시지를 기록합니다.
  • signalR.LogLevel.Information: 오류 없는 상태 메시지입니다. Information, WarningError 메시지를 기록합니다.
  • signalR.LogLevel.Trace: 추적 메시지. 허브와 클라이언트 간에 전송되는 데이터를 포함하여 모든 것을 기록합니다.

HubConnectionBuilderconfigureLogging 메서드를 사용하여 로그 수준을 구성합니다. 메시지가 브라우저 콘솔에 기록됩니다.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

클라이언트 다시 연결

자동으로 다시 연결

HubConnectionBuilderwithAutomaticReconnect 메서드를 사용하여 자동으로 다시 연결하도록 SignalR에 대한 JavaScript 클라이언트를 구성할 수 있습니다. 기본적으로 자동으로 다시 연결되지 않습니다.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

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

다시 연결 시도를 시작하기 전에 HubConnection은 자동 다시 연결을 구성하지 않고 Disconnected 상태로 전환하고 HubConnection 같은 해당 onclose 콜백을 트리거하는 대신, HubConnectionState.Reconnecting 상태로 전환하고 onreconnecting 콜백을 실행합니다. 이렇게 하면 연결이 손실되었음을 사용자에게 경고하고 UI 요소를 사용하지 않도록 설정할 수 있습니다.

connection.onreconnecting(error => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messageList").appendChild(li);
});

클라이언트가 처음 네 번의 시도 내에서 성공적으로 다시 연결되면 HubConnectionConnected 상태로 다시 전환되고 onreconnected 콜백을 실행합니다. 이렇게 하면 연결이 다시 구성되었다는 것을 사용자에게 알릴 수 있습니다.

연결은 서버에 완전히 새로운 것으로 보이므로 onreconnected 콜백에 새 connectionId가 제공됩니다.

Warning

HubConnection건너뛰기 협상으로 구성된 경우 onreconnected 콜백의 connectionId 매개 변수가 정의되지 않습니다.

connection.onreconnected(connectionId => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messageList").appendChild(li);
});

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

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("SignalR Connected.");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

클라이언트가 처음 네 번의 시도 내에서 성공적으로 다시 연결되지 않으면 HubConnectionDisconnected 상태로 전환되고 onclose 콜백을 실행합니다. 이렇게 하면 연결이 영구적으로 끊겼음을 사용자에게 알리고 페이지를 새로 고치는 것이 좋습니다.

connection.onclose(error => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messageList").appendChild(li);
});

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

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

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

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

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

그런 다음, 사용자 지정 동작은 기본 구성에서와 같이 30초 후에 다시 연결 시도를 한 번 더 시도하는 대신 세 번째 다시 연결 시도 실패 후 중지하여 기본 동작에서 다시 분기합니다.

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

nextRetryDelayInMillisecondsRetryContext 형식의 단일 인수를 사용합니다. RetryContext에는 각각 number, numberError인 세 가지 속성 previousRetryCount, elapsedMillisecondsretryReason이 있습니다. 첫 번째 다시 연결 시도 전에 previousRetryCountelapsedMilliseconds는 모두 0이 되고 retryReason은 연결이 끊어지도록 하는 오류가 됩니다. 실패한 각 재시도 후에는 previousRetryCount가 1씩 증가하고, 지금까지 다시 연결하는 데 걸린 시간(밀리초)을 반영하도록 elapsedMilliseconds가 업데이트되며, retryReason은 마지막 다시 연결 시도의 실패를 초래한 오류입니다.

HubConnection이 다시 연결을 중지해야 하는 경우 nextRetryDelayInMilliseconds는 다음 다시 연결 시도까지 대기할 시간(밀리초)을 나타내는 숫자 또는 null을 반환해야 합니다.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

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

수동으로 다시 연결

다음 코드는 일반적인 수동 다시 연결 방법을 설명합니다.

  1. 연결을 시작하는 함수(이 경우 start 함수)가 만들어집니다.
  2. 연결의 onclose 이벤트 처리기에서 start 함수를 호출합니다.
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

프로덕션 구현은 일반적으로 지수 백오프를 사용하거나 지정된 횟수를 다시 시도합니다.

브라우저 절전 모드 탭

일부 브라우저에는 비활성 탭에 대한 컴퓨터 리소스 사용량을 줄이기 위한 탭 고정 또는 절전 모드 기능이 있습니다. 이로 인해 SignalR 연결이 닫힐 수 있으며 원치 않는 사용자 환경이 발생할 수 있습니다. 브라우저는 추론을 사용하여 다음과 같이 탭을 절전 모드로 유지해야 하는지 파악합니다.

  • 오디오 재생
  • 웹 잠금 유지
  • IndexedDB 잠금 유지
  • USB 디바이스에 연결
  • 비디오 또는 오디오 캡처
  • 미러되는 중
  • 창 또는 디스플레이 캡처

참고 항목

이러한 추론은 시간이 지남에 따라 변경되거나 브라우저 간에 다를 수 있습니다. 지원 매트릭스를 확인하고 시나리오에 가장 적합한 방법을 알아보세요.

앱을 절전 모드로 두지 않으려면 앱이 브라우저에서 사용하는 추론 중 하나를 트리거해야 합니다.

다음 코드 예제에서는 웹 잠금을 사용하여 탭을 끊은 상태로 유지하고 예기치 않은 연결 닫기를 방지하는 방법을 보여 줍니다.

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

이전 코드 예제에서:

  • 웹 잠금은 실험적입니다. 조건부 검사는 브라우저가 웹 잠금을 지원하는지 확인합니다.
  • 약속 확인자(lockResolver)는 탭이 절전 모드로 있을 수 있을 때 잠금을 해제할 수 있도록 저장됩니다.
  • 연결을 닫으면 lockResolver()를 호출하여 잠금이 해제됩니다. 잠금이 해제되면 탭이 절전 모드로 있을 수 있습니다.

추가 리소스