当前位置: 移动技术网 > IT编程>开发语言>c# > 使用SignalR从服务端主动推送警报日志到各种终端(桌面、移动、网页)

使用SignalR从服务端主动推送警报日志到各种终端(桌面、移动、网页)

2020年01月11日  | 移动技术网IT编程  | 我要评论
微信公众号: "Dotnet9" ,网站: "Dotnet9" ,问题或建议: "请网站留言" , 如果对您有所帮助: "欢迎赞赏" 。 使用SignalR从服务端主动推送警报日志到各种终端(桌面、移动、网页) 阅读导航 1. 本文背景 2. 代码实现 3. 本文参考 1.本文背景 工作上有个业务, ...

微信公众号:dotnet9,网站:dotnet9,问题或建议:,
如果对您有所帮助:。

使用signalr从服务端主动推送警报日志到各种终端(桌面、移动、网页)

阅读导航

  1. 本文背景
  2. 代码实现
  3. 本文参考

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后端的朋友非常熟悉了。

  1. configureservices方法

添加signalr管道(是这个说法吧?):

services.addsignalr(options => { options.enabledetailederrors = true; });
  1. 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 网站

参考

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.参考

  1. .net 客户端 signalr asp.net core
  2. signalr-samples
  3. how do i push data from hub to client every second using signalr

除非注明,文章均由 dotnet9 整理发布,欢迎转载。

转载请注明本文地址:

欢迎扫描下方二维码关注 dotnet9 的微信公众号,本站会及时推送最新技术文章

dotnet9

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网