자습서: SignalR 2를 사용 하 여 서버 브로드캐스트Tutorial: Server broadcast with SignalR 2

Warning

이 설명서는 최신 버전의 SignalR에는 적합 하지 않습니다.This documentation isn't for the latest version of SignalR. ASP.NET Core SignalR를 살펴보세요.Take a look at ASP.NET Core SignalR.

이 자습서에서는 ASP.NET SignalR 2를 사용 하 여 서버 브로드캐스트 기능을 제공 하는 웹 응용 프로그램을 만드는 방법을 보여 줍니다.This tutorial shows how to create a web application that uses ASP.NET SignalR 2 to provide server broadcast functionality. 서버 브로드캐스트는 서버에서 클라이언트로 전송 되는 통신을 시작 하는 것을 의미 합니다.Server broadcast means that the server starts the communications sent to clients.

이 자습서에서 만들 응용 프로그램은 서버 브로드캐스트 기능을 위한 일반적인 시나리오인 주식 시세 표시기를 시뮬레이션 합니다.The application that you'll create in this tutorial simulates a stock ticker, a typical scenario for server broadcast functionality. 정기적으로 서버는 주식 가격을 임의로 업데이트 하 고 연결 된 모든 클라이언트에 업데이트를 브로드캐스트합니다.Periodically, the server randomly updates stock prices and broadcast the updates to all connected clients. 브라우저에서 변경 및 열의 숫자 및 기호는 % 서버의 알림에 대 한 응답으로 동적으로 변경 됩니다.In the browser, the numbers and symbols in the Change and % columns dynamically change in response to notifications from the server. 동일한 URL에 대 한 추가 브라우저를 열 경우 모두 동일한 데이터 및 동일한 데이터 변경 내용을 동시에 표시 합니다.If you open additional browsers to the same URL, they all show the same data and the same changes to the data simultaneously.

웹 만들기

이 자습서에서는 다음을 수행합니다.In this tutorial, you:

  • 프로젝트 만들기Create the project
  • 서버 코드 설정Set up the server code
  • 서버 코드 검사Examine the server code
  • 클라이언트 코드 설정Set up the client code
  • 클라이언트 코드 검사Examine the client code
  • 애플리케이션 테스트Test the application
  • 로깅 사용Enable logging

Important

응용 프로그램을 빌드하는 단계를 수행 하지 않으려면 비어 있는 새 ASP.NET 웹 응용 프로그램 프로젝트에 SignalR 패키지를 설치 하면 됩니다.If you don't want to work through the steps of building the application, you can install the SignalR.Sample package in a new Empty ASP.NET Web Application project. 이 자습서의 단계를 수행 하지 않고 NuGet 패키지를 설치 하는 경우 readme.txt 파일의 지침을 따라야 합니다.If you install the NuGet package without performing the steps in this tutorial, you must follow the instructions in the readme.txt file. 패키지를 실행 하려면 설치 된 패키지에서 메서드를 호출 하는 OWIN startup 클래스를 추가 해야 ConfigureSignalR 합니다.To run the package you need to add an OWIN startup class which calls the ConfigureSignalR method in the installed package. OWIN startup 클래스를 추가 하지 않으면 오류가 표시 됩니다.You will receive an error if you do not add the OWIN startup class. 이 문서의 StockTicker 샘플 설치 섹션을 참조 하세요.See the Install the StockTicker sample section of this article.

사전 요구 사항Prerequisites

프로젝트 만들기Create the project

이 섹션에서는 Visual Studio 2017을 사용 하 여 빈 ASP.NET 웹 응용 프로그램을 만드는 방법을 보여 줍니다.This section shows how to use Visual Studio 2017 to create an empty ASP.NET Web Application.

  1. Visual Studio에서 ASP.NET 웹 응용 프로그램을 만듭니다.In Visual Studio, create an ASP.NET Web Application.

    웹 만들기

  2. 새 ASP.NET 웹 응용 프로그램-SignalR. StockTicker 창에서 선택 된 상태로 두고 확인을 선택 합니다.In the New ASP.NET Web Application - SignalR.StockTicker window, leave Empty selected and select OK.

서버 코드 설정Set up the server code

이 섹션에서는 서버에서 실행 되는 코드를 설정 합니다.In this section, you set up the code that runs on the server.

스톡 클래스 만들기Create the Stock class

먼저 주식 정보를 저장 하 고 전송 하는 데 사용할 스톡 모델 클래스를 만듭니다.You begin by creating the Stock model class that you'll use to store and transmit information about a stock.

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭 하 고 Add > 클래스추가를 선택 합니다.In Solution Explorer, right-click the project and select Add > Class.

  2. 클래스 이름을 스톡 으로 만들고 프로젝트에 추가 합니다.Name the class Stock and add it to the project.

  3. Stock.cs 파일의 코드를 다음 코드로 바꿉니다.Replace the code in the Stock.cs file with this code:

    using System;
    
    namespace SignalR.StockTicker
    {
        public class Stock
        {
            private decimal _price;
    
            public string Symbol { get; set; }
    
            public decimal Price
            {
                get
                {
                    return _price;
                }
                set
                {
                    if (_price == value)
                    {
                        return;
                    }
    
                    _price = value;
    
                    if (DayOpen == 0)
                    {
                        DayOpen = _price;
                    }
                }
            }
    
            public decimal DayOpen { get; private set; }
    
            public decimal Change
            {
                get
                {
                    return Price - DayOpen;
                }
            }
    
            public double PercentChange
            {
                get
                {
                    return (double)Math.Round(Change / Price, 4);
                }
            }
        }
    }
    

    주식을 만들 때 설정 하는 두 가지 속성은 Symbol (예: MSFT For Microsoft) 및 Price 입니다.The two properties that you'll set when you create stocks are Symbol (for example, MSFT for Microsoft) and Price. 다른 속성은 설정 하는 방법과 시기에 따라 달라 집니다 Price .The other properties depend on how and when you set Price. 처음 설정 하는 경우 Price 값이로 전파 DayOpen 됩니다.The first time you set Price, the value gets propagated to DayOpen. 그런 다음를 설정 하면 Price 앱에서 Change 와의 PercentChange 차이에 따라 및 속성 값을 계산 합니다 Price DayOpen .After that, when you set Price, the app calculates the Change and PercentChange property values based on the difference between Price and DayOpen.

StockTickerHub 및 StockTicker 클래스 만들기Create the StockTickerHub and StockTicker classes

SignalR Hub API를 사용 하 여 서버와 클라이언트 간의 상호 작용을 처리 합니다.You'll use the SignalR Hub API to handle server-to-client interaction. StockTickerHubSignalR 클래스에서 파생 되는 클래스는 Hub 클라이언트의 수신 연결 및 메서드 호출을 처리 합니다.A StockTickerHub class that derives from the SignalR Hub class will handle receiving connections and method calls from clients. 또한 주식 데이터를 유지 관리 하 고 개체를 실행 해야 Timer 합니다.You also need to maintain stock data and run a Timer object. Timer개체는 클라이언트 연결과 무관 하 게 가격 업데이트를 주기적으로 트리거합니다.The Timer object will periodically trigger price updates independent of client connections. 허브가 일시적 이기 때문에 클래스에 이러한 함수를 배치할 수 없습니다 Hub .You can't put these functions in a Hub class, because Hubs are transient. 앱은 Hub 클라이언트에서 서버로의 연결과 같은 허브의 각 태스크에 대 한 클래스 인스턴스를 만듭니다.The app creates a Hub class instance for each task on the hub, like connections and calls from the client to the server. 따라서 주식 데이터를 유지 하 고, 가격을 업데이트 하 고, 가격 업데이트를 브로드캐스팅하는 메커니즘이 별도의 클래스에서 실행 되어야 합니다.So the mechanism that keeps stock data, updates prices, and broadcasts the price updates has to run in a separate class. 클래스의 이름을로 StockTicker 합니다.You'll name the class StockTicker.

StockTicker에서 브로드캐스팅

