SignalR 1.x를 사용하는 고주파수 실시간

작성자 : Patrick Fletcher

경고

이 설명서는 최신 버전의 SignalR용이 아닙니다. ASP.NET Core SignalR을 살펴보세요.

이 자습서에서는 ASP.NET SignalR을 사용하여 고주파 메시징 기능을 제공하는 웹 애플리케이션을 만드는 방법을 보여줍니다. 이 경우 고주파 메시징은 고정된 속도로 전송되는 업데이트를 의미합니다. 이 애플리케이션의 경우 초당 최대 10건의 메시지를 표시합니다.

이 자습서에서 만들 애플리케이션은 사용자가 끌 수 있는 셰이프를 표시합니다. 그러면 다른 모든 연결된 브라우저에서 셰이프의 위치가 시간 제한 업데이트를 사용하여 끌어간 셰이프의 위치와 일치하도록 업데이트됩니다.

이 자습서에 도입된 개념에는 실시간 게임 및 기타 시뮬레이션 애플리케이션의 애플리케이션이 있습니다.

자습서에 대한 의견을 환영합니다. 자습서와 직접 관련이 없는 질문이 있는 경우 ASP.NET SignalR 포럼 또는 StackOverflow.com 게시할 수 있습니다.

개요

이 자습서에서는 개체의 상태를 다른 브라우저와 실시간으로 공유하는 애플리케이션을 만드는 방법을 보여 줍니다. 만들 애플리케이션을 MoveShape라고 합니다. MoveShape 페이지에는 사용자가 끌 수 있는 HTML Div 요소가 표시됩니다. 사용자가 Div를 끌면 새 위치가 서버로 전송됩니다. 그러면 연결된 다른 모든 클라이언트가 셰이프의 위치를 일치하도록 업데이트하도록 지시합니다.

MoveShape 애플리케이션 페이지를 보여 주는 스크린샷

이 자습서에서 만든 애플리케이션은 Damian Edwards의 데모를 기반으로 합니다. 이 데모가 포함된 비디오는 여기에서 볼 수 있습니다.

이 자습서에서는 셰이프를 끌 때 발생하는 각 이벤트에서 SignalR 메시지를 보내는 방법을 보여 줍니다. 연결된 각 클라이언트는 메시지를 받을 때마다 셰이프의 로컬 버전 위치를 업데이트합니다.

애플리케이션은 이 메서드를 사용하여 작동하지만 전송되는 메시지 수에 상한이 없으므로 클라이언트와 서버가 메시지와 성능이 저하될 수 있으므로 권장되는 프로그래밍 모델이 아닙니다. 각 새 위치로 원활하게 이동하는 대신 각 메서드에 의해 셰이프가 즉시 이동되기 때문에 클라이언트에 표시된 애니메이션도 연결되지 않습니다. 자습서의 이후 섹션에서는 클라이언트 또는 서버에서 메시지를 보내는 최대 속도를 제한하는 타이머 함수를 만드는 방법과 위치 간에 셰이프를 원활하게 이동하는 방법을 보여 줍니다. 이 자습서에서 만든 애플리케이션의 최종 버전은 코드 갤러리에서 다운로드할 수 있습니다.

이 자습서에는 다음 섹션이 포함되어 있습니다.

사전 요구 사항

이 자습서에는 Visual Studio 2012 또는 Visual Studio 2010이 필요합니다. Visual Studio 2010을 사용하는 경우 프로젝트는 .NET Framework 4.5 대신 .NET Framework 4를 사용합니다.

Visual Studio 2012를 사용하는 경우 ASP.NET 및 Web Tools 2012.2 업데이트를 설치하는 것이 좋습니다. 이 업데이트에는 게시 기능 향상, 새 기능 및 새 템플릿과 같은 새로운 기능이 포함되어 있습니다.

Visual Studio 2010이 있는 경우 NuGet 이 설치되어 있는지 확인합니다.

프로젝트 만들기

이 섹션에서는 Visual Studio에서 프로젝트를 만듭니다.

  1. 파일 메뉴에서 새 프로젝트를 클릭합니다.

  2. 새 프로젝트 대화 상자의 템플릿 아래에서 C#을 확장하고 선택합니다.

  3. ASP.NET 빈 웹 애플리케이션 템플릿을 선택하고 프로젝트 이름을 MoveShapeDemo로 지정한 다음 확인을 클릭합니다.

    새 프로젝트 만들기

