1. 首页
  2. 文章列表
  3. ASP.NET Core SignalR 实时推送框架简单入门教程+服务端主动推送案例

最近在迁移博客项目的时候,项目之前一直是基于.NET Framework的,在迁移到.NET Core 2.2的过程中,由于其中有一部分功能用到了服务端实时推送消息的功能,即网站服务器性能实时监控,界面如下图:

懒得勤快的博客_互联网分享精神

而SignalR的.NET Core API和.NET Framework API大相径庭,所以只能重新研究.NET Core的SignalR,最终调试成功并实现了一个简单的Demo,分享在这里方便大家也来学习。

分析

由于场景是硬件监测,所以需要服务器端来主动推送消息,而不是让客户端来主动拉消息,所以这就不是简单的实现一个聊天室了,网上扒了大部分文章,都是以聊天室为例的,无奈只能翻墙上Google找文档,最终实现这个功能。用SignalR的流传输来解决这样的问题。

这种应用场景其实在金融行业里挺多的,尤其是行情数据的实时推送。

开始之前需要注意

1. 将你的.NET Core运行时升级至最新版本

2. VisualStudio2017及以上开发环境

3. SignalR客户端js文件(文末会提供下载地址)

废话少说,放码过来

首先新建一个.NET Core的网站项目,选择空网站即可,然后引入nuget包:Microsoft.AspNetCore.SignalR以及Masuit.Tools.Core,为什么要引Masuit.Tools.Core?因为实现硬件监测需要用到这里面的一些函数,所以,算是硬广一波了吧。

先做准备工作,在Startup.cs中配置好SignalR;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // ...
        services.AddWebSockets(opt =>
        {
            opt.AllowedOrigins.Add("*");
            opt.ReceiveBufferSize = 2097152;
        }).AddSignalR();
        // ...
    }
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        // ...
        app.UseStaticFiles();
        app.UseSignalR(hub => hub.MapHub<MyHub>("/hubs"));
        // ...
    }
}

新建一个类PerformanceCounter.cs,用于存储硬件状态

/// <summary>
/// 性能计数器
/// </summary>
public class PerformanceCounter
{
    /// <summary>
    /// 当前时间戳
    /// </summary>
    public double Time { get; set; }

    /// <summary>
    /// CPU负载
    /// </summary>
    public double CpuLoad { get; set; }

    /// <summary>
    /// CPU温度
    /// </summary>
    public double Temperature { get; set; }

    /// <summary>
    /// 内存使用率
    /// </summary>
    public double MemoryUsage { get; set; }

    /// <summary>
    /// 磁盘读
    /// </summary>
    public double DiskRead { get; set; }

    /// <summary>
    /// 磁盘写
    /// </summary>
    public double DiskWrite { get; set; }

    /// <summary>
    /// 网络上行
    /// </summary>
    public double Upload { get; set; }

    /// <summary>
    /// 网络下行
    /// </summary>
    public double Download { get; set; }
}

接着新建一个类MyHub.cs,继承自Hub,然后重写OnConnectedAsync和OnDisconnectedAsync,给类添加一个字典类型的静态属性,用于存放连接进来的客户端信息

public static ConcurrentDictionary<string, bool> Connections { get; set; } = new ConcurrentDictionary<string, bool>();
public override Task OnConnectedAsync()
{
    Connections.TryAdd(Context.ConnectionId, false);//加入客户端列表
    return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
    Connections.TryRemove(Context.ConnectionId, out _);//从客户端列表移除
    return Task.CompletedTask;
}

接下来才是核心代码,实现流式推送:

/// <summary>
/// 前端调用的方法
/// </summary>
/// <param name="delay"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public ChannelReader<object> Counter(int delay, CancellationToken cancellationToken)
{
    var channel = Channel.CreateUnbounded<object>();//创建一个频道
    WriteItemsAsync(channel.Writer, delay, cancellationToken);
    return channel.Reader;
}

/// <summary>
/// 将消息写入流推送回客户端
/// </summary>
/// <param name="writer"></param>
/// <param name="delay"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task WriteItemsAsync(ChannelWriter<object> writer, int delay, CancellationToken cancellationToken)
{
    if (Connections[Context.ConnectionId])//防止重复连接
    {
        return;
    }
    while (Connections.Any(s => s.Key.Equals(Context.ConnectionId)))//如果这个客户端没断开,就一直给他推消息
    {
        Connections[Context.ConnectionId] = true;//将这个客户端的连接状态置为true
        try
        {
            cancellationToken.ThrowIfCancellationRequested();
            await writer.WriteAsync(GetCurrentPerformanceCounter(), cancellationToken);//推消息
        }
        catch (Exception e)
        {
            break;
        }
        await Task.Delay(delay, cancellationToken);
    }
    writer.TryComplete();
}