클래스의 인스턴스 하나를 서버에서 실행 하려는 경우에만 StockTicker 각 인스턴스에서 singleton 인스턴스로 참조를 설정 해야 StockTickerHub StockTicker 합니다.You only want one instance of the StockTicker class to run on the server, so you'll need to set up a reference from each StockTickerHub instance to the singleton StockTicker instance. StockTicker클래스는 스톡 데이터를 포함 하 고 업데이트를 트리거 하지만 클래스가 아니라 클라이언트에 브로드캐스트합니다 StockTicker Hub .The StockTicker class has to broadcast to clients because it has the stock data and triggers updates, but StockTicker isn't a Hub class. StockTicker클래스는 SignalR Hub 연결 컨텍스트 개체에 대 한 참조를 가져와야 합니다.The StockTicker class has to get a reference to the SignalR Hub connection context object. 그런 다음 SignalR 연결 컨텍스트 개체를 사용 하 여 클라이언트에 브로드캐스트할 수 있습니다.It can then use the SignalR connection context object to broadcast to clients.

StockTickerHub.cs 만들기Create StockTickerHub.cs

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭 하 고 Add > 새 항목추가를 선택 합니다.In Solution Explorer, right-click the project and select Add > New Item.

  2. 새 항목 추가-SignalR. StockTicker에서 설치 됨 > Visual c # > > SignalR 를 선택한 다음 SignalR Hub 클래스 (v2) 를 선택 합니다.In Add New Item - SignalR.StockTicker, select Installed > Visual C# > Web > SignalR and then select SignalR Hub Class (v2).

  3. 클래스 이름을 StockTickerHub 로 추가 하 고 프로젝트에 추가 합니다.Name the class StockTickerHub and add it to the project.

    이 단계에서는 StockTickerHub.cs 클래스 파일을 만듭니다.This step creates the StockTickerHub.cs class file. 동시에 SignalR를 지 원하는 스크립트 파일 및 어셈블리 참조 집합을 프로젝트에 추가 합니다.Simultaneously, it adds a set of script files and assembly references that supports SignalR to the project.

  4. StockTickerHub.cs 파일의 코드를 다음 코드로 바꿉니다.Replace the code in the StockTickerHub.cs file with this code:

    using System.Collections.Generic;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    
    namespace SignalR.StockTicker
    {
        [HubName("stockTickerMini")]
        public class StockTickerHub : Hub
        {
            private readonly StockTicker _stockTicker;
    
            public StockTickerHub() : this(StockTicker.Instance) { }
    
            public StockTickerHub(StockTicker stockTicker)
            {
                _stockTicker = stockTicker;
            }
    
            public IEnumerable<Stock> GetAllStocks()
            {
                return _stockTicker.GetAllStocks();
            }
        }
    }
    
  5. 파일을 저장합니다.Save the file.

앱은 허브 클래스를 사용 하 여 클라이언트가 서버에서 호출할 수 있는 메서드를 정의 합니다.The app uses the Hub class to define methods the clients can call on the server. 메서드 하나를 정의 하 고 있습니다 GetAllStocks() .You're defining one method: GetAllStocks(). 클라이언트는 처음 서버에 연결할 때이 메서드를 호출 하 여 현재 가격으로 모든 주식의 목록을 가져옵니다.When a client initially connects to the server, it will call this method to get a list of all of the stocks with their current prices. 메서드는 IEnumerable<Stock> 메모리에서 데이터를 반환 하기 때문에 동기적으로 실행 하 고 반환할 수 있습니다.The method can run synchronously and return IEnumerable<Stock> because it's returning data from memory.

데이터베이스 조회 나 웹 서비스 호출과 같이 대기와 관련 된 작업을 수행 하 여 메서드가 데이터를 가져와야 하는 경우 Task<IEnumerable<Stock>> 에는를 반환 값으로 지정 하 여 비동기 처리를 사용 하도록 설정 합니다.If the method had to get the data by doing something that would involve waiting, like a database lookup or a web service call, you would specify Task<IEnumerable<Stock>> as the return value to enable asynchronous processing. 자세한 내용은 ASP.NET SignalR HUBS API 가이드-서버-비동기적으로 실행 하는 경우를 참조 하세요.For more information, see ASP.NET SignalR Hubs API Guide - Server - When to execute asynchronously.

HubName특성은 앱이 클라이언트의 JavaScript 코드에서 허브를 참조 하는 방법을 지정 합니다.The HubName attribute specifies how the app will reference the Hub in JavaScript code on the client. 클라이언트의 기본 이름은이 특성을 사용 하지 않는 경우 클래스 이름의 camelCase 버전입니다 .이 경우에는 stockTickerHub 입니다.The default name on the client if you don't use this attribute, is a camelCase version of the class name, which in this case would be stockTickerHub.

나중에 클래스를 만들 때 StockTicker 앱은 정적 속성에 해당 클래스의 단일 인스턴스를 만듭니다 Instance .As you'll see later when you create the StockTicker class, the app creates a singleton instance of that class in its static Instance property. 의 singleton 인스턴스는 StockTicker 연결 하거나 연결을 끊는 클라이언트 수에 관계 없이 메모리에 있습니다.That singleton instance of StockTicker is in memory no matter how many clients connect or disconnect. 이 인스턴스는 GetAllStocks() 메서드가 현재 주식 정보를 반환 하는 데 사용 하는 것입니다.That instance is what the GetAllStocks() method uses to return current stock information.

StockTicker.cs 만들기Create StockTicker.cs

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭 하 고 Add > 클래스추가를 선택 합니다.In Solution Explorer, right-click the project and select Add > Class.

  2. 클래스 이름을 StockTicker 로 추가 하 고 프로젝트에 추가 합니다.Name the class StockTicker and add it to the project.

  3. StockTicker.cs 파일의 코드를 다음 코드로 바꿉니다.Replace the code in the StockTicker.cs file with this code:

    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Threading;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    
    namespace SignalR.StockTicker
    {
        public class StockTicker
        {
            // Singleton instance
            private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
    
            private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();
    
            private readonly object _updateStockPricesLock = new object();
    
            //stock can go up or down by a percentage of this factor on each change
            private readonly double _rangePercent = .002;
    
            private readonly TimeSpan _updateInterval = TimeSpan.FromMilliseconds(250);
            private readonly Random _updateOrNotRandom = new Random();
    
            private readonly Timer _timer;
            private volatile bool _updatingStockPrices = false;
    
            private StockTicker(IHubConnectionContext<dynamic> clients)
            {
                Clients = clients;
    
                _stocks.Clear();
                var stocks = new List<Stock>
                {
                    new Stock { Symbol = "MSFT", Price = 30.31m },
                    new Stock { Symbol = "APPL", Price = 578.18m },
                    new Stock { Symbol = "GOOG", Price = 570.30m }
                };
                stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock));
    
                _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
    
            }
    
            public static StockTicker Instance
            {
                get
                {
                    return _instance.Value;
                }
            }
    
            private IHubConnectionContext<dynamic> Clients
            {
                get;
                set;
            }
    
            public IEnumerable<Stock> GetAllStocks()
            {
                return _stocks.Values;
            }
    
            private void UpdateStockPrices(object state)
            {
                lock (_updateStockPricesLock)
                {
                    if (!_updatingStockPrices)
                    {
                        _updatingStockPrices = true;
    
                        foreach (var stock in _stocks.Values)
                        {
                            if (TryUpdateStockPrice(stock))
                            {
                                BroadcastStockPrice(stock);
                            }
                        }
    
                        _updatingStockPrices = false;
                    }
                }
            }
    
            private bool TryUpdateStockPrice(Stock stock)
            {
                // Randomly choose whether to update this stock or not
                var r = _updateOrNotRandom.NextDouble();
                if (r > .1)
                {
                    return false;
                }
    
                // Update the stock price by a random factor of the range percent
                var random = new Random((int)Math.Floor(stock.Price));
                var percentChange = random.NextDouble() * _rangePercent;
                var pos = random.NextDouble() > .51;
                var change = Math.Round(stock.Price * (decimal)percentChange, 2);
                change = pos ? change : -change;
    
                stock.Price += change;
                return true;
            }
    
            private void BroadcastStockPrice(Stock stock)
            {
                Clients.All.updateStockPrice(stock);
            }
    
        }
    }
    