SignalR 및 JQuery.UI NuGet 패키지 추가

NuGet 패키지를 설치하여 프로젝트에 SignalR 기능을 추가할 수 있습니다. 이 자습서에서는 셰이프를 끌어서 애니메이션할 수 있도록 JQuery.UI 패키지도 사용합니다.

  1. 도구 | 클릭 NuGet 패키지 관리자 | 패키지 관리자 콘솔.

  2. 패키지 관리자에서 다음 명령을 입력합니다.

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    SignalR 패키지는 여러 다른 NuGet 패키지를 종속성으로 설치합니다. 설치가 완료되면 ASP.NET 애플리케이션에서 SignalR을 사용하는 데 필요한 모든 서버 및 클라이언트 구성 요소가 있습니다.

  3. 패키지 관리자 콘솔에 다음 명령을 입력하여 JQuery 및 JQuery.UI 패키지를 설치합니다.

    Install-Package jQuery.ui.combined
    

기본 애플리케이션 만들기

이 섹션에서는 각 마우스 이동 이벤트 중에 셰이프의 위치를 서버로 보내는 브라우저 애플리케이션을 만듭니다. 그런 다음 서버는 수신되는 다른 모든 연결된 클라이언트에 이 정보를 브로드캐스트합니다. 이후 섹션에서는 이 애플리케이션을 확장합니다.

  1. 솔루션 탐색기 프로젝트를 마우스 오른쪽 단추로 클릭하고 추가, 클래스...를 선택합니다. 클래스 이름을 MoveShapeHub로 지정하고 추가를 클릭합니다.

  2. MoveShapeHub 클래스의 코드를 다음 코드로 바꿉 있습니다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    using Newtonsoft.Json;
    
    namespace MoveShapeDemo
    {
        public class MoveShapeHub : Hub
        {
            public void UpdateModel(ShapeModel clientModel)
            {
                clientModel.LastUpdatedBy = Context.ConnectionId;
                // Update the shape model within our broadcaster
                Clients.AllExcept(clientModel.LastUpdatedBy).updateShape(clientModel);
            }
        }
        public class ShapeModel
        {
            // We declare Left and Top as lowercase with 
            // JsonProperty to sync the client and server models
            [JsonProperty("left")]
            public double Left { get; set; }
    
            [JsonProperty("top")]
            public double Top { get; set; }
    
            // We don't want the client to get the "LastUpdatedBy" property
            [JsonIgnore]
            public string LastUpdatedBy { get; set; }
        }
    }
    

    MoveShapeHub 위의 클래스는 SignalR 허브의 구현입니다. SignalR을 사용한 시작 자습서와 마찬가지로 허브에는 클라이언트가 직접 호출하는 메서드가 있습니다. 이 경우 클라이언트는 셰이프의 새 X 및 Y 좌표가 포함된 개체를 서버에 보낸 다음 다른 모든 연결된 클라이언트에 브로드캐스트됩니다. SignalR은 JSON을 사용하여 이 개체를 자동으로 직렬화합니다.

    클라이언트(ShapeModel)로 전송될 개체에는 셰이프의 위치를 저장할 멤버가 포함됩니다. 서버의 개체 버전에는 지정된 클라이언트가 자체 데이터를 전송하지 않도록 어떤 클라이언트의 데이터가 저장되고 있는지 추적하는 멤버도 포함되어 있습니다. 이 멤버는 JsonIgnore 특성을 사용하여 직렬화되어 클라이언트로 전송되지 않도록 합니다.

  3. 다음으로, 애플리케이션이 시작될 때 허브를 설정합니다. 솔루션 탐색기 프로젝트를 마우스 오른쪽 단추로 클릭한 다음 추가 | 전역 애플리케이션 클래스. Global의 기본 이름을 적용하고 확인을 클릭합니다.

    전역 애플리케이션 클래스 추가

  4. Global.asax.cs 클래스에서 제공된 using 문 다음에 다음 using 문을 추가합니다.

    using System.Web.Routing;
    
  5. Global 클래스의 메서드에 Application_Start 다음 코드 줄을 추가하여 SignalR에 대한 기본 경로를 등록합니다.

    RouteTable.Routes.MapHubs();
    

    global.asax 파일은 다음과 같아야 합니다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Security;
    using System.Web.SessionState;
    
    using System.Web.Routing;
    
    namespace MoveShapeDemo
    {
        public class Global : System.Web.HttpApplication
        {
            protected void Application_Start(object sender, EventArgs e)
            {
                RouteTable.Routes.MapHubs();
            }
        }
    }
    
  6. 다음으로 클라이언트를 추가합니다. 솔루션 탐색기 프로젝트를 마우스 오른쪽 단추로 클릭한 다음 추가 | 새 항목입니다. 새 항목 추가 대화 상자에서 Html 페이지를 선택합니다. 페이지에 적절한 이름( 예:Default.html)을 지정하고 추가를 클릭합니다.

  7. 솔루션 탐색기 방금 만든 페이지를 마우스 오른쪽 단추로 클릭하고 시작 페이지로 설정을 클릭합니다.

  8. HTML 페이지의 기본 코드를 다음 코드 조각으로 바꿉니다.

    참고

    아래 스크립트 참조가 Scripts 폴더의 프로젝트에 추가된 패키지와 일치하는지 확인합니다. Visual Studio 2010에서 프로젝트에 추가된 JQuery 및 SignalR 버전이 아래 버전 번호와 일치하지 않을 수 있습니다.

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    </head>
    <body>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
     $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                $shape = $("#shape"),
    
                shapeModel = {
                    left: 0,
                    top: 0
                };
    
                moveShapeHub.client.updateShape = function (model) {
                    shapeModel = model;
                    $shape.css({ left: model.left, top: model.top });
                };
    
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moveShapeHub.server.updateModel(shapeModel);
                        }
                    });
                });
            });
    </script>
    
        <div id="shape" />
    </body>
    </html>
    

    위의 HTML 및 JavaScript 코드는 Shape라는 빨간색 Div를 만들고, jQuery 라이브러리를 사용하여 셰이프의 끌기 동작을 사용하도록 설정하고, 셰이프의 drag 이벤트를 사용하여 셰이프의 위치를 서버로 보냅니다.

  9. F5 키를 눌러 애플리케이션을 시작합니다. 페이지의 URL을 복사하여 두 번째 브라우저 창에 붙여넣습니다. 브라우저 창 중 하나에서 셰이프를 끌어다 옵니다. 다른 브라우저 창의 셰이프가 이동해야 합니다.

    한 브라우저 창에서 끌어서 다른 창에서 셰이프를 이동하는 방법을 보여 주는 스크린샷