/// <summary>
/// 获取当前的硬件状态
/// </summary>
/// <returns></returns>
private PerformanceCounter GetCurrentPerformanceCounter()
{
    double time = DateTime.Now.GetTotalMilliseconds();
    float load = SystemInfo.CpuLoad;
    double temperature = SystemInfo.GetCPUTemperature();
    double mem = (1 - SystemInfo.MemoryAvailable.To<double>() / SystemInfo.PhysicalMemory.To<double>()) * 100;

    var read = SystemInfo.GetDiskData(DiskData.Read) / 1024;
    var write = SystemInfo.GetDiskData(DiskData.Write) / 1024;

    var up = SystemInfo.GetNetData(NetData.Received) / 1024;
    var down = SystemInfo.GetNetData(NetData.Sent) / 1024;
    PerformanceCounter counter = new PerformanceCounter()
    {
        Time = time,
        CpuLoad = load,
        Temperature = temperature,
        MemoryUsage = mem,
        DiskRead = read,
        DiskWrite = write,
        Download = down,
        Upload = up
    };
    return counter;
}

所以,完整的MyHub.cs的代码应该是这样的了:

    public class MyHub : Hub
    {
        public static ConcurrentDictionary<string, bool> Connections { get; set; } = new ConcurrentDictionary<string, bool>();

        public override Task OnConnectedAsync()
        {
            Connections.TryAdd(Context.ConnectionId, false);
            return base.OnConnectedAsync();
        }

        public override Task OnDisconnectedAsync(Exception exception)
        {
            Connections.TryRemove(Context.ConnectionId, out _);
            return Task.CompletedTask;
        }

        /// <summary>
        /// 前端调用的方法
        /// </summary>
        /// <param name="delay"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public ChannelReader<object> Counter(int delay, CancellationToken cancellationToken)
        {
            var channel = Channel.CreateUnbounded<object>();//创建一个频道
            WriteItemsAsync(channel.Writer, delay, cancellationToken);
            return channel.Reader;
        }

        /// <summary>
        /// 将消息写入流推送回客户端
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="delay"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task WriteItemsAsync(ChannelWriter<object> writer, int delay, CancellationToken cancellationToken)
        {
            if (Connections[Context.ConnectionId])//防止重复连接
            {
                return;
            }
            while (Connections.Any(s => s.Key.Equals(Context.ConnectionId)))//如果这个客户端没断开,就一直给他推消息
            {
                Connections[Context.ConnectionId] = true;//将这个客户端的连接状态置为true
                try
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    await writer.WriteAsync(GetCurrentPerformanceCounter(), cancellationToken);//推消息
                }
                catch (Exception e)
                {
                    break;
                }
                await Task.Delay(delay, cancellationToken);
            }
            writer.TryComplete();
        }

        /// <summary>
        /// 获取当前的硬件状态
        /// </summary>
        /// <returns></returns>
        private PerformanceCounter GetCurrentPerformanceCounter()
        {
            double time = DateTime.Now.GetTotalMilliseconds();
            float load = SystemInfo.CpuLoad;
            double temperature = SystemInfo.GetCPUTemperature();
            double mem = (1 - SystemInfo.MemoryAvailable.To<double>() / SystemInfo.PhysicalMemory.To<double>()) * 100;

            var read = SystemInfo.GetDiskData(DiskData.Read) / 1024;
            var write = SystemInfo.GetDiskData(DiskData.Write) / 1024;

            var up = SystemInfo.GetNetData(NetData.Received) / 1024;
            var down = SystemInfo.GetNetData(NetData.Sent) / 1024;
            PerformanceCounter counter = new PerformanceCounter()
            {
                Time = time,
                CpuLoad = load,
                Temperature = temperature,
                MemoryUsage = mem,
                DiskRead = read,
                DiskWrite = write,
                Download = down,
                Upload = up
            };
            return counter;
        }
    }

现在为止,后端的工作已经完成了,剩下的就让前端来连接我们的socket服务吧;