모든 스레드가 동일한 StockTicker 코드 인스턴스를 실행 하므로 StockTicker 클래스는 스레드로부터 안전 해야 합니다.Since all threads will be running the same instance of StockTicker code, the StockTicker class has to be thread-safe.

서버 코드 검사Examine the server code

서버 코드를 살펴보면 앱이 작동 하는 방식을 이해 하는 데 도움이 됩니다.If you examine the server code, it will help you understand how the app works.

단일 인스턴스를 정적 필드에 저장Storing the singleton instance in a static field

코드는 _instance Instance 클래스의 인스턴스를 사용 하 여 속성을 백업 하는 정적 필드를 초기화 합니다.The code initializes the static _instance field that backs the Instance property with an instance of the class. 생성자는 전용 이므로 앱에서 만들 수 있는 클래스의 유일한 인스턴스입니다.Because the constructor is private, it's the only instance of the class that the app can create. 앱은 필드에 초기화 지연을 사용 합니다 _instance .The app uses Lazy initialization for the _instance field. 성능상의 이유로는 그렇지 않습니다.It's not for performance reasons. 인스턴스를 만드는 것은 스레드로부터 안전한 지 확인 하는 것입니다.It's to make sure the instance creation is thread-safe.

private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));

public static StockTicker Instance
{
    get
    {
        return _instance.Value;
    }
}

클라이언트가 서버에 연결할 때마다 별도의 스레드에서 실행 되는 StockTickerHub 클래스의 새 인스턴스는 StockTicker.Instance 이전에 클래스에서 살펴본 것 처럼 정적 속성에서 StockTicker singleton 인스턴스를 가져옵니다 StockTickerHub .Each time a client connects to the server, a new instance of the StockTickerHub class running in a separate thread gets the StockTicker singleton instance from the StockTicker.Instance static property, as you saw earlier in the StockTickerHub class.

ConcurrentDictionary에 주식 데이터 저장Storing stock data in a ConcurrentDictionary

생성자는 _stocks 일부 샘플 재고 데이터를 사용 하 여 컬렉션을 초기화 하 고 GetAllStocks 주식을 반환 합니다.The constructor initializes the _stocks collection with some sample stock data, and GetAllStocks returns the stocks. 앞에서 살펴본 것 처럼이 주식 컬렉션은 StockTickerHub.GetAllStocks Hub 클라이언트가 호출할 수 있는 클래스의 서버 메서드인에 의해 반환 됩니다.As you saw earlier, this collection of stocks is returned by StockTickerHub.GetAllStocks, which is a server method in the Hub class that clients can call.

private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();
private StockTicker(IHubConnectionContext<dynamic> clients)
{
    Clients = clients;

    _stocks.Clear();
    var stocks = new List<Stock>
    {
        new Stock { Symbol = "MSFT", Price = 30.31m },
        new Stock { Symbol = "APPL", Price = 578.18m },
        new Stock { Symbol = "GOOG", Price = 570.30m }
    };
    stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock));

    _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
}

public IEnumerable<Stock> GetAllStocks()
{
    return _stocks.Values;
}

스톡 컬렉션은 스레드 보안을 위해 ConcurrentDictionary 형식으로 정의 됩니다.The stocks collection is defined as a ConcurrentDictionary type for thread safety. 또는 사전 개체를 사용 하 고 변경할 때 사전을 명시적으로 잠글 수 있습니다.As an alternative, you could use a Dictionary object and explicitly lock the dictionary when you make changes to it.

이 샘플 응용 프로그램의 경우 응용 프로그램 데이터를 메모리에 저장 하 고 앱이 인스턴스를 삭제할 때 데이터를 잃지는 것이 좋습니다 StockTicker .For this sample application, it's OK to store application data in memory and to lose the data when the app disposes of the StockTicker instance. 실제 응용 프로그램에서는 데이터베이스와 같은 백 엔드 데이터 저장소를 사용 합니다.In a real application, you would work with a back-end data store like a database.

정기적으로 주식 가격 업데이트Periodically updating stock prices

생성자는 Timer 임의 기준으로 주식 가격을 업데이트 하는 메서드를 주기적으로 호출 하는 개체를 시작 합니다.The constructor starts up a Timer object that periodically calls methods that update stock prices on a random basis.

_timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);

private void UpdateStockPrices(object state)
{
    lock (_updateStockPricesLock)
    {
        if (!_updatingStockPrices)
        {
            _updatingStockPrices = true;

            foreach (var stock in _stocks.Values)
            {
                if (TryUpdateStockPrice(stock))
                {
                    BroadcastStockPrice(stock);
                }
            }

            _updatingStockPrices = false;
        }
    }
}

private bool TryUpdateStockPrice(Stock stock)
{
    // Randomly choose whether to update this stock or not
    var r = _updateOrNotRandom.NextDouble();
    if (r > .1)
    {
        return false;
    }

    // Update the stock price by a random factor of the range percent
    var random = new Random((int)Math.Floor(stock.Price));
    var percentChange = random.NextDouble() * _rangePercent;
    var pos = random.NextDouble() > .51;
    var change = Math.Round(stock.Price * (decimal)percentChange, 2);
    change = pos ? change : -change;

    stock.Price += change;
    return true;
}

Timer``UpdateStockPrices상태 매개 변수에 null을 전달 하는를 호출 합니다.Timer calls UpdateStockPrices, which passes in null in the state parameter. 앱은 가격을 업데이트 하기 전에 개체에 대 한 잠금을 사용 _updateStockPricesLock 합니다.Before updating prices, the app takes a lock on the _updateStockPricesLock object. 코드는 다른 스레드가 이미 가격을 업데이트 하 고 있는지 확인 한 다음 TryUpdateStockPrice 목록의 각 재고에 대해를 호출 합니다.The code checks if another thread is already updating prices, and then it calls TryUpdateStockPrice on each stock in the list. TryUpdateStockPrice메서드는 주식 가격을 변경할 것인지 여부와 변경 되는 정도를 결정 합니다.The TryUpdateStockPrice method decides whether to change the stock price, and how much to change it. 주가 변경 되 면 앱은 BroadcastStockPrice 를 호출 하 여 주식 가격 변경을 모든 연결 된 클라이언트에 브로드캐스트합니다.If the stock price changes, the app calls BroadcastStockPrice to broadcast the stock price change to all connected clients.

_updatingStockPrices 일시적 으로 지정 된 플래그는 스레드로부터 안전 하 게 보호 되도록 합니다.The _updatingStockPrices flag designated volatile to make sure it is thread-safe.

private volatile bool _updatingStockPrices = false;

실제 응용 프로그램에서 메서드는 TryUpdateStockPrice 웹 서비스를 호출 하 여 가격을 조회 합니다.In a real application, the TryUpdateStockPrice method would call a web service to look up the price. 이 코드에서 앱은 난수 생성기를 사용 하 여 임의로 변경을 수행 합니다.In this code, the app uses a random number generator to make changes randomly.

StockTicker 클래스가 클라이언트에 브로드캐스트할 수 있도록 SignalR 컨텍스트 가져오기Getting the SignalR context so that the StockTicker class can broadcast to clients

여기서는 가격 변경이 개체에서 발생 하기 때문에 StockTicker updateStockPrice 연결 된 모든 클라이언트에서 메서드를 호출 해야 하는 개체입니다.Because the price changes originate here in the StockTicker object, it's the object that needs to call an updateStockPrice method on all connected clients. 클래스에는 Hub 클라이언트 메서드를 호출 하기 위한 API가 있지만 StockTicker 클래스에서 파생 되지 않고 개체에 대 한 Hub 참조가 없습니다 Hub .In a Hub class, you have an API for calling client methods, but StockTicker doesn't derive from the Hub class and doesn't have a reference to any Hub object. 연결 된 클라이언트에 브로드캐스트하려면 클래스는 StockTicker 클래스에 대 한 SignalR context 인스턴스를 가져온 StockTickerHub 다음이를 사용 하 여 클라이언트에서 메서드를 호출 해야 합니다.To broadcast to connected clients, the StockTicker class has to get the SignalR context instance for the StockTickerHub class and use that to call methods on clients.