클라이언트 루프 추가

모든 마우스 이동 이벤트에서 셰이프의 위치를 보내면 불필요한 양의 네트워크 트래픽이 생성되므로 클라이언트의 메시지를 제한해야 합니다. Javascript setInterval 함수를 사용하여 고정된 속도로 서버에 새 위치 정보를 보내는 루프를 설정합니다. 이 루프는 게임 또는 기타 시뮬레이션의 모든 기능을 구동하는 반복적으로 호출되는 함수인 "게임 루프"의 매우 기본적인 표현입니다.

  1. HTML 페이지의 클라이언트 코드를 다음 코드 조각과 일치하도록 업데이트합니다.

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    
    </head>
    <body>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
            $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                    $shape = $("#shape"),
                    // Send a maximum of 10 messages per second 
                    // (mouse movements trigger a lot of messages)
                    messageFrequency = 10, 
                    // Determine how often to send messages in
                    // time to abide by the messageFrequency
                    updateRate = 1000 / messageFrequency, 
                    shapeModel = {
                        left: 0,
                        top: 0
                    },
                    moved = false;
    
                moveShapeHub.client.updateShape = function (model) {
                    shapeModel = model;
                    $shape.css({ left: model.left, top: model.top });
                };
    
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moved = true;
                        }
                    });
    
                    // Start the client side server update interval
                    setInterval(updateServerModel, updateRate);
                });
    
                function updateServerModel() {
                    // Only update server if we have a new movement
                    if (moved) {
                        moveShapeHub.server.updateModel(shapeModel);
                        moved = false;
                    }
                }
            });
    </script>
    
        <div id="shape" />
    </body>
    </html>
    

    위의 업데이트는 고정된 빈도로 updateServerModel 호출되는 함수를 추가합니다. 이 함수는 플래그가 보낼 새 위치 데이터가 있음을 표시할 때마다 moved 위치 데이터를 서버로 보냅니다.

  2. F5 키를 눌러 애플리케이션을 시작합니다. 페이지의 URL을 복사하여 두 번째 브라우저 창에 붙여넣습니다. 브라우저 창 중 하나에서 셰이프를 끌어다 옵니다. 다른 브라우저 창의 셰이프가 이동해야 합니다. 서버로 전송되는 메시지 수가 제한되므로 애니메이션은 이전 섹션처럼 매끄럽게 표시되지 않습니다.

    클라이언트 루프를 추가할 때 한 브라우저 창에서 끌어서 다른 창에서 셰이프를 이동하는 방법을 보여 주는 스크린샷

서버 루프 추가

현재 애플리케이션에서 서버에서 클라이언트로 전송된 메시지는 수신되는 만큼 자주 꺼집니다. 이는 클라이언트에서 볼 수 있는 것과 유사한 문제를 표시합니다. 메시지가 필요한 것보다 더 자주 전송될 수 있으며 결과적으로 연결이 플러드될 수 있습니다. 이 섹션에서는 보내는 메시지의 속도를 제한하는 타이머를 구현하도록 서버를 업데이트하는 방법을 설명합니다.

  1. MoveShapeHub.cs 내용을 다음 코드 조각으로 바꿉니다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    using System.Threading;
    using Microsoft.AspNet.SignalR;
    
    using Newtonsoft.Json;
    
    namespace MoveShapeDemo
    {
        public class Broadcaster
        {
            private readonly static Lazy<Broadcaster> _instance = 
                new Lazy<Broadcaster>(() => new Broadcaster());
            // We're going to broadcast to all clients a maximum of 25 times per second
            private readonly TimeSpan BroadcastInterval = 
                TimeSpan.FromMilliseconds(40); 
            private readonly IHubContext _hubContext;
            private Timer _broadcastLoop;
            private ShapeModel _model;
            private bool _modelUpdated;
    
            public Broadcaster()
            {
                // Save our hub context so we can easily use it 
                // to send to its connected clients
                _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
    
                _model = new ShapeModel();
                _modelUpdated = false;
    
                // Start the broadcast loop
                _broadcastLoop = new Timer(
                    BroadcastShape, 
                    null, 
                    BroadcastInterval, 
                    BroadcastInterval);
            }
    
            public void BroadcastShape(object state)
            {
                // No need to send anything if our model hasn't changed
                if (_modelUpdated)
                {
                    // This is how we can access the Clients property 
                    // in a static hub method or outside of the hub entirely
                    _hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
                    _modelUpdated = false;
                }
            }
    
            public void UpdateShape(ShapeModel clientModel)
            {
                _model = clientModel;
                _modelUpdated = true;
            }
    
            public static Broadcaster Instance
            {
                get
                {
                    return _instance.Value;
                }
            }
        }
        
        public class MoveShapeHub : Hub
        {
            // Is set via the constructor on each creation
            private Broadcaster _broadcaster;
    
            public MoveShapeHub()
                : this(Broadcaster.Instance)
            {
            }
    
            public MoveShapeHub(Broadcaster broadcaster)
            {
                _broadcaster = broadcaster;
            }
    
            public void UpdateModel(ShapeModel clientModel)
            {
                clientModel.LastUpdatedBy = Context.ConnectionId;
                // Update the shape model within our broadcaster
                _broadcaster.UpdateShape(clientModel);
            }
        }
        public class ShapeModel
        {
            // We declare Left and Top as lowercase with 
            // JsonProperty to sync the client and server models
            [JsonProperty("left")]
            public double Left { get; set; }
    
            [JsonProperty("top")]
            public double Top { get; set; }
    
            // We don't want the client to get the "LastUpdatedBy" property
            [JsonIgnore]
            public string LastUpdatedBy { get; set; }
        }
        
    }
    

    위의 코드는 클라이언트를 확장하여 .NET Framework에서 클래스를 사용하여 Timer 나가는 메시지를 제한하는 클래스를 추가 Broadcaster 합니다.

    허브 자체는 일시적이므로(필요할 때마다 만들어짐) Broadcaster 는 싱글톤으로 만들어집니다. 지연 초기화(.NET 4에 도입됨)는 필요할 때까지 생성을 연기하여 타이머가 시작되기 전에 첫 번째 허브 instance 완전히 만들어지도록 하는 데 사용됩니다.

    그러면 클라이언트의 UpdateShape 함수에 대한 호출이 허브의 UpdateModel 메서드에서 이동되므로 들어오는 메시지가 수신될 때마다 더 이상 즉시 호출되지 않습니다. 대신 클라이언트에 대한 메시지는 클래스 내에서 Broadcaster 타이머가 관리하는 _broadcastLoop 초당 25개 호출 속도로 전송됩니다.

    마지막으로, 허브에서 클라이언트 메서드를 직접 호출하는 대신 클래스는 Broadcaster 를 사용하여 현재 운영 허브(_hubContext)에 대한 참조를 GlobalHost가져와야 합니다.

  2. F5 키를 눌러 애플리케이션을 시작합니다. 페이지의 URL을 복사하여 두 번째 브라우저 창에 붙여넣습니다. 브라우저 창 중 하나에서 셰이프를 끌어다 옵니다. 다른 브라우저 창의 셰이프가 이동해야 합니다. 브라우저에서 이전 섹션과 눈에 띄는 차이는 없지만 클라이언트로 전송되는 메시지 수가 제한됩니다.

    서버 루프를 추가할 때 한 브라우저 창에서 끌어서 다른 창에서 셰이프를 이동하는 방법을 보여 주는 스크린샷