我们现在安装一下客户端js,在解决方案资源管理器右击项目->添加->添加客户端库,提供程序选择unpkg,库名:@aspnet/[email protected],选择特定文件,只把dist/browser/signalr.js引入项目就好了,当然你要全选我也没拦你。

懒得勤快的博客_互联网分享精神懒得勤快的博客_互联网分享精神

接下来在wwwroot下新建一个index.html文件:

<!DOCTYPE html>
<html>
<head>
    <script src="/signalr.js"></script>
</head>
<body>
<div class="counter"></div>
</body>
</html>
<script type="text/javascript">
	document.addEventListener('DOMContentLoaded', function() {
		var hub = new signalR.HubConnectionBuilder().withUrl("/hubs").build();
		hub.start().then(function() {
			hub.stream("Counter", 1000).subscribe({
				next:(item) => {
					document.querySelector(".counter").innerHTML=JSON.stringify(item);
				},
				complete:() => {
					console.log("done");
				},
				error:(err) => {
					console.error(err);
				},
			});
		});
</script>

然后,直接跑起来,你将会看到消息动起来!

懒得勤快的博客_互联网分享精神

当然,这是一段json化的数据,接下来你想怎样呈现数据那你就尽情发挥了吧!你完全可以借助于各种图表控件将数据可视化的呈现出来!

既然写了这篇文章,那还是把聊天室案例也一笔带过吧🤣🤣🤣

服务端收发消息代码:

/// <summary>
/// 单播
/// </summary>
/// <param name="msg"></param>
public void Send(string msg)
{
    Clients.Caller.SendCoreAsync("Receive", new[]
    {
        "服务器收到消息:" + msg
    });
}

/// <summary>
/// 广播
/// </summary>
/// <param name="msg"></param>
public void Broadcast(string msg)
{
    Clients.All.SendCoreAsync("Broadcast", new[]
    {
        Context.ConnectionId + "说:" + msg
    });
}

客户端代码:

<input type="text" value="" id="msg" />
<button id="send">发送消息给服务端</button>
<button id="cast">广播消息</button>
<ul id="msgs"></ul>
hub.on("Receive", function(msg) {
	let li = document.createElement("li");
    li.innerHTML=msg;
	document.querySelector("#msgs").appendChild(li);
});
hub.on("Broadcast", function(msg) {
	let li = document.createElement("li");
    li.innerHTML=msg;
	document.querySelector("#msgs").appendChild(li);
});
document.querySelector("#send").addEventListener("click", function(e) {
	var msg = document.querySelector("#msg").value;
	hub.invoke("Send",msg);
});
document.querySelector("#cast").addEventListener("click", function(e) {
	var msg = document.querySelector("#msg").value;
    hub.invoke("Broadcast",msg);
});

最后

你会发现.NET Core版本的SignalR其实比.NET Framework版本的还要简单易用,能更轻松的上手!

Demo下载:https://masuit.com/download?path=/Masuit.HardwareMonitor.Dashboard.7z

github:https://github.com/ldqk/Masuit.MyBlogshttps://github.com/ldqk/Masuit.Tools

分享到:

墙裂,秒开YouTube 4k,支持Netflix,超高性价比稳定v2ray加速服务 [推广]

墙裂,秒开YouTube 4k,支持Netflix,超高性价比稳定v2ray加速服务

超高性价比稳定v2ray加速服务,国内优化中转,Google Facebook Instagram等秒开无卡顿,YouTube 4K视频无压力,Netflix/TVB/Hulu/HBO 等流媒体访问解锁

版权声明:

🈲⚠本文为作者原创,仅用于本站访客学习、研究和交流目的,未经授权禁止转载。️⚠🈲

相关推荐:

你写的Try...Catch真的有必要么? 解决ASP.NET Core MVC的Razor视图渲染中文乱码的问题
ASP.NET Core中使用拦截器实现一个简单的web防火墙 博主的又一开源项目——基于EntityFrameworkCore和Lucene.NET实现的全文搜索引擎库
Autofac在.NET Core中的属性注入 C# Winform自定义控件系列博文
AutoMapper 9.0快速上手体验,以及如何从AutoMapper老版本迁移到9.0+AutoMapper9.0和Autofac的完美结合 .NET/java Office组件神器——Aspose.Total 17.x/18.x/19.x破解版+破解补丁下载
C#高级编程(第10版)C# 6 & .NET Core 1.0 中文完整pdf扫描版[229MB] AutoMapper 6.x起步

评论区: