使用SignalR从服务端主动推送警报日志到各种终端(桌面、移动、网页)
使用signalr从服务端主动推送警报日志到各种终端(桌面、移动、网页)
阅读导航
- 本文背景
- 代码实现
- 本文参考
1.本文背景
工作上有个业务,.net core webapi作为服务端,需要将运行过程中产生的日志分类,并实时推送到各种终端进行报警,终端有桌面(wpf)、移动(xamarin.forms)、网站(angular.js)等,使用signalr进行警报日志推送。
下面是桌面端的测试效果:
2.代码实现
整个系统由服务端、桌面端、网站、移动端组成,结构如下:
2.1 服务端与客户端都使用的日志实体类
简单的日志定义,服务端会主动将最新日志通过alarmlogitem实例推送到各个终端:
/// <summary> /// 报警日志 /// </summary> public class alarmlogitem { public string id { get; set; } /// <summary> /// 日志类型 /// </summary> public alarmlogtype type { get; set; } /// <summary> /// 日志名称 /// </summary> public string text { get; set; } /// <summary> /// 日志详细信息 /// </summary> public string description { get; set; } /// <summary> /// 日志更新时间 /// </summary> public string updatetime { get; set; } } public enum alarmlogtype { info, warn, error }
2.2 服务端
使用 .net core 2.2 搭建的web api项目
2.2.1 集线器类alarmloghub.cs
定义集线器hub类alarmloghub,继承自hub,用于signalr通信,看下面的代码,没加任何方法,您没看错:
public class alarmloghub : hub {}
2.2.2 startup.cs
需要在此类中注册signalr管道及服务,在下面两个关键方法中用到,b/s后端的朋友非常熟悉了。
- configureservices方法
添加signalr管道(是这个说法吧?):
services.addsignalr(options => { options.enabledetailederrors = true; });
- configure方法注册signalr服务地址
端口用的8022,客户端访问地址是:
app.usesignalr(routes => { routes.maphub<alarmloghub>("/alarmlog"); });
2.2.3 signalrtimedhostedservice.cs
这是个关键类,用于服务端主动推送日志使用,baidu、google好久才找到,站长技术栈以c/s为主,b/s做的不多,没人指点,心酸,参考网址:how do i push data from hub to client every second using signalr。
该类继承自ihostedservice,作为服务自启动(乱说的),通过signalrtimedhostedservice 的构造函数依赖注入得到ihubcontext<alarmloghub>的实例,用于服务端向各客户端推送日志使用(在startasync方法中开启定时器,模拟服务端主动推送警报日志,见 dowork 方法):
internal class signalrtimedhostedservice : ihostedservice, idisposable { private readonly ihubcontext<alarmloghub> _hub; private timer _timer; //模拟发送报警日志 list<alarmlogitem> lstlogs = new list<alarmlogitem> { new alarmlogitem{ type=alarmlogtype.error,text="ok websocket断连",description="尝试连接50次,未成功重连!"}, new alarmlogitem{ type=alarmlogtype.warn,text="ok websocket断开重连",description="尝试连接5次,成功重连!"}, new alarmlogitem{ type=alarmlogtype.warn,text="ok restfull断连",description="尝试连接30次,成功重连!"}, new alarmlogitem{ type=alarmlogtype.error,text="ok websocket断连",description="第一次断开链接!"}, new alarmlogitem{ type=alarmlogtype.info,text="ok websocket连接成功",description="首次成功连接!"}, new alarmlogitem{ type=alarmlogtype.error,text="ok websocket断连",description="尝试连接第7次,未成功重连!"} }; random rd = new random(datetime.now.millisecond); public signalrtimedhostedservice(ihubcontext<alarmloghub> hub) { _hub = hub; } public task startasync(cancellationtoken cancellationtoken) { _timer = new timer(dowork, null, timespan.zero, timespan.fromseconds(1)); return task.completedtask; } private void dowork(object state) { if (datetime.now.second % rd.next(1, 3) == 0) { alarmlogitem log = lstlogs[rd.next(lstlogs.count)]; log.updatetime = datetime.now.tostring("yyyy-mm-dd hh:mm:ss.fff"); _hub.clients.all.sendasync("receivealarmlog", log); } } public task stopasync(cancellationtoken cancellationtoken) { _timer?.change(timeout.infinite, 0); return task.completedtask; } public void dispose() { _timer?.dispose(); } }
signalrtimedhostedservice 类作为host服务(继承自 ihostedservice),需要在startup.cs的configureservices方法中注册管道(是吧?各位有没有b/s比较好的书籍推荐,站长打算有空好好学学):
services.addhostedservice<signalrtimedhostedservice>();
服务端关键代码已经全部奉上,下面主要说说桌面端和移动端代码,其实两者代码类似。
2.3 网站
参考 index.html
2.4 桌面端(wpf)
使用 .net core 3.0创建的wfp工程,需要引入nuget包:microsoft.aspnetcore.signalr.client
界面用一个listview展示收到的日志:
<grid> <listbox x:name="messageslist" rendertransformorigin="-0.304,0.109" borderthickness="1" borderbrush="gainsboro"/> </grid>
后台写的简陋,直接在窗体构造函数中连接服务端signalr地址:, 监听服务端警报日志推送:receivealarmlog。
using appclient.models; using microsoft.aspnetcore.signalr.client; using system; using system.threading.tasks; using system.windows; namespace signalrchatclientcore { /// <summary> /// interaction logic for mainwindow.xaml /// </summary> public partial class mainwindow : window { hubconnection connection; public mainwindow() { initializecomponent(); connection = new hubconnectionbuilder() .withurl("http://localhost:8022/alarmlog") .build(); connection.closed += async (error) => { await task.delay(new random().next(0, 5) * 1000); await connection.startasync(); }; connection.on<alarmlogitem>("receivealarmlog", (message) => { this.dispatcher.invoke(() => { messageslist.items.add(message.description); }); }); try { connection.startasync(); messageslist.items.add("connection started"); } catch (exception ex) { messageslist.items.add(ex.message); } } } }
2.4 移动端
移动端其实和桌面端类似,因为桌面端使用的 .net core 3.0,移动端使用的 .net standard 2.0,都需要引入nuget包:microsoft.aspnetcore.signalr.client。
界面使用listview展示日志,这就不贴代码了,使用的mvvm方式,直接贴viewmodel代码吧,大家只看个大概,不要纠结具体代码,参照桌面.cs代码,是不是一样的?
using appclient.models; using appclient.views; using microsoft.aspnetcore.signalr.client; using system; using system.collections.objectmodel; using system.diagnostics; using system.threading.tasks; using xamarin.forms; using system.linq; namespace appclient.viewmodels { /// <summary> /// 报警日志vm /// </summary> public class alarmitemsviewmodel : baseviewmodel { private viewstate _state = viewstate.disconnected; /// <summary> /// 报警日志列表 /// </summary> public observablecollection<alarmlogitem> alarmitems { get; set; } public command loaditemscommand { get; set; } //连接报警服务端 private hubconnection _connection; public alarmitemsviewmodel() { title = "报警日志"; alarmitems = new observablecollection<alarmlogitem>(); loaditemscommand = new command(async () => await executeloaditemscommand()); //收到登录成功通知 messagingcenter.subscribe<loginviewmodel, loginuser>(this, "loginsuccess", async (sender, userinfo) => { //displayalert("登录成功", userinfo.username, "确定"); }); messagingcenter.subscribe<newitempage, alarmlogitem>(this, "添加项", async (obj, item) => { var newitem = item as alarmlogitem; alarmitems.add(newitem); await datastore.additemasync(newitem); }); connectalarmserver(); } async task executeloaditemscommand() { if (isbusy) return; isbusy = true; try { alarmitems.clear(); var items = await datastore.getitemsasync(true); foreach (var item in items) { alarmitems.add(item); } } catch (exception ex) { debug.writeline(ex); } finally { isbusy = false; } } private async task connectalarmserver() { if (_state == viewstate.connected) { try { await _connection.stopasync(); } catch (exception ex) { return; } _state = viewstate.disconnected; } else { try { _connection = new hubconnectionbuilder() .withurl(app.setting.alarmhost) .build(); _connection.on<alarmlogitem>("receivealarmlog", async (newitem) => { alarmitems.add(newitem); await datastore.additemasync(newitem); }); _connection.closed += async (error) => { await task.delay(new random().next(0, 5) * 1000); await _connection.startasync(); }; await _connection.startasync(); } catch (exception ex) { return; } _state = viewstate.connected; } } private enum viewstate { disconnected, connecting, connected, disconnecting } } }
关键代码已经贴完了,希望对大家能有所帮助。
3.参考
- .net 客户端 signalr asp.net core
- signalr-samples
- how do i push data from hub to client every second using signalr
除非注明,文章均由 dotnet9 整理发布,欢迎转载。
转载请注明本文地址:
欢迎扫描下方二维码关注 dotnet9 的微信公众号,本站会及时推送最新技术文章