이 코드는 singleton 클래스 인스턴스를 만들고 해당 참조를 생성자에 전달 하 고 생성자가이를 속성에 배치 하는 경우 SignalR 컨텍스트에 대 한 참조를 가져옵니다 Clients .The code gets a reference to the SignalR context when it creates the singleton class instance, passes that reference to the constructor, and the constructor puts it in the Clients property.

컨텍스트를 한 번만 가져오려고 하는 두 가지 이유는 다음과 같습니다. 컨텍스트 가져오기 작업은 비용이 많이 들고, 한 번 가져오면 앱이 클라이언트에 전송 되는 메시지의 순서를 유지 하도록 할 수 있습니다.There are two reasons why you want to get the context only once: getting the context is an expensive task, and getting it once makes sure the app preserves the intended order of messages sent to the clients.

private readonly static Lazy<StockTicker> _instance =
    new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));

private StockTicker(IHubConnectionContext<dynamic> clients)
{
    Clients = clients;

    // Remainder of constructor ...
}

private IHubConnectionContext<dynamic> Clients
{
    get;
    set;
}

private void BroadcastStockPrice(Stock stock)
{
    Clients.All.updateStockPrice(stock);
}

Clients컨텍스트의 속성을 가져와서 속성에 배치 StockTickerClient 하면 클래스에서와 동일 하 게 보이는 클라이언트 메서드를 호출 하는 코드를 작성할 수 있습니다 Hub .Getting the Clients property of the context and putting it in the StockTickerClient property lets you write code to call client methods that looks the same as it would in a Hub class. 예를 들어, 작성할 수 있는 모든 클라이언트에 브로드캐스트할 수 있습니다 Clients.All.updateStockPrice(stock) .For instance, to broadcast to all clients you can write Clients.All.updateStockPrice(stock).

updateStockPrice에서 호출 하는 메서드가 BroadcastStockPrice 아직 존재 하지 않습니다.The updateStockPrice method that you're calling in BroadcastStockPrice doesn't exist yet. 나중에 클라이언트에서 실행 되는 코드를 작성할 때 추가 합니다.You'll add it later when you write code that runs on the client. updateStockPrice가 동적 이므로이를 참조할 수 있습니다 Clients.All . 즉, 앱이 런타임에 식을 평가 합니다.You can refer to updateStockPrice here because Clients.All is dynamic, which means the app will evaluate the expression at runtime. 이 메서드 호출이 실행 되 면 SignalR는 메서드 이름과 매개 변수 값을 클라이언트에 보내고, 클라이언트에 라는 메서드가 있으면 updateStockPrice 앱은 해당 메서드를 호출 하 고 매개 변수 값을 전달 합니다.When this method call executes, SignalR will send the method name and the parameter value to the client, and if the client has a method named updateStockPrice, the app will call that method and pass the parameter value to it.

Clients.All 모든 클라이언트에 보내기를 의미 합니다.Clients.All means send to all clients. SignalR는 보낼 클라이언트 또는 클라이언트 그룹을 지정 하는 다른 옵션을 제공 합니다.SignalR gives you other options to specify which clients or groups of clients to send to. 자세한 내용은 HubConnectionContext를 참조 하세요.For more information, see HubConnectionContext.

SignalR 경로 등록Register the SignalR route

서버에서 가로채서 SignalR에 지시할 URL을 알고 있어야 합니다.The server needs to know which URL to intercept and direct to SignalR. 이렇게 하려면 OWIN startup 클래스를 추가 합니다.To do that, add an OWIN startup class:

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭 하 고 Add > 새 항목추가를 선택 합니다.In Solution Explorer, right-click the project and select Add > New Item.

  2. 새 항목 추가-SignalR. StockTicker 에서 설치 된 > Visual c # > 을 선택 하 고 OWIN 시작 클래스를 선택 합니다.In Add New Item - SignalR.StockTicker select Installed > Visual C# > Web and then select OWIN Startup Class.

  3. 클래스 이름을 시작 으로 하 고 확인을 선택 합니다.Name the class Startup and select OK.

  4. Startup.cs 파일의 기본 코드를 다음 코드로 바꿉니다.Replace the default code in the Startup.cs file with this code:

    using System;
    using System.Threading.Tasks;
    using Microsoft.Owin;
    using Owin;
    
    [assembly: OwinStartup(typeof(SignalR.StockTicker.Startup))]
    
    namespace SignalR.StockTicker
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                // Any connection or hub wire up and configuration should go here
                app.MapSignalR();
            }
    
        }
    }
    

이제 서버 코드를 설정 했습니다.You have now finished setting up the server code. 다음 섹션에서는 클라이언트를 설정 합니다.In the next section, you'll set up the client.

클라이언트 코드 설정Set up the client code

이 섹션에서는 클라이언트에서 실행 되는 코드를 설정 합니다.In this section, you set up the code that runs on the client.

HTML 페이지 및 JavaScript 파일 만들기Create the HTML page and JavaScript file

HTML 페이지에 데이터가 표시 되 고 JavaScript 파일이 데이터를 구성 합니다.The HTML page will display the data and the JavaScript file will organize the data.

StockTicker.html 만들기Create StockTicker.html

먼저 HTML 클라이언트를 추가 합니다.First, you'll add the HTML client.

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭 하 고 Add > HTML 페이지추가를 선택 합니다.In Solution Explorer, right-click the project and select Add > HTML Page.

  2. 파일 이름을 StockTicker 로 선택 하 고 확인을선택 합니다.Name the file StockTicker and select OK.

  3. StockTicker.html 파일의 기본 코드를 다음 코드로 바꿉니다.Replace the default code in the StockTicker.html file with this code:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>ASP.NET SignalR Stock Ticker</title>
        <style>
            body {
                font-family: 'Segoe UI', Arial, Helvetica, sans-serif;
                font-size: 16px;
            }
            #stockTable table {
                border-collapse: collapse;
            }
                #stockTable table th, #stockTable table td {
                    padding: 2px 6px;
                }
                #stockTable table td {
                    text-align: right;
                }
            #stockTable .loading td {
                text-align: left;
            }
        </style>
    </head>
    <body>
        <h1>ASP.NET SignalR Stock Ticker Sample</h1>
    
        <h2>Live Stock Table</h2>
        <div id="stockTable">
            <table border="1">
                <thead>
                    <tr><th>Symbol</th><th>Price</th><th>Open</th><th>Change</th><th>%</th></tr>
                </thead>
                <tbody>
                    <tr class="loading"><td colspan="5">loading...</td></tr>
                </tbody>
            </table>
        </div>
    
        <!--Script references. -->
        <!--Reference the jQuery library. -->
        <script src="/Scripts/jquery-1.10.2.min.js" ></script>
        <!--Reference the SignalR library. -->
        <script src="/Scripts/jquery.signalR-2.1.0.js"></script>
        <!--Reference the autogenerated SignalR hub script. -->
        <script src="/signalr/hubs"></script>
        <!--Reference the StockTicker script. -->
        <script src="StockTicker.js"></script>
    </body>
    </html>
    

    HTML은 5 개의 열, 머리글 행 및 5 개 열 전체에 걸쳐 있는 단일 셀을 포함 하는 데이터 행을 포함 하는 테이블을 만듭니다.The HTML creates a table with five columns, a header row, and a data row with a single cell that spans all five columns. 데이터 행에 "로드 중 ..."이 표시 됩니다. 앱이 시작 되는 일시적입니다.The data row shows "loading..." momentarily when the app starts. JavaScript 코드는 해당 행을 제거 하 고 서버에서 검색 된 주식 데이터를 사용 하 여 해당 행을 추가 합니다.JavaScript code will remove that row and add in its place rows with stock data retrieved from the server.

    스크립트 태그는 다음을 지정 합니다.The script tags specify:

    • JQuery 스크립트 파일입니다.The jQuery script file.

    • SignalR core 스크립트 파일입니다.The SignalR core script file.

    • SignalR 프록시 스크립트 파일입니다.The SignalR proxies script file.

    • 나중에 만들 StockTicker 스크립트 파일입니다.A StockTicker script file that you'll create later.

    앱은 SignalR 프록시 스크립트 파일을 동적으로 생성 합니다.The app dynamically generates the SignalR proxies script file. "/Signalr/hubs" URL을 지정 하 고 허브 클래스 (이 경우)의 메서드에 대 한 프록시 메서드를 정의 StockTickerHub.GetAllStocks 합니다.It specifies the "/signalr/hubs" URL and defines proxy methods for the methods on the Hub class, in this case, for StockTickerHub.GetAllStocks. 원한다 면 SignalR 유틸리티를 사용 하 여이 JavaScript 파일을 수동으로 생성할 수 있습니다.If you prefer, you can generate this JavaScript file manually by using SignalR Utilities. 메서드 호출에서 동적 파일 생성을 사용 하지 않도록 설정 하는 것을 잊지 마세요 MapHubs .Don't forget to disable dynamic file creation in the MapHubs method call.

  4. 솔루션 탐색기에서 스크립트를 확장 합니다.In Solution Explorer, expand Scripts.

    JQuery 및 SignalR에 대 한 스크립트 라이브러리는 프로젝트에 표시 됩니다.Script libraries for jQuery and SignalR are visible in the project.

    Important

    패키지 관리자가 SignalR 스크립트의 최신 버전을 설치 합니다.The package manager will install a later version of the SignalR scripts.

  5. 프로젝트의 스크립트 파일 버전에 해당 하는 코드 블록의 스크립트 참조를 업데이트 합니다.Update the script references in the code block to correspond to the versions of the script files in the project.

  6. 솔루션 탐색기에서 StockTicker.html을 마우스 오른쪽 단추로 클릭 한 다음 시작 페이지로 설정을 선택 합니다.In Solution Explorer, right-click StockTicker.html, and then select Set as Start Page.

