C# SignalR을 활용한 실시간 서버 알림 및 간단한 채팅 시스템 구현

SignalR를 사용하여 웹 기반 실시간 통신을 구현할 수 있습니다. 특히, 서버에서 클라이언트로의 즉시 메시지 전달(서버 푸시)과 다중 사용자 간 채팅 기능이 가능합니다.

먼저 프로젝트에 SignalR을 설치해야 합니다. 다음 명령어를 통해 NuGet 패키지를 추가하세요: Install-Package Microsoft.AspNet.SignalR

동적 프록시 생성 방식의 사용 조건

  • 클라이언트 측에서 하나의 메서드에 여러 이벤트 핸들러를 등록하려면 동적 프록시를 사용할 수 없습니다.
  • 동적 프록시를 사용하지 않으면 signalr/hubs URL을 참조할 수 없습니다.

클라이언트 설정

아래 스크립트를 페이지에 포함하세요:

주의: 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=안녕하세요 형식으로 호출하면 모든 클라이언트에 서버 알림을 전달합니다.


태그: SignalR C# WebSockets Real-time Communication ASP.NET Web Forms

5월 27일 14:02에 게시됨