클라이언트에 부드러운 애니메이션 추가

애플리케이션은 거의 완료되었지만 서버 메시지에 대한 응답으로 이동할 때 클라이언트에서 셰이프의 동작을 한 번 더 개선할 수 있습니다. 셰이프의 위치를 서버에서 제공하는 새 위치로 설정하는 대신 JQuery UI 라이브러리의 animate 함수를 사용하여 셰이프를 현재 위치와 새 위치 간에 원활하게 이동합니다.

  1. 아래 강조 표시된 코드처럼 보이도록 클라이언트의 updateShape 메서드를 업데이트합니다.

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    
    </head>
    <body>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
            $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                    $shape = $("#shape"),
                    // Send a maximum of 10 messages per second 
                    // (mouse movements trigger a lot of messages)
                    messageFrequency = 10, 
                    // Determine how often to send messages in
                    // time to abide by the messageFrequency
                    updateRate = 1000 / messageFrequency, 
                    shapeModel = {
                        left: 0,
                        top: 0
                    },
                    moved = false;
    
                 moveShapeHub.client.updateShape = function (model) {
                     shapeModel = model;
                     // Gradually move the shape towards the new location (interpolate)
                     // The updateRate is used as the duration because by the time 
                     // we get to the next location we want to be at the "last" location
                     // We also clear the animation queue so that we start a new 
                     // animation and don't lag behind.
                     $shape.animate(shapeModel, { duration: updateRate, queue: false });
                };
    
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moved = true;
                        }
                    });
    
                    // Start the client side server update interval
                    setInterval(updateServerModel, updateRate);
                });
    
                function updateServerModel() {
                    // Only update server if we have a new movement
                    if (moved) {
                        moveShapeHub.server.updateModel(shapeModel);
                        moved = false;
                    }
                }
            });
    </script>
    
        <div id="shape" />
    </body>
    </html>
    

    위의 코드는 애니메이션 간격(이 경우 100밀리초)을 통해 이전 위치에서 서버가 제공한 새 위치로 셰이프를 이동합니다. 셰이프에서 실행 중인 이전 애니메이션은 새 애니메이션이 시작되기 전에 지워집니다.

  2. F5 키를 눌러 애플리케이션을 시작합니다. 페이지의 URL을 복사하여 두 번째 브라우저 창에 붙여넣습니다. 브라우저 창 중 하나에서 셰이프를 끌어다 옵니다. 다른 브라우저 창의 셰이프가 이동해야 합니다. 다른 창에서 셰이프의 이동은 들어오는 메시지당 한 번 설정되지 않고 시간이 지남에 따라 이동이 보간되므로 덜 육포로 표시됩니다.

    클라이언트에 부드러운 애니메이션을 추가할 때 한 브라우저 창에서 끌어온 셰이프가 다른 창에서 이동하는 방식을 보여 주는 스크린샷

추가 단계

이 자습서에서는 클라이언트와 서버 간에 고주파 메시지를 보내는 SignalR 애플리케이션을 프로그래밍하는 방법을 알아보았습니다. 이 통신 패러다임은 SignalR로 만든 ShootR 게임과 같은 온라인 게임 및 기타 시뮬레이션을 개발하는 데 유용합니다.

이 자습서에서 만든 전체 애플리케이션은 코드 갤러리에서 다운로드할 수 있습니다.

SignalR 개발 개념에 대해 자세히 알아보려면 SignalR 소스 코드 및 리소스에 대한 다음 사이트를 방문하세요.