ASP.NET Core 2026 SignalR 완벽 가이드: 실시간 통신, Hub, 면접 질문
ASP.NET Core에서 SignalR을 활용한 실시간 웹 애플리케이션 구축 방법을 설명합니다. Hub, 그룹, Redis 스케일링, 기술 면접 질문까지 모두 다룹니다.

SignalR은 ASP.NET Core 애플리케이션에서 서버와 클라이언트 간의 양방향 실시간 통신을 구현하는 라이브러리입니다. 기존의 HTTP 요청-응답 패턴과 달리 SignalR은 영구적인 연결을 유지하여 서버에서 클라이언트로 즉시 데이터를 푸시할 수 있습니다. 채팅 애플리케이션, 실시간 대시보드, 협업 편집 도구, 실시간 알림 시스템 등 즉시성이 필요한 애플리케이션에 필수적인 기술입니다.
SignalR은 사용 가능한 최적의 전송 방식을 자동으로 선택합니다. 먼저 WebSockets를 시도하고, 그 다음 Server-Sent Events, 마지막으로 Long Polling으로 폴백합니다. 이 메커니즘을 통해 코드 변경 없이 모든 브라우저와 네트워크 구성을 지원합니다.
SignalR Hub를 통한 서버-클라이언트 통신 원리
Hub는 클라이언트와 서버 간의 메서드 호출을 처리하는 고수준 파이프라인 역할을 합니다. 클라이언트는 Hub의 메서드를 호출하고, Hub는 연결된 클라이언트의 메서드를 호출할 수 있습니다. 이러한 추상화를 통해 WebSocket 연결을 수동으로 관리하는 복잡성이 제거됩니다.
다음 예제는 연결된 모든 클라이언트에 메시지를 브로드캐스트하는 기본 채팅 Hub를 보여줍니다:
using Microsoft.AspNetCore.SignalR;
public class ChatHub : Hub
{
// Called when a client sends a message
public async Task SendMessage(string user, string message)
{
// Broadcast to ALL connected clients
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
// Called automatically when a client connects
public override async Task OnConnectedAsync()
{
await Clients.Caller.SendAsync("Connected", Context.ConnectionId);
await base.OnConnectedAsync();
}
// Called automatically when a client disconnects
public override async Task OnDisconnectedAsync(Exception? exception)
{
// Clean up resources, notify other users, etc.
await base.OnDisconnectedAsync(exception);
}
}Clients 속성은 All, Caller, Others, Group, User 등 다양한 타겟팅 옵션을 통해 연결된 모든 클라이언트에 대한 접근을 제공합니다.
ASP.NET Core 9에서 SignalR 구성하기
ASP.NET Core 9에서 SignalR 등록은 표준 미들웨어 패턴을 따릅니다. 다음 구성에서는 사용자 정의 버퍼 크기로 JSON 및 MessagePack 프로토콜을 활성화합니다:
var builder = WebApplication.CreateBuilder(args);
// Add SignalR services with configuration
builder.Services.AddSignalR(options =>
{
options.EnableDetailedErrors = builder.Environment.IsDevelopment();
options.MaximumReceiveMessageSize = 64 * 1024; // 64 KB
options.StreamBufferCapacity = 10;
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
})
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions.PropertyNamingPolicy = null; // PascalCase
});
var app = builder.Build();
// Map the hub endpoint
app.MapHub<ChatHub>("/chathub");
app.Run();KeepAliveInterval과 ClientTimeoutInterval 설정은 연결 상태 모니터링을 제어합니다. 타임아웃 기간 내에 응답하지 않는 클라이언트는 연결이 끊긴 것으로 간주됩니다.
그룹을 활용한 타겟 메시지 전달
그룹은 연결을 구성하고 클라이언트의 하위 집합에 메시지를 보내는 메커니즘을 제공합니다. 일반적인 사용 사례는 채팅방으로, 사용자가 특정 방에 참여하고 해당 방의 메시지만 수신합니다.
public class GroupChatHub : Hub
{
// Add the current connection to a group
public async Task JoinRoom(string roomName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
// Notify the group that someone joined
await Clients.Group(roomName).SendAsync(
"UserJoined",
Context.User?.Identity?.Name ?? "Anonymous"
);
}
// Remove the current connection from a group
public async Task LeaveRoom(string roomName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
await Clients.Group(roomName).SendAsync(
"UserLeft",
Context.User?.Identity?.Name ?? "Anonymous"
);
}
// Send a message to a specific group only
public async Task SendToRoom(string roomName, string message)
{
await Clients.Group(roomName).SendAsync(
"ReceiveMessage",
Context.User?.Identity?.Name,
message
);
}
}그룹 멤버십은 사용자가 아닌 연결에 연결됩니다. 사용자가 재연결하면 그룹에 다시 참여해야 합니다. 영구적인 그룹 멤버십을 위해서는 그룹 연결을 데이터베이스에 저장하고 OnConnectedAsync에서 자동으로 다시 참여하도록 구현합니다.
그룹은 메모리에만 존재하며 서버 재시작이나 백플레인 없이 스케일 아웃 시나리오에서는 지속되지 않습니다. 여러 서버를 사용하는 프로덕션 배포에서는 Redis 또는 Azure SignalR Service를 백플레인으로 사용하세요.
인증을 통한 SignalR 연결 보안
SignalR은 ASP.NET Core 인증과 통합됩니다. [Authorize] 특성으로 Hub 접근을 인증된 사용자로 제한하고, Context.User를 통해 인증된 사용자의 클레임에 접근할 수 있습니다.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
[Authorize] // Require authentication for the entire hub
public class SecureHub : Hub
{
// Access user claims through Context.User
public async Task SendPrivateMessage(string recipientUserId, string message)
{
var senderName = Context.User?.Identity?.Name
?? throw new HubException("User not authenticated");
// Send to a specific user (all their connections)
await Clients.User(recipientUserId).SendAsync(
"PrivateMessage",
senderName,
message
);
}
[Authorize(Roles = "Admin")] // Role-based authorization on method
public async Task BroadcastAnnouncement(string announcement)
{
await Clients.All.SendAsync("Announcement", announcement);
}
}WebSocket 연결의 경우 사용자 정의 헤더를 지원하지 않으므로 인증 토큰을 쿼리 문자열을 통해 전달해야 합니다. JavaScript 클라이언트에서 토큰을 포함하는 설정은 다음과 같습니다:
import * as signalR from "@microsoft/signalr";
const connection = new signalR.HubConnectionBuilder()
.withUrl("/securehub", {
accessTokenFactory: () => localStorage.getItem("authToken") || ""
})
.withAutomaticReconnect()
.build();
await connection.start();서버 측에서는 쿼리 문자열에서 토큰을 읽도록 JWT 인증을 구성합니다:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// Read token from query string for SignalR
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
path.StartsWithSegments("/securehub"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});.NET 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
Redis 백플레인을 통한 스케일링
로드 밸런싱 환경에서는 클라이언트가 서로 다른 서버 인스턴스에 연결될 수 있습니다. 백플레인이 없으면 한 서버에서 보낸 메시지는 해당 서버에 연결된 클라이언트에만 도달합니다. Redis는 모든 서버 인스턴스 간에 메시지를 동기화하는 백플레인 역할을 합니다.
builder.Services.AddSignalR()
.AddStackExchangeRedis(options =>
{
options.Configuration = builder.Configuration
.GetConnectionString("Redis");
// Optional: configure Redis-specific options
options.Configuration.ChannelPrefix =
RedisChannel.Literal("MyApp_SignalR_");
});Redis 백플레인은 모든 SignalR 메시지를 Redis 채널에 게시합니다. 각 서버 인스턴스는 이러한 채널을 구독하고 로컬 클라이언트에 메시지를 전달합니다. 이 접근 방식은 최소한의 지연 시간(일반적으로 1-2ms)으로 수평 확장을 가능하게 합니다.
엔터프라이즈 배포의 경우 Azure SignalR Service가 스케일링, 연결 관리 및 가용성을 자동으로 처리하는 관리형 대안을 제공합니다.
강력한 타입의 Hub로 컴파일 타임 안전성 확보
강력한 타입의 Hub는 매직 문자열을 인터페이스 메서드로 대체하여 클라이언트 메서드 호출의 컴파일 타임 검증을 가능하게 합니다:
public interface INotificationClient
{
Task ReceiveNotification(string title, string message, string severity);
Task UpdateProgress(int percentage);
Task TaskCompleted(Guid taskId, bool success);
}
// NotificationHub.cs
public class NotificationHub : Hub<INotificationClient>
{
public async Task NotifyAll(string title, string message)
{
// Compile-time checking - no magic strings
await Clients.All.ReceiveNotification(title, message, "info");
}
public async Task ReportProgress(string groupName, int percentage)
{
// IDE autocomplete works here
await Clients.Group(groupName).UpdateProgress(percentage);
}
}인터페이스의 메서드 이름을 변경하면 업데이트가 필요한 모든 호출 위치가 즉시 표시되므로 리팩토링이 더 안전해집니다.
SignalR 채널을 통한 스트리밍
SignalR은 진행 상황 업데이트, 로그 테일링, 실시간 센서 데이터 등 데이터가 점진적으로 생성되는 시나리오를 위한 스트리밍을 지원합니다. 서버에서 클라이언트로, 클라이언트에서 서버로의 양방향 스트리밍이 모두 가능합니다.
public class StreamingHub : Hub
{
// Server-to-client streaming with IAsyncEnumerable
public async IAsyncEnumerable<StockPrice> StreamStockPrices(
string[] symbols,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var random = new Random();
while (!cancellationToken.IsCancellationRequested)
{
foreach (var symbol in symbols)
{
yield return new StockPrice
{
Symbol = symbol,
Price = random.NextDouble() * 1000,
Timestamp = DateTime.UtcNow
};
}
await Task.Delay(1000, cancellationToken);
}
}
// Client-to-server streaming
public async Task UploadStream(IAsyncEnumerable<LogEntry> stream)
{
await foreach (var entry in stream)
{
// Process each log entry as it arrives
await ProcessLogEntry(entry);
}
}
}
public record StockPrice(string Symbol, double Price, DateTime Timestamp);
public record LogEntry(string Level, string Message, DateTime Timestamp);JavaScript 클라이언트는 비동기 이터레이터를 사용하여 스트림을 소비합니다:
const stream = connection.stream("StreamStockPrices", ["AAPL", "MSFT"]);
stream.subscribe({
next: (price) => console.log(`${price.symbol}: $${price.price}`),
error: (err) => console.error(err),
complete: () => console.log("Stream completed")
});SignalR 기술 면접 자주 나오는 질문
기술 면접에서는 SignalR 아키텍처와 실무 시나리오가 자주 다루어집니다. 다음은 .NET 면접 세션에서 정기적으로 출제되는 질문입니다:
전송 협상 프로세스를 설명할 수 있어야 합니다: SignalR은 먼저 WebSockets를 시도하고, Server-Sent Events, Long Polling 순으로 폴백합니다. 각 전송 방식이 언제 사용되고 그 제한이 무엇인지 이해하는 것이 중요합니다.
Q: 클라이언트가 일시적으로 연결을 잃었을 때 SignalR은 연결 상태를 어떻게 처리합니까?
SignalR은 구성 가능한 기간 동안 서버에서 연결 상태를 유지합니다. 자동 재연결이 활성화된 경우 클라이언트는 동일한 연결 ID를 사용하여 재연결을 시도합니다. 성공하면 그룹 멤버십을 잃지 않고 연결이 재개됩니다. 연결 끊김이 타임아웃을 초과하면 새 연결이 설정되고 클라이언트는 그룹에 다시 참여해야 합니다.
Q: Clients.User()와 Clients.Client()의 차이점은 무엇입니까?
Clients.User(userId)는 특정 인증된 사용자에게 속한 모든 연결을 대상으로 합니다. 한 사용자가 여러 브라우저 탭을 열어둔 경우에 유용합니다. Clients.Client(connectionId)는 단일 특정 연결을 대상으로 합니다. 사용자 기반 타겟팅에는 인증이 필요하며 IUserIdProvider를 사용하여 연결을 사용자 ID에 매핑합니다.
Q: 프레즌스 감지(온라인 사용자 표시)를 어떻게 구현합니까?
OnConnectedAsync와 OnDisconnectedAsync에서 연결을 추적합니다. 사용자-연결 매핑을 ConcurrentDictionary 또는 분산 캐시에 저장합니다. 스케일 아웃된 배포에서는 Redis와 같은 공유 저장소를 사용합니다. 사용자가 연결하거나 연결이 끊길 때 관련 그룹에 프레즌스 업데이트를 브로드캐스트합니다.
Q: SignalR 백플레인의 목적을 설명하세요.
백플레인은 로드 밸런싱 환경에서 여러 서버 인스턴스 간에 메시지를 동기화합니다. 백플레인이 없으면 서버 A에서 보낸 메시지는 서버 A에 연결된 클라이언트에만 도달합니다. Redis와 Azure SignalR Service가 일반적인 백플레인 옵션입니다. 백플레인은 모든 서버가 구독하는 공유 채널에 메시지를 게시합니다.
성능 최적화 전략
SignalR 성능은 메시지 크기, 빈도 및 연결 수에 따라 달라집니다. ASP.NET Core 성능 모범 사례 문서에서 자세한 벤치마크를 제공합니다.
- MessagePack 프로토콜 사용: 바이너리 직렬화로 JSON 대비 페이로드 크기를 30-50% 감소
- 메시지 일괄 처리: 많은 작은 메시지를 보내는 대신 가능한 한 묶어서 전송
- 그룹 크기 제한: 멤버를 수천 명으로 제한. 더 큰 규모의 배포에는 pub/sub 패턴을 고려
- 압축 활성화: 텍스트 중심의 페이로드에 효과적
- 적절한 타임아웃 설정: 오래된 연결에서 리소스를 신속하게 해제
// MessagePack configuration for smaller payloads
builder.Services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.SerializerOptions =
MessagePackSerializerOptions.Standard
.WithCompression(MessagePackCompression.Lz4Block);
});결론
- SignalR은 전송 선택(WebSockets, SSE, Long Polling)을 추상화하고 실시간 통신을 위한 일관된 프로그래밍 모델을 제공합니다
- Hub는 연결 관리를 중앙화하고, 강력한 타입의 Hub는 클라이언트 메서드 호출에 컴파일 타임 안전성을 추가합니다
- 그룹은 연결의 하위 집합에 대한 타겟 메시지 전달을 가능하게 합니다. 재연결 후에는 그룹에 다시 참여해야 합니다
- 인증은 ASP.NET Core ID와 통합됩니다. WebSocket 연결의 경우 쿼리 문자열을 통해 토큰을 전달합니다
- Redis 백플레인 또는 Azure SignalR Service를 사용하여 서버 인스턴스 간에 메시지를 동기화하고 수평 확장을 구현합니다
- 스트리밍은
IAsyncEnumerable을 사용한 점진적 데이터 전달을 지원하며 서버에서 클라이언트로, 클라이언트에서 서버로 양방향 모두 사용 가능합니다
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.