StockTicker.js 만들기Create StockTicker.js

이제 JavaScript 파일을 만듭니다.Now create the JavaScript file.

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭 하 고 Add > JavaScript 파일추가를 선택 합니다.In Solution Explorer, right-click the project and select Add > JavaScript File.

  2. 파일 이름을 StockTicker 로 선택 하 고 확인을선택 합니다.Name the file StockTicker and select OK.

  3. StockTicker.js 파일에 다음 코드를 추가 합니다.Add this code to the StockTicker.js file:

    // A simple templating method for replacing placeholders enclosed in curly braces.
    if (!String.prototype.supplant) {
        String.prototype.supplant = function (o) {
            return this.replace(/{([^{}]*)}/g,
                function (a, b) {
                    var r = o[b];
                    return typeof r === 'string' || typeof r === 'number' ? r : a;
                }
            );
        };
    }
    
    $(function () {
    
        var ticker = $.connection.stockTickerMini, // the generated client-side hub proxy
            up = '▲',
            down = '▼',
            $stockTable = $('#stockTable'),
            $stockTableBody = $stockTable.find('tbody'),
            rowTemplate = '<tr data-symbol="{Symbol}"><td>{Symbol}</td><td>{Price}</td><td>{DayOpen}</td><td>{Direction} {Change}</td><td>{PercentChange}</td></tr>';
    
        function formatStock(stock) {
            return $.extend(stock, {
                Price: stock.Price.toFixed(2),
                PercentChange: (stock.PercentChange * 100).toFixed(2) + '%',
                Direction: stock.Change === 0 ? '' : stock.Change >= 0 ? up : down
            });
        }
    
        function init() {
            ticker.server.getAllStocks().done(function (stocks) {
                $stockTableBody.empty();
                $.each(stocks, function () {
                    var stock = formatStock(this);
                    $stockTableBody.append(rowTemplate.supplant(stock));
                });
            });
        }
    
        // Add a client-side hub method that the server will call
        ticker.client.updateStockPrice = function (stock) {
            var displayStock = formatStock(stock),
                $row = $(rowTemplate.supplant(displayStock));
    
            $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
                .replaceWith($row);
            }
    
        // Start the connection
        $.connection.hub.start().done(init);
    
    });
    

클라이언트 코드 검사Examine the client code

클라이언트 코드를 살펴보면 클라이언트 코드가 서버 코드와 상호 작용 하 여 앱이 작동 하도록 하는 방법을 배우는 데 도움이 됩니다.If you examine the client code, it will help you learn how the client code interacts with the server code to make the app work.

연결 시작Starting the connection

$.connection SignalR 프록시를 참조 합니다.$.connection refers to the SignalR proxies. 이 코드는 클래스의 프록시에 대 한 참조를 가져와 StockTickerHub ticker 변수에 넣습니다.The code gets a reference to the proxy for the StockTickerHub class and puts it in the ticker variable. 프록시 이름은 특성에 의해 설정 된 이름입니다 HubName .The proxy name is the name that was set by the HubName attribute:

var ticker = $.connection.stockTickerMini
[HubName("stockTickerMini")]
public class StockTickerHub : Hub

모든 변수 및 함수를 정의한 후 파일의 마지막 코드 줄은 SignalR 함수를 호출 하 여 SignalR 연결을 초기화 합니다 start .After you define all the variables and functions, the last line of code in the file initializes the SignalR connection by calling the SignalR start function. start함수는 비동기적으로 실행 되 고 jQuery 지연 된 개체를 반환 합니다.The start function executes asynchronously and returns a jQuery Deferred object. Done 함수를 호출 하 여 앱이 비동기 작업을 완료할 때 호출할 함수를 지정할 수 있습니다.You can call the done function to specify the function to call when the app finishes the asynchronous action.

$.connection.hub.start().done(init);

모든 주식 가져오기Getting all the stocks

init함수는 getAllStocks 서버에서 함수를 호출 하 고 서버에서 반환 하는 정보를 사용 하 여 스톡 테이블을 업데이트 합니다.The init function calls the getAllStocks function on the server and uses the information that the server returns to update the stock table. 서버에서 메서드 이름이 파스칼식 대/소문자를 사용 하는 경우에도 기본적으로 클라이언트에서 camelCasing를 사용 해야 합니다.Notice that, by default, you have to use camelCasing on the client even though the method name is pascal-cased on the server. CamelCasing 규칙은 개체가 아닌 메서드에만 적용 됩니다.The camelCasing rule only applies to methods, not objects. 예를 들어, 또는가 stock.Symbol 아닌 and를 참조 stock.Price stock.symbol stock.price 합니다.For example, you refer to stock.Symbol and stock.Price, not stock.symbol or stock.price.

function init() {
    ticker.server.getAllStocks().done(function (stocks) {
        $stockTableBody.empty();
        $.each(stocks, function () {
            var stock = formatStock(this);
            $stockTableBody.append(rowTemplate.supplant(stock));
        });
    });
}
public IEnumerable<Stock> GetAllStocks()
{
    return _stockTicker.GetAllStocks();
}

init메서드에서 응용 프로그램은를 호출 하 여 formatStock 개체의 속성 형식을 지정 하 stock 고,를 호출 하 여 supplant 변수의 자리 표시자를 rowTemplate stock 개체 속성 값으로 대체 하는 방식으로 서버에서 받은 각 스톡 개체의 테이블 행에 대 한 HTML을 만듭니다.In the init method, the app creates HTML for a table row for each stock object received from the server by calling formatStock to format properties of the stock object, and then by calling supplant to replace placeholders in the rowTemplate variable with the stock object property values. 그러면 결과 HTML이 스톡 테이블에 추가 됩니다.The resulting HTML is then appended to the stock table.

Note

init callback 비동기 함수가 완료 된 후 실행 되는 함수로에 전달 하 여를 호출 start 합니다.You call init by passing it in as a callback function that executes after the asynchronous start function finishes. init를 호출한 후 별도의 JavaScript 문으로 호출한 경우 start 함수는 시작 함수가 연결 설정을 완료할 때까지 기다리지 않고 즉시 실행 되기 때문에 실패 합니다.If you called init as a separate JavaScript statement after calling start, the function would fail because it would run immediately without waiting for the start function to finish establishing the connection. 이 경우 init 함수는 getAllStocks 앱이 서버 연결을 설정 하기 전에 함수를 호출 하려고 시도 합니다.In that case, the init function would try to call the getAllStocks function before the app establishes a server connection.

업데이트 된 주식 가격Getting updated stock prices

서버에서 주식 시세를 변경 하면 연결 된 클라이언트에서를 호출 합니다 updateStockPrice .When the server changes a stock's price, it calls the updateStockPrice on connected clients. 앱은 stockTicker 서버에서 호출할 수 있도록 프록시의 client 속성에 함수를 추가 합니다.The app adds the function to the client property of the stockTicker proxy to make it available to calls from the server.

ticker.client.updateStockPrice = function (stock) {
    var displayStock = formatStock(stock),
        $row = $(rowTemplate.supplant(displayStock));

    $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
        .replaceWith($row);
    }

updateStockPrice함수는 함수에서와 동일한 방법으로 서버에서 받은 스톡 개체의 형식을 테이블 행으로 지정 합니다 init .The updateStockPrice function formats a stock object received from the server into a table row the same way as in the init function. 테이블에 행을 추가 하는 대신 테이블에서 주식의 현재 행을 찾아 해당 행을 새 행으로 바꿉니다.Instead of appending the row to the table, it finds the stock's current row in the table and replaces that row with the new one.

애플리케이션 테스트Test the application

앱이 작동 하는지 테스트할 수 있습니다.You can test the app to make sure it's working. 모든 브라우저 창에서 주식 가격이 변동 인 라이브 재고 테이블이 표시 됩니다.You'll see all browser windows display the live stock table with stock prices fluctuating.

  1. 도구 모음에서 스크립트 디버깅 을 사용 하도록 설정 하 고 재생 단추를 선택 하 여 디버그 모드에서 앱을 실행 합니다.In the toolbar, turn on Script Debugging and then select the play button to run the app in Debug mode.

    디버깅 모드를 켜고 재생을 선택 하는 사용자의 스크린샷

    라이브 재고 테이블을 표시 하는 브라우저 창이 열립니다.A browser window will open displaying the Live Stock Table. Stock 테이블에는 처음에 "로드 중 ..."이 표시 됩니다. 그런 다음, 짧은 시간 후에 앱은 초기 주식 데이터를 표시 한 다음 주가 변경 되기 시작 합니다.The stock table initially shows the "loading..." line, then, after a short time, the app shows the initial stock data, and then the stock prices start to change.

  2. 브라우저에서 URL을 복사 하 여 다른 두 브라우저를 열고 Url을 주소 표시줄에 붙여넣습니다.Copy the URL from the browser, open two other browsers, and paste the URLs into the address bars.

    초기 주식 표시는 첫 번째 브라우저와 동일 하며 변경 내용이 동시에 발생 합니다.The initial stock display is the same as the first browser and changes happen simultaneously.

  3. 모든 브라우저를 닫고 새 브라우저를 연 다음 동일한 URL로 이동 합니다.Close all browsers, open a new browser, and go to the same URL.

    StockTicker singleton 개체가 서버에서 계속 실행 됩니다.The StockTicker singleton object continued to run in the server. 라이브 스톡 테이블 은 주가 계속 변경 된 것을 보여 줍니다.The Live Stock Table shows that the stocks have continued to change. 변경 수치가 0 인 초기 테이블은 표시 되지 않습니다.You don't see the initial table with zero change figures.

  4. 브라우저를 닫습니다.Close the browser.

로깅 사용Enable logging

SignalR에는 문제 해결을 지원 하기 위해 클라이언트에서 사용할 수 있는 기본 제공 로깅 함수가 있습니다.SignalR has a built-in logging function that you can enable on the client to aid in troubleshooting. 이 섹션에서는 로깅을 사용 하도록 설정 하 고 SignalR에서 사용 하는 전송 방법에 대 한 로그를 표시 하는 예제를 표시 합니다.In this section, you enable logging and see examples that show how logs tell you which of the following transport methods SignalR is using:

SignalR는 지정 된 모든 연결에 대해 서버와 클라이언트가 지 원하는 최상의 전송 방법을 선택 합니다.For any given connection, SignalR chooses the best transport method that both the server and the client support.

  1. StockTicker.js를 엽니다.Open StockTicker.js.

  2. 이 강조 표시 된 코드 줄을 추가 하 여 파일 끝에서 연결을 초기화 하는 코드 바로 앞에서 로깅을 사용 하도록 설정 합니다.Add this highlighted line of code to enable logging immediately before the code that initializes the connection at the end of the file:

    // Start the connection
    $.connection.hub.logging = true;
    $.connection.hub.start().done(init);
    
  3. F5 키를 눌러 프로젝트를 실행 합니다.Press F5 to run the project.

  4. 브라우저의 개발자 도구 창을 열고 콘솔을 선택 하 여 로그를 확인 합니다.Open your browser's developer tools window, and select the Console to see the logs. 새 연결에 대 한 전송 방법을 협상 하는 SignalR의 로그를 확인 하려면 페이지를 새로 고쳐야 할 수도 있습니다.You might have to refresh the page to see the logs of SignalR negotiating the transport method for a new connection.

    • Windows 8 (IIS 8)에서 Internet Explorer 10을 실행 하는 경우 전송 방법은 websocket입니다.If you're running Internet Explorer 10 on Windows 8 (IIS 8), the transport method is WebSockets.

    • Windows 7 (IIS 7.5)에서 Internet Explorer 10을 실행 하는 경우 전송 방법은 iframe입니다.If you're running Internet Explorer 10 on Windows 7 (IIS 7.5), the transport method is iframe.

    • Windows 8 (IIS 8)에서 Firefox 19를 실행 하는 경우 전송 방법은 websocket입니다.If you're running Firefox 19 on Windows 8 (IIS 8), the transport method is WebSockets.

      Tip

      Firefox에서 Firebug 추가 기능을 설치 하 여 콘솔 창을 가져옵니다.In Firefox, install the Firebug add-in to get a Console window.

    • Windows 7 (IIS 7.5)에서 Firefox 19를 실행 하는 경우 전송 방법은 서버에서 보낸 이벤트입니다.If you're running Firefox 19 on Windows 7 (IIS 7.5), the transport method is server-sent events.

StockTicker 샘플 설치Install the StockTicker sample

SignalR 는 StockTicker 응용 프로그램을 설치 합니다.The Microsoft.AspNet.SignalR.Sample installs the StockTicker application. NuGet 패키지에는 처음부터 만든 단순화 된 버전 보다 더 많은 기능이 포함 되어 있습니다.The NuGet package includes more features than the simplified version that you created from scratch. 자습서의이 섹션에서는 NuGet 패키지를 설치 하 고 새 기능 및이를 구현 하는 코드를 검토 합니다.In this section of the tutorial, you install the NuGet package and review the new features and the code that implements them.

Important

이 자습서의 이전 단계를 수행 하지 않고 패키지를 설치 하는 경우 프로젝트에 OWIN startup 클래스를 추가 해야 합니다.If you install the package without performing the earlier steps of this tutorial, you must add an OWIN startup class to your project. NuGet 패키지에 대 한이 readme.txt 파일에서는이 단계에 대해 설명 합니다.This readme.txt file for the NuGet package explains this step.

