回到顶部

ASP.NET和SignalR简单实现股票行情实时展示和价格变动推送

时间:3年前   作者:请喊我大龙哥   浏览:3221   [站内原创,转载请注明出处]

标签: C sharp   ASP.NET   SignalR  

ASP.NET C# SignalR 简单实现股票行情实时展示和价格变动推送

本文将从SignalR的简介、项目新建和实现入手,简单演示SignalR功能的一部分。

效果见第11步,demo见附件。

1、开发环境

  1. Visual Studio 2013
  2. .Net Framework 4.5
  3. SignalR-2.0.0

2、SignalR简介

    ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程。实时 Web 功能是指这样一种功能:当所连接的客户端变得可用时服务器代码可以立即向其推送内容,而不是让服务器等待客户端请求新的数据。

    ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信。什么是实时通信的Web呢?就是让客户端(Web页面)和服务器端可以互相通知消息及调用方法,当然这是实时操作的。

    WebSockets是HTML5提供的新的API,可以在Web网页与服务器端间建立Socket连接,当WebSockets可用时(即浏览器支持Html5)SignalR使用WebSockets,当不支持时SignalR将使用其它技术来保证达到相同效果。

    SignalR当然也提供了非常简单易用的高阶API,使服务器端可以单个或批量调用客户端上的JavaScript函数,并且非常 方便地进行连接管理,例如客户端连接到服务器端,或断开连接,客户端分组,以及客户端授权,使用SignalR都非常 容易实现。


    SignalR 将与客户端进行实时通信带给了ASP .NET 。当然这样既好用,而且也有足够的扩展性。以前用户需要刷新页面或使用Ajax轮询才能实现的实时显示数据,现在只要使用SignalR,就可以简单实现了。

    最重要的是您无需重新建立项目,使用现有ASP .NET项目即可无缝使用SignalR。



3、创建项目

    

    

4、创建stock.cs类,用于股票信息实体类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

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);
            }
        }
    }
}
stock.cs实体类中的两个属性,用于存储股票名称代码,股票价格,其他剩余属性用于决定如何和何时来设定价格字段,会根据dayopen之间的差异计算各个属性值的变化


5、使用SignaR集线器的API来处理服务器到客户端的交互。一个stocktickerhub类派生类将处理signalr接收到的client连接和调用方法。项目中还需要维护股票数据,并运行一个计时器对象,以周期性地触发价格更新,这个计时器独立于客户端连接。而且也不能把这些函数放在一个集线器类中,因为集线器实例是暂时的。一个集线器类实例是在集线器上的每个操作创建的,比如客户端到服务器的连接和调用。这样的机制,使股票价格数据、更新,和server推送到client的价格更新已经运行在一个单独的类,就是stockticker。

    

6、右键项目添加SignalR Hub Class (v2)模板类 StockTickerHub.cs,

    

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();
        }
    }
}
7、创建一个新的StockTicker.cs类,用于判断如何,何时进行股票价格的变动处理


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
    {
        // 单一实例对象
        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();

        // 股票可以按比例上升或下降的百分比
        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 clients)
        {
            Clients = clients;

            _stocks.Clear();
            var stocks = new List<Stock>
            {
                new Stock { Symbol = "微软", Price = 30.31m },
                new Stock { Symbol = "苹果", Price = 578.18m },
                new Stock { Symbol = "谷歌", 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 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)
        {
            // 判断是否进行报价更新
            var r = _updateOrNotRandom.NextDouble();
            if (r > .1)
            {
                return false;
            }

            // 注:这里应该是进行数据库实时数据读取
            // 暂时使用random随机数值来演示股票行情报价的变动
            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);
        }
    }
}
8、右键项目添加启动文件Startup.cs,用于处理入口处理


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)
        { 
            //任何一个client连接或集线器连线和配置都会先进这里
            app.MapSignalR();
        }
    }
}
9、上面8步已经基本将server端处理好,下面处理client页面端,创建一个StockTicker.html页面,并设为启动页面


<!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 股票行情实时显示demo</h1>
    <div id="stockTable">
        <table border="1">
            <thead>
                <tr><th>股票名称</th><th>开盘价格</th><th>当前价格</th><th>涨跌值</th><th>涨跌幅(%)</th></tr>
            </thead>
            <tbody>
                <tr class="loading"><td colspan="5">加载中...</td></tr>
            </tbody>
        </table>
    </div>
    <script src="Scripts/jquery-1.10.2.min.js"></script>
    <script src="Scripts/jquery.signalR-2.0.0.js"></script>
    <script src="/signalr/hubs"></script>
    <script src="Scripts/StockTicker.js"></script>
</body>
</html>

注意:上面的html代码中我用的我本地script脚本,自行处理。


10、创建通讯脚本StockTicker.js

// 简单替换方法
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, //StockTickerHub类,[HubName("stockTickerMini")] 属性值
        up = '▲',
        down = '▼',
        $stockTable = $('#stockTable'),
        $stockTableBody = $stockTable.find('tbody'),
        rowTemplate = '<tr data-symbol="{Symbol}"><td>{Symbol}</td><td>{DayOpen}</td><td>{Price}</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));
            });
        });
    }
     
    // 客户端集线器方法,股票价格变动的时候服务器将调用
    ticker.client.updateStockPrice = function (stock) {
        var displayStock = formatStock(stock),
            $row = $(rowTemplate.supplant(displayStock));

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

    // 开始client和server连接
    $.connection.hub.start().done(init);

});


  1. var ticker = $.connection.stockTickerMini; 这是指定signalr连接代理,引用StockTickerHub.cs类中的 [HubName("stockTickerMini")]
  2. $.connection.hub.start().done(init); 启动signalr,开始执行并返回推送的消息象,这里的call和response都是函数异步调用的。
  3. function init() 此方法用于初始化显示的表格,这里全都是用的 camel 命名规则,如 getAllStocks()
  4. ticker.client.updateStockPrice 用于执行股票价格变动的更新操作
11,以上步骤完成后,编译通过后,F5测试即可,在不同的多个浏览器同时打开效果如下:



请喊我大龙哥最后编辑于:3年前

内容均为作者独立观点,不代表八零IT人立场,如涉及侵权,请及时告知。

评论努力加载中...
暂无评论
暂无评论

手机扫码阅读

热门相关

加载中...
关于我们   联系我们   申请友链   赞助记录   站点地图
© 2014 - 2017 www.80iter.com All Rights Reserved. 京ICP备14042174号-1
本站遵循 CC BY 4.0 协议,转载请注明出处 。