使用SignalR從服務(wù)端主動(dòng)推送警報(bào)日志到各種終端(桌面、移動(dòng)凤粗、網(wǎng)頁(yè))

微信公眾號(hào):Dotnet9酥泛,網(wǎng)站:Dotnet9,問(wèn)題或建議:請(qǐng)網(wǎng)站留言嫌拣,
如果對(duì)您有所幫助:歡迎贊賞柔袁。

使用SignalR從服務(wù)端主動(dòng)推送警報(bào)日志到各種終端(桌面、移動(dòng)异逐、網(wǎng)頁(yè))

閱讀導(dǎo)航

  1. 本文背景
  2. 代碼實(shí)現(xiàn)
  3. 本文參考

1.本文背景

工作上有個(gè)業(yè)務(wù)捶索,.Net Core WebAPI作為服務(wù)端,需要將運(yùn)行過(guò)程中產(chǎn)生的日志分類灰瞻,并實(shí)時(shí)推送到各種終端進(jìn)行報(bào)警腥例,終端有桌面(WPF)、移動(dòng)(Xamarin.Forms)酝润、網(wǎng)站(Angular.JS)等燎竖,使用SignalR進(jìn)行警報(bào)日志推送。

下面是桌面端的測(cè)試效果:


桌面客戶端

2.代碼實(shí)現(xiàn)

整個(gè)系統(tǒng)由服務(wù)端袍祖、桌面端底瓣、網(wǎng)站、移動(dòng)端組成,結(jié)構(gòu)如下:


系統(tǒng)結(jié)構(gòu)

2.1 服務(wù)端與客戶端都使用的日志實(shí)體類

簡(jiǎn)單的日志定義捐凭,服務(wù)端會(huì)主動(dòng)將最新日志通過(guò)AlarmLogItem實(shí)例推送到各個(gè)終端:

/// <summary>
/// 報(bào)警日志
/// </summary>
public class AlarmLogItem
{
    public string Id { get; set; }
    /// <summary>
    /// 日志類型
    /// </summary>
    public AlarmLogType Type { get; set; }
    /// <summary>
    /// 日志名稱
    /// </summary>
    public string Text { get; set; }
    /// <summary>
    /// 日志詳細(xì)信息
    /// </summary>
    public string Description { get; set; }
    /// <summary>
    /// 日志更新時(shí)間
    /// </summary>
    public string UpdateTime { get; set; }
}

public enum AlarmLogType
{
    Info,
    Warn,
    Error
}

2.2 服務(wù)端

使用 .Net Core 2.2 搭建的Web API項(xiàng)目

2.2.1 集線器類AlarmLogHub.cs

定義集線器Hub類AlarmLogHub拨扶,繼承自Hub,用于SignalR通信茁肠,看下面的代碼患民,沒(méi)加任何方法,您沒(méi)看錯(cuò):

public class AlarmLogHub : Hub
{}

2.2.2 Startup.cs

需要在此類中注冊(cè)SignalR管道及服務(wù)垦梆,在下面兩個(gè)關(guān)鍵方法中用到匹颤,B/S后端的朋友非常熟悉了。

  1. ConfigureServices方法

添加SignalR管道(是這個(gè)說(shuō)法吧托猩?):

services.AddSignalR(options => { options.EnableDetailedErrors = true; });
  1. Configure方法注冊(cè)SignalR服務(wù)地址

端口用的8022印蓖,客戶端訪問(wèn)地址是:http://localhost:8022/alarmlog

app.UseSignalR(routes =>
{
    routes.MapHub<AlarmLogHub>("/alarmlog");
});

2.2.3 SignalRTimedHostedService.cs

這是個(gè)關(guān)鍵類,用于服務(wù)端主動(dòng)推送日志使用京腥,Baidu赦肃、Google好久才找到,站長(zhǎng)技術(shù)棧以C/S為主公浪,B/S做的不多他宛,沒(méi)人指點(diǎn),心酸欠气,參考網(wǎng)址:How do I push data from hub to client every second using SignalR
厅各。

該類繼承自IHostedService,作為服務(wù)自啟動(dòng)(亂說(shuō)的)预柒,通過(guò)SignalRTimedHostedService 的構(gòu)造函數(shù)依賴注入得到IHubContext<AlarmLogHub>的實(shí)例队塘,用于服務(wù)端向各客戶端推送日志使用(在StartAsync方法中開(kāi)啟定時(shí)器,模擬服務(wù)端主動(dòng)推送警報(bào)日志卫旱,見(jiàn) DoWork 方法):

internal class SignalRTimedHostedService : IHostedService, IDisposable
{
    private readonly IHubContext<AlarmLogHub> _hub;
    private Timer _timer;