SignalR NuGet 패키지를 설치 합니다.Install the SignalR.Sample NuGet package

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭하고 NuGet 패키지 관리를 선택합니다.In Solution Explorer, right-click the project and select Manage NuGet Packages.

  2. NuGet 패키지 관리자: SignalR. StockTicker에서 찾아보기를 선택 합니다.In NuGet Package manager: SignalR.StockTicker, select Browse.

  3. 패키지 원본에서 nuget.org를 선택 합니다.From Package source, select nuget.org.

  4. 검색 상자에 SignalR 를 입력 하 고 SignalR > 설치를 선택 합니다.Enter SignalR.Sample in the search box and select Microsoft.AspNet.SignalR.Sample > Install.

  5. 솔루션 탐색기에서 SignalR 폴더를 확장 합니다.In Solution Explorer, expand the SignalR.Sample folder.

    SignalR 패키지를 설치 하면 폴더와 해당 내용이 생성 됩니다.Installing the SignalR.Sample package created the folder and its contents.

  6. SignalR 폴더에서 StockTicker.html을 마우스 오른쪽 단추로 클릭 한 다음 시작 페이지로 설정을 선택 합니다.In the SignalR.Sample folder, right-click StockTicker.html, and then select Set As Start Page.

    Note

    SignalR NuGet 패키지를 설치 하면 Scripts 폴더에 있는 jQuery 버전이 변경 될 수 있습니다.Installing The SignalR.Sample NuGet package might change the version of jQuery that you have in your Scripts folder. 패키지가 SignalR 폴더에 설치 하는 새 StockTicker.html 파일은 패키지가 설치 하는 jQuery 버전과 동기화 되지만 원래 StockTicker.html 파일을 다시 실행 하려는 경우 먼저 스크립트 태그에서 jquery 참조를 업데이트 해야 할 수 있습니다.The new StockTicker.html file that the package installs in the SignalR.Sample folder will be in sync with the jQuery version that the package installs, but if you want to run your original StockTicker.html file again, you might have to update the jQuery reference in the script tag first.

애플리케이션 실행Run the application

첫 번째 앱에서 살펴본 테이블에는 유용한 기능이 있습니다.The table that you saw in the first app had useful features. 전체 주식 시세 응용 프로그램에는 새 기능이 표시 됩니다. 가로 스크롤 창에서는 주식 데이터 및 주식으로 색을 변경 하는 주가 표시 됩니다.The full stock ticker application shows new features: a horizontally scrolling window that shows the stock data and stocks that change color as they rise and fall.

  1. F5 키를 눌러 앱을 실행 합니다.Press F5 to run the app.

    앱을 처음으로 실행 하는 경우 "market"은 "닫힘" 이며, 스크롤하지 않는 정적 테이블과 표시기 창이 표시 됩니다.When you run the app for the first time, the "market" is "closed" and you see a static table and a ticker window that isn't scrolling.

  2. 시장 열기를 선택 합니다.Select Open Market.

    라이브 표시기의 스크린샷

    • 라이브 주식 시세 표시기 상자를 가로로 스크롤하면 서버에서 정기적으로 주식 시세 변경을 정기적으로 브로드캐스트 하기 시작 합니다.The Live Stock Ticker box starts to scroll horizontally, and the server starts to periodically broadcast stock price changes on a random basis.

    • 주가 변경 될 때마다 앱은 Live Stock 테이블과 라이브 주식 시세 표시기를 모두 업데이트 합니다.Each time a stock price changes, the app updates both the Live Stock Table and the Live Stock Ticker.

    • 주가 변화 하는 경우 앱은 녹색 배경의 재고를 표시 합니다.When a stock's price change is positive, the app shows the stock with a green background.

    • 변경이 음수 이면 앱은 빨간색 배경의 재고를 표시 합니다.When the change is negative, the app shows the stock with a red background.

  3. 시장 종결을 선택 합니다.Select Close Market.

    • 테이블 업데이트가 중지 됩니다.The table updates stop.

    • 표시기가 스크롤을 중지 합니다.The ticker stops scrolling.

  4. 재설정을 선택합니다.Select Reset.

    • 모든 재고 데이터를 다시 설정 합니다.All stock data is reset.

    • 앱은 가격 변경이 시작 되기 전에 초기 상태를 복원 합니다.The app restores the initial state before price changes started.

  5. 브라우저에서 URL을 복사 하 여 다른 두 브라우저를 열고 Url을 주소 표시줄에 붙여넣습니다.Copy the URL from the browser, open two other browsers, and paste the URLs into the address bars.

  6. 각 브라우저에서 동시에 동일한 데이터를 동적으로 업데이트 하는 것을 볼 수 있습니다.You see the same data dynamically updated at the same time in each browser.

  7. 컨트롤 중 하나를 선택 하면 모든 브라우저가 동시에 동일한 방식으로 응답 합니다.When you select any of the controls, all browsers respond the same way at the same time.

라이브 주식 종목 표시Live Stock Ticker display

라이브 주식 시세 표시는 <div> CSS 스타일로 한 줄로 서식 지정 된 요소에서 순서가 지정 되지 않은 목록입니다.The Live Stock Ticker display is an unordered list in a <div> element formatted into a single line by CSS styles. 앱은 템플릿 문자열에서 자리 표시자를 대체 <li> 하 고 요소를 <li> 요소에 동적으로 추가 하 여 테이블과 동일한 방식으로 종목을 초기화 하 고 업데이트 합니다. <ul>The app initializes and updates the ticker the same way as the table: by replacing placeholders in an <li> template string and dynamically adding the <li> elements to the <ul> element. 앱은 jQuery 함수를 사용 하 여에서 순서가 지정 되지 않은 animate 목록의 왼쪽 여백을 변경 하는 스크롤을 포함 합니다 <div> .The app includes scrolling by using the jQuery animate function to vary the margin-left of the unordered list within the <div>.

SignalR StockTicker.htmlSignalR.Sample StockTicker.html

주식 시세 HTML 코드:The stock ticker HTML code:

<h2>Live Stock Ticker</h2>
<div id="stockTicker">
    <div class="inner">
        <ul>
            <li class="loading">loading...</li>
        </ul>
    </div>
</div>

SignalR StockTickerSignalR.Sample StockTicker.css

주식 시세 CSS 코드:The stock ticker CSS code:

#stockTicker {
    overflow: hidden;
    width: 450px;
    height: 24px;
    border: 1px solid #999;
    }

    #stockTicker .inner {
        width: 9999px;
    }

    #stockTicker ul {
        display: inline-block;
        list-style-type: none;
        margin: 0;
        padding: 0;
    }

    #stockTicker li {
        display: inline-block;
        margin-right: 8px;   
    }

    /*<li data-symbol="{Symbol}"><span class="symbol">{Symbol}</span><span class="price">{Price}</span><span class="change">{PercentChange}</span></li>*/
    #stockTicker .symbol {
        font-weight: bold;
    }

    #stockTicker .change {
        font-style: italic;
    }

SignalR SignalR.StockTicker.jsSignalR.Sample SignalR.StockTicker.js

스크롤할 수 있도록 하는 jQuery 코드:The jQuery code that makes it scroll:

function scrollTicker() {
    var w = $stockTickerUl.width();
    $stockTickerUl.css({ marginLeft: w });
    $stockTickerUl.animate({ marginLeft: -w }, 15000, 'linear', scrollTicker);
}

클라이언트에서 호출할 수 있는 서버에 대 한 추가 메서드Additional methods on the server that the client can call

앱에 유연성을 추가 하기 위해 앱에서 호출할 수 있는 추가 방법이 있습니다.To add flexibility to the app, there are additional methods the app can call.

SignalR StockTickerHub.csSignalR.Sample StockTickerHub.cs

StockTickerHub클래스는 클라이언트에서 호출할 수 있는 다음 네 가지 메서드를 추가로 정의 합니다.The StockTickerHub class defines four additional methods that the client can call:

public string GetMarketState()
{
    return _stockTicker.MarketState.ToString();
}

public void OpenMarket()
{
    _stockTicker.OpenMarket();
}

public void CloseMarket()
{
    _stockTicker.CloseMarket();
}

public void Reset()
{
    _stockTicker.Reset();
}

앱은 OpenMarket CloseMarket Reset 페이지 맨 위에 있는 단추에 대 한 응답으로, 및를 호출 합니다.The app calls OpenMarket, CloseMarket, and Reset in response to the buttons at the top of the page. 모든 클라이언트에 즉시 전파 되는 상태 변경을 트리거하는 한 클라이언트의 패턴을 보여 줍니다.They demonstrate the pattern of one client triggering a change in state immediately propagated to all clients. 이러한 각 메서드는 StockTicker 시장 상태를 변경 하 고 새 상태를 브로드캐스팅하는 클래스의 메서드를 호출 합니다.Each of these methods calls a method in the StockTicker class that causes the market state change and then broadcasts the new state.

SignalR StockTicker.csSignalR.Sample StockTicker.cs