작성자
Anthony Fillion-Maillet풀스택 개발자, SharpSkill 창업자
10년 이상 풀스택 개발을 해왔습니다. SharpSkill을 운영하며 이곳에 게시되는 모든 내용에 책임을 집니다.
2026년 8월 12일 업데이트
공유
관련 기사

ASP.NET Core Minimal APIs 2026 완벽 가이드: 아키텍처, 성능 최적화, 면접 대비
ASP.NET Core Minimal APIs의 설계 원칙, 성능 최적화, 의존성 주입, 엔드포인트 구성에 대한 실무 가이드. 2026년 기술 면접에서 자주 출제되는 질문과 모범 답변 수록.

.NET 10 (2026): 새로운 기능, Native AOT 성숙도 및 면접 질문 완벽 가이드
.NET 10의 새로운 기능을 심층 분석합니다. Native AOT 프로덕션 성숙도, C# 14 확장 멤버, field 키워드, EF Core 10 명명된 쿼리 필터 등 기술 면접에 필요한 핵심 내용을 다룹니다.

2026년 .NET MAUI 완벽 가이드: 크로스 플랫폼 개발과 면접 질문 정리
.NET MAUI 10으로 크로스 플랫폼 앱을 구축하는 방법을 핸들러 아키텍처, MVVM, HybridWebView, SafeAreaEdges와 함께 다루고, 2026년 기술 면접 핵심 질문을 정리합니다.