SignalR를 사용하여 웹 기반 실시간 통신을 구현할 수 있습니다. 특히, 서버에서 클라이언트로의 즉시 메시지 전달(서버 푸시)과 다중 사용자 간 채팅 기능이 가능합니다.
먼저 프로젝트에 SignalR을 설치해야 합니다. 다음 명령어를 통해 NuGet 패키지를 추가하세요:
Install-Package Microsoft.AspNet.SignalR
동적 프록시 생성 방식의 사용 조건
- 클라이언트 측에서 하나의 메서드에 여러 이벤트 핸들러를 등록하려면 동적 프록시를 사용할 수 없습니다.
- 동적 프록시를 사용하지 않으면
signalr/hubsURL을 참조할 수 없습니다.
클라이언트 설정
아래 스크립트를 페이지에 포함하세요:
주의:
signalr/hubs는 SignalR이 자동으로 생성하는 스크립트이며, 디버그 모드 실행 시 개발자 도구의 "Script" 탭에서 확인할 수 있습니다.
1. OWIN 시작 클래스 추가 (Startup.cs)
오른쪽 클릭 → 추가 → 새 항목 → "OWIN Startup Class" 선택 후 생성합니다.
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(Demo_SignalR_2._4._0.Models.Startup))]
namespace Demo_SignalR_2._4._0.Models
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}
2. SignalR Hub 클래스 생성 (ChatHub.cs)
새로운 항목으로 "SignalR Hub Class (v2)"를 추가하고 다음과 같이 구현합니다.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
namespace Demo_SignalR_2._4._0.Models
{
[HubName("chat")]
public class ChatHub : Hub
{
public static ConcurrentDictionary<string, string> ActiveUsers = new ConcurrentDictionary<string, string>();
[HubMethodName("sendMessage")]
public void SendMessage(string content)
{
var senderId = Context.ConnectionId;
var senderName = ActiveUsers[senderId];
var sanitizedContent = HttpUtility.HtmlEncode(content)
.Replace("\r\n", "<br/>")
.Replace("\n", "<br/>");
Clients.All.receiveMessage(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), senderName, sanitizedContent);
}
[HubMethodName("sendDirect")]
public void SendPrivateMessage(string targetConnectionId, string content)
{
var senderName = ActiveUsers[Context.ConnectionId];
var sanitizedContent = HttpUtility.HtmlEncode(content)
.Replace("\r\n", "<br/>")
.Replace("\n", "<br/>");
// 보내는 사람에게 반응
Clients.Caller.receiveMessage(
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
$"당신에게 {senderName}가 보냄",
sanitizedContent);
// 받는 사람에게 전달
Clients.Client(targetConnectionId).receiveMessage(
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
$"{senderName}로부터의 메시지",
sanitizedContent);
}
// 서버에서 직접 클라이언트로 메시지 전송
public static void BroadcastMessage(string message)
{
var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.broadcast(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), message);
}
public override async Task OnConnected()
{
var userName = Context.QueryString["userName"];
ActiveUsers.AddOrUpdate(Context.ConnectionId, userName, (key, value) => userName);
Clients.All.userStatusUpdate(
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
$"{userName} 접속 완료",
ActiveUsers.ToArray());
await base.OnConnected();
}
public override async Task OnDisconnected(bool stopCalled)
{
var userName = Context.QueryString["userName"];
Clients.All.userStatusUpdate(
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
$"{userName} 연결 종료",
ActiveUsers.ToArray());
ActiveUsers.TryRemove(Context.ConnectionId, out _);
await base.OnDisconnected(stopCalled);
}
}
}
3. 클라이언트 페이지 (Index.aspx)
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta charset="utf-8" />
<title>실시간 채팅 시스템</title>
<script src="Scripts/jquery-3.3.1.min.js"></script>
<script src="Scripts/jquery.signalR-2.4.0.min.js"></script>
<script src="signalr/hubs" type="text/javascript"></script>
<style type="text/css">
#chatArea {
width: 100%;
height: 500px;
border: 1px solid #ccc;
padding: 10px;
margin: 10px 0;
overflow-y: auto;
font-family: Arial, sans-serif;
}
.message-left { text-align: left; }
.message-right { text-align: right; }
.user-status { color: #666; font-size: 0.9em; }
</style>
<script type="text/javascript">
$(function () {
const userName = $("#userNameInput").val();
const chatArea = $("#chatArea");
const userSelect = $("#userList");
const chatHub = $.connection.chat;
$.connection.hub.qs = { userName: userName };
// 메시지 수신 처리
chatHub.client.receiveMessage = function (timestamp, sender, msg) {
const className = sender === userName ? "message-right" : "message-left";
chatArea.append(`<p class="${className}"><strong>${sender}</strong> (${timestamp})<br>${msg}</p>`);
chatArea.scrollTop(chatArea[0].scrollHeight);
};
// 사용자 목록 업데이트
chatHub.client.userStatusUpdate = function (timestamp, status, users) {
chatArea.append(`<p class="user-status">${timestamp} ${status}</p>`);
userSelect.empty();
users.forEach(u => {
if (u.Key !== userName) {
userSelect.append(`<option value="${u.Key}">${u.Value}</option>`);
}
});
};
// 서버 알림 수신
chatHub.client.broadcast = function (timestamp, msg) {
chatArea.append(`<p class="user-status">${timestamp} [서버] ${msg}</p>`);
chatArea.scrollTop(chatArea[0].scrollHeight);
};
// 연결 완료 후 이벤트 바인딩
$.connection.hub.start().done(function () {
$("#sendBtn").click(function () {
const recipient = userSelect.val();
const messageText = $("#messageInput").val().trim();
if (!messageText) return;
if (recipient) {
chatHub.server.sendDirect(recipient, messageText)
.done(() => {
$("#messageInput").val("").focus();
})
.fail(err => alert("전송 실패: " + err));
} else {
chatHub.server.sendMessage(messageText)
.done(() => {
$("#messageInput").val("").focus();
})
.fail(err => alert("전송 실패: " + err));
}
});
});
});
</script>
</head>
<body>
<form id="form1" runat="server">
<h3>실시간 채팅 시스템</h3>
<div id="chatArea"></div>
<div>
<label>사용자 이름:</label>
<input type="text" id="userNameInput" value="사용자-" readonly style="width: 200px;" />
<label>대화 상대:</label>
<select id="userList" style="width: 200px;">
<option value="">전체</option>
</select>
</div>
<div>
<textarea id="messageInput" rows="4" style="width: 70%;"></textarea>
<button type="button" id="sendBtn">전송</button>
</div>
</form>
</body>
</html>
4. 서버 푸시 요청 페이지 (ToServer.aspx)
using System;
using System.Web.UI;
namespace Demo_SignalR_2._4._0
{
public partial class ToServer : Page
{
protected void Page_Load(object sender, EventArgs e)
{
var message = Request["msg"];
if (!string.IsNullOrWhiteSpace(message))
{
Models.ChatHub.BroadcastMessage($"관리자 알림: {message}");
}
}
}
}
이 페이지는 /ToServer.aspx?msg=안녕하세요 형식으로 호출하면 모든 클라이언트에 서버 알림을 전달합니다.