클래스에서 StockTicker 앱은 MarketState 열거형 값을 반환 하는 속성을 사용 하 여 시장의 상태를 유지 관리 합니다 MarketState .In the StockTicker class, the app maintains the state of the market with a MarketState property that returns a MarketState enum value:

public MarketState MarketState
{
    get { return _marketState; }
    private set { _marketState = value; }
}

public enum MarketState
{
    Closed,
    Open
}

StockTicker클래스를 스레드로부터 안전 하 게 보호 해야 하므로 시장 상태를 변경 하는 각 메서드는 잠금 블록 내에서이 작업을 수행 합니다.Each of the methods that change the market state do so inside a lock block because the StockTicker class has to be thread-safe:

public void OpenMarket()
{
    lock (_marketStateLock)
    {
        if (MarketState != MarketState.Open)
        {
            _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
            MarketState = MarketState.Open;
            BroadcastMarketStateChange(MarketState.Open);
        }
    }
}

public void CloseMarket()
{
    lock (_marketStateLock)
    {
        if (MarketState == MarketState.Open)
        {
            if (_timer != null)
            {
                _timer.Dispose();
            }
            MarketState = MarketState.Closed;
            BroadcastMarketStateChange(MarketState.Closed);
        }
    }
}

public void Reset()
{
    lock (_marketStateLock)
    {
        if (MarketState != MarketState.Closed)
        {
            throw new InvalidOperationException("Market must be closed before it can be reset.");
        }
        LoadDefaultStocks();
        BroadcastMarketReset();
    }
}

이 코드가 스레드로부터 안전 하 게 보호 되도록 하기 위해 _marketState 지정 된 속성을 지 원하는 필드는 MarketState volatile 다음과 같습니다.To make sure this code is thread-safe, the _marketState field that backs the MarketState property designated volatile:

private volatile MarketState _marketState;

BroadcastMarketStateChangeBroadcastMarketReset 메서드는 클라이언트에 정의 된 다른 메서드를 호출 하는 것을 제외 하 고 이미 보았던 BroadcastStockPrice 메서드와 비슷합니다.The BroadcastMarketStateChange and BroadcastMarketReset methods are similar to the BroadcastStockPrice method that you already saw, except they call different methods defined at the client:

private void BroadcastMarketStateChange(MarketState marketState)
{
    switch (marketState)
    {
        case MarketState.Open:
            Clients.All.marketOpened();
            break;
        case MarketState.Closed:
            Clients.All.marketClosed();
            break;
        default:
            break;
    }
}

private void BroadcastMarketReset()
{
    Clients.All.marketReset();
}

서버에서 호출할 수 있는 클라이언트의 추가 함수Additional functions on the client that the server can call

updateStockPrice이제 함수는 테이블 및 표시기 표시를 모두 처리 하 고를 사용 하 여 jQuery.Color 빨강 및 녹색 색을 표시 합니다.The updateStockPrice function now handles both the table and the ticker display, and it uses jQuery.Color to flash red and green colors.

의 새 함수는 시장 상태에 따라 단추를 사용 하거나 사용 하지 않도록 설정 SignalR.StockTicker.js 합니다.New functions in SignalR.StockTicker.js enable and disable the buttons based on market state. 또한 라이브 주식 시세 표시기 가로 스크롤을 중지 하거나 시작 합니다.They also stop or start the Live Stock Ticker horizontal scrolling. 많은 함수가에 추가 되기 때문에 ticker.client 앱은 jQuery extend 함수 를 사용 하 여 추가 합니다.Since many functions are being added to ticker.client, the app uses the jQuery extend function to add them.

$.extend(ticker.client, {
    updateStockPrice: function (stock) {
        var displayStock = formatStock(stock),
            $row = $(rowTemplate.supplant(displayStock)),
            $li = $(liTemplate.supplant(displayStock)),
            bg = stock.LastChange === 0
                ? '255,216,0' // yellow
                : stock.LastChange > 0
                    ? '154,240,117' // green
                    : '255,148,148'; // red

        $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
            .replaceWith($row);
        $stockTickerUl.find('li[data-symbol=' + stock.Symbol + ']')
            .replaceWith($li);

        $row.flash(bg, 1000);
        $li.flash(bg, 1000);
    },

    marketOpened: function () {
        $("#open").prop("disabled", true);
        $("#close").prop("disabled", false);
        $("#reset").prop("disabled", true);
        scrollTicker();
    },

    marketClosed: function () {
        $("#open").prop("disabled", false);
        $("#close").prop("disabled", true);
        $("#reset").prop("disabled", false);
        stopTicker();
    },

    marketReset: function () {
        return init();
    }
});

연결을 설정한 후 추가 클라이언트 설정Additional client setup after establishing the connection

클라이언트에서 연결을 설정한 후에는 다음과 같은 몇 가지 추가 작업을 수행 해야 합니다.After the client establishes the connection, it has some additional work to do:

  • 적절 한 or 함수를 호출 하기 위해 시장이 열려 있는지 또는 닫혀 있는지 확인 marketOpened marketClosed 합니다.Find out if the market is open or closed to call the appropriate marketOpened or marketClosed function.

  • 서버 메서드 호출을 단추에 연결 합니다.Attach the server method calls to the buttons.

$.connection.hub.start()
    .pipe(init)
    .pipe(function () {
        return ticker.server.getMarketState();
    })
    .done(function (state) {
        if (state === 'Open') {
            ticker.client.marketOpened();
        } else {
            ticker.client.marketClosed();
        }

        // Wire up the buttons
        $("#open").click(function () {
            ticker.server.openMarket();
        });

        $("#close").click(function () {
            ticker.server.closeMarket();
        });

        $("#reset").click(function () {
            ticker.server.reset();
        });
    });

서버 메서드는 앱이 연결을 설정할 때까지 단추에 연결 되지 않습니다.The server methods aren't wired up to the buttons until after the app establishes the connection. 코드를 사용 하기 전에 서버 메서드를 호출할 수 없기 때문입니다.It's so the code can't call the server methods before they're available.

추가 리소스Additional resources

이 자습서에서는 서버에서 연결 된 모든 클라이언트로 메시지를 브로드캐스팅하는 SignalR 응용 프로그램을 프로그래밍 하는 방법에 대해 알아보았습니다.In this tutorial you've learned how to program a SignalR application that broadcasts messages from the server to all connected clients. 이제 모든 클라이언트의 알림에 대 한 응답으로 메시지를 정기적으로 브로드캐스트할 수 있습니다.Now you can broadcast messages on a periodic basis and in response to notifications from any client. 다중 스레드 singleton 인스턴스의 개념을 사용 하 여 다중 플레이어 온라인 게임 시나리오에서 서버 상태를 유지 관리할 수 있습니다.You can use the concept of multi-threaded singleton instance to maintain server state in multi-player online game scenarios. 예를 들어 SignalR을 기반으로 하는 Sho이상 r 게임을 참조 하세요.For an example, see the ShootR game based on SignalR.

피어 투 피어 통신 시나리오를 보여 주는 자습서는 SignalR 시작 하기 및 SignalR를 사용 하 여 실시간 업데이트를 참조 하세요.For tutorials that show peer-to-peer communication scenarios, see Getting Started with SignalR and Real-Time Updating with SignalR.

SignalR에 대 한 자세한 내용은 다음 리소스를 참조 하세요.For more about SignalR, see the following resources:

다음 단계Next steps

이 자습서에서는 다음을 수행합니다.In this tutorial, you:

  • 프로젝트를 만듦Created the project
  • 서버 코드 설정Set up the server code
  • 서버 코드를 검사 합니다.Examined the server code
  • 클라이언트 코드 설정Set up the client code
  • 클라이언트 코드를 검사 합니다.Examined the client code
  • 애플리케이션 테스트Tested the application
  • 로깅 사용Enabled logging

ASP.NET SignalR 2를 사용 하는 실시간 웹 응용 프로그램을 만드는 방법에 대해 알아보려면 다음 문서로 이동 합니다.Advance to the next article to learn how to create a real-time web application that uses ASP.NET SignalR 2.