    //模擬發(fā)送報(bào)警日志            
    List<AlarmLogItem> lstLogs = new List<AlarmLogItem> {
            new AlarmLogItem{ Type=AlarmLogType.Error,Text="OK WebSocket斷連",Description="嘗試連接50次人灼,未成功重連!"},
            new AlarmLogItem{ Type=AlarmLogType.Warn,Text="OK WebSocket斷開(kāi)重連",Description="嘗試連接5次顾翼,成功重連投放!"},
            new AlarmLogItem{ Type=AlarmLogType.Warn,Text="OK Restfull斷連",Description="嘗試連接30次,成功重連适贸!"},
            new AlarmLogItem{ Type=AlarmLogType.Error,Text="OK WebSocket斷連",Description="第一次斷開(kāi)鏈接灸芳!"},
            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服務(wù)(繼承自 IHostedService),需要在Startup.cs的ConfigureServices方法中注冊(cè)管道(是吧蕊肥?各位有沒(méi)有B/S比較好的書(shū)籍推薦谒获,站長(zhǎng)打算有空好好學(xué)學(xué)):

services.AddHostedService<SignalRTimedHostedService>();

服務(wù)端關(guān)鍵代碼已經(jīng)全部奉上蛤肌,下面主要說(shuō)說(shuō)桌面端和移動(dòng)端代碼,其實(shí)兩者代碼類似批狱。

2.3 網(wǎng)站

參考 index.html

2.4 桌面端(WPF)

使用 .Net Core 3.0創(chuàng)建的WFP工程裸准,需要引入Nuget包:Microsoft.AspNetCore.SignalR.Client

界面用一個(gè)ListView展示收到的日志:

<Grid>
    <ListBox x:Name="messagesList"  RenderTransformOrigin="-0.304,0.109" BorderThickness="1" BorderBrush="Gainsboro"/>
</Grid>

后臺(tái)寫(xiě)的簡(jiǎn)陋,直接在窗體構(gòu)造函數(shù)中連接服務(wù)端SignalR地址:http://localhost:8022/alarmlog赔硫, 監(jiān)聽(tīng)服務(wù)端警報(bào)日志推送: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 移動(dòng)端

移動(dòng)端其實(shí)和桌面端類似,因?yàn)樽烂娑耸褂玫?.Net Core 3.0爪膊,移動(dòng)端使用的 .NET Standard 2.0权悟,都需要引入Nuget包:Microsoft.AspNetCore.SignalR.Client。

界面使用ListView展示日志推盛,這就不貼代碼了峦阁,使用的MVVM方式,直接貼ViewModel代碼吧耘成,大家只看個(gè)大概拇派,不要糾結(jié)具體代碼,參照桌面.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>
    /// 報(bào)警日志VM
    /// </summary>
    public class AlarmItemsViewModel : BaseViewModel
    {
        private ViewState _state = ViewState.Disconnected;

        /// <summary>
        /// 報(bào)警日志列表
        /// </summary>
        public ObservableCollection<AlarmLogItem> AlarmItems { get; set; }
        public Command LoadItemsCommand { get; set; }

        //連接報(bào)警服務(wù)端
        private HubConnection _connection;

        public AlarmItemsViewModel()
        {
            Title = "報(bào)警日志";
            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, "添加項(xiàng)", 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
        }
    }
}

關(guān)鍵代碼已經(jīng)貼完了疮方,希望對(duì)大家能有所幫助控嗜。

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 整理發(fā)布骡显,歡迎轉(zhuǎn)載疆栏。

轉(zhuǎn)載請(qǐng)注明本文地址:https://dotnet9.com/6913.html

歡迎掃描下方二維碼關(guān)注 Dotnet9 的微信公眾號(hào),本站會(huì)及時(shí)推送最新技術(shù)文章

Dotnet9

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惫谤,一起剝皮案震驚了整個(gè)濱河市壁顶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌溜歪,老刑警劉巖若专,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蝴猪,居然都是意外死亡调衰,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門自阱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)嚎莉,“玉大人,你說(shuō)我怎么就攤上這事沛豌∏髀幔” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)叫确。 經(jīng)常有香客問(wèn)我跳芳,道長(zhǎng),這世上最難降的妖魔是什么启妹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任筛严,我火速辦了婚禮,結(jié)果婚禮上饶米,老公的妹妹穿的比我還像新娘桨啃。我一直安慰自己,他們只是感情好檬输,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布照瘾。 她就那樣靜靜地躺著,像睡著了一般丧慈。 火紅的嫁衣襯著肌膚如雪析命。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天逃默,我揣著相機(jī)與錄音鹃愤,去河邊找鬼。 笑死完域,一個(gè)胖子當(dāng)著我的面吹牛软吐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吟税,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼凹耙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了肠仪?” 一聲冷哼從身側(cè)響起肖抱,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎异旧,沒(méi)想到半個(gè)月后意述,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吮蛹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年欲险,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匹涮。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡天试,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出然低,到底是詐尸還是另有隱情喜每,我是刑警寧澤务唐,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站带兜,受9級(jí)特大地震影響枫笛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜刚照,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一刑巧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧无畔,春花似錦啊楚、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至郭变,卻和暖如春颜价,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诉濒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工周伦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人未荒。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓横辆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親茄猫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容