国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - ASP.NET教程 - 【ASP.NET Core】使用SignalR推送服務器日志

【ASP.NET Core】使用SignalR推送服務器日志

2024-01-06 00:03未知服務器之家 ASP.NET教程

一個多月前接手了一個產線機器人項目,上位機以讀寫寄存器的方式控制機器人,服務器就是用 ASP.NET Core 寫的 Web API。由于前一位開發者寫的代碼質量問題,導致上位機需要16秒才能啟動。經過我近一個月的改造,除了保留業務邏

一個多月前接手了一個產線機器人項目,上位機以讀寫寄存器的方式控制機器人,服務器就是用 ASP.NET Core 寫的 Web API。由于前一位開發者寫的代碼質量問題,導致上位機需要16秒才能啟動。經過我近一個月的改造,除了保留業務邏輯代碼,其他的基本重寫。如今上位機的啟動時間在網絡狀態良好的條件下可以秒啟動。原上位機啟動慢的原因:

1、啟動時使用同步方式訪問 Web API,在網絡較弱時需要等待很長時間。我改為導步請求,并且不等待請求結果,直接顯示窗口;如果前面的請求失敗,在窗口顯示后再次發出異步請求,并且不等待。如果再失敗才提示用戶。

2、原項目在 Main 方式處就連接PLC,而產線的PLC壓根就沒插電源。我改為在連接機器人之后才連接,同樣是異步不等待。如果連不上直接忽略。

3、原項目是一個窗口一個項目,然后把這些窗口生成 .dll,放到一個目錄下,主程序啟動時從目錄下掃描 .dll,通過反射動態實例化窗口。這根本不需要的,一個上位機不可能有幾百個窗口吧,何必呢。我改為使用服務容器的方式管理窗口,主界面通過依賴注入自動獲取子窗口列表,再添加到主界面上。每個子窗口實現 IPage 接口用于識別,接口里面定義標題和頁面索引即可。

4、干掉 Log4Net,使用官方的 Logging 庫。

5、通信用的 JSON 數據全改用 System.Text.Json,而不是某 Newton,修改后速度快了一個次元。

由于 Web API 程序是運行在服務器的 IIS 中的,上一位開發者沒有實現日志功能(僅僅用 ASP.NET Core 應用程序默認開啟的控制臺等日志功能),問題是日志沒有保存。

我原來的計劃是把日志寫到系統中,這樣就能保存下來,用“事件查看器”就能欣賞。后來想想這方案不行,工廠那伙人肯定找不到日志在哪。寫數據庫里面?想想似乎沒這個必要。簡單粗暴,直接自定義一個 ILogger,把日志輸出到文件中,然后加一個 Web API 讀取文件,上位機那里就可以調用,返回日志內容。

后經過現場調試發現,其實也不需要這樣。時間長了,會存下很多日志文件,就算用日期標識文件名也是很亂。實際上他們并不要求保存日志,只是在運作過程中實時監控機器人(應該叫機械臂)的工作狀態而已。如果不出問題,他們甚至連日志都不看。上面用文件實現的日志方式,主要缺點是不能實時推到上位機。就算他們不看,那我現場調試也方便我自己。

于是,我又想到了另一方案:用 SignalR 實時向上位機推送日志。

----------------------------------------------------------------------------------------------------------------------------------------

上面都是大話,現在開始主題。

原理是這樣的:上位機作為 SignalR 客戶端,發起連接后,不用主動調用服務器上的方法,而是等服務器調用回調方法。

?第一步,咱們要自定義一個 ILogger。

public class KingkingLogger : ILogger
{
    private readonly string cateName;
    
    public KingkingLogger(string cate)
    {
        cateName = cate;
    }

    public IDisposable? BeginScope<TState>(TState state) where TState : notnull
    {
        return default;
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return logLevel != LogLevel.None;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        if(IsEnabled(logLevel) == false)
        {
            return;
        }
        // 獲取格式化后的文本
        string fstr = formatter(state, exception);
        // 顯示消息類型
        string head = logLevel switch
        {
            LogLevel.Information => "消息",
            LogLevel.Warning => "警告",
            LogLevel.Error => "錯誤",
            _ => "未知"
        };
        // 加個日期
        string currdate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        // 連接字符串
        fstr = $"[{head}:{cateName}][{currdate}]{fstr}";
        // 觸發事件
        TransferLog?.Invoke(fstr);
    }

    // 靜態屬性
    public static Action<string>? TransferLog {  get; set; }
}

我暫時想不到叫啥名字,就暫且叫它 Kingking 日志記錄器吧,我在項目中的類是叫 WTFLogger 的,什么內涵你懂的,反正現在這項目只有我一個人在寫,取這個名字也無所謂。這個類不復雜,我解釋一下你就明白了。

1、字符串 cateName 是類別名稱。就是記錄日志時它屬于哪個名錄下的,比如我們常見的?Microsoft.Hosting.Lifetime、Microsoft.Hosting.Lifetime 等這些就是。在 Logging 庫中有兩種方式指定:一是用字符串,二是用 ILogger<T> ,這個類型T將作為日志類別的名稱。這里我采用的是字符串方式,所以不使用 ILogger<T>。

2、BeginScope 方法的用處是當你要把 logger 用在 using 語句塊時才會實現。正因為用在 using 塊中,所以它要求是實現?IDisposable 接口。這個實現?IDisposable 的類一般不用公開。這方法會接收一個泛型參數 TState state。這個看你的需要了,運行庫內部調用經常會用字典類型傳遞一些額外數據。這個 TState 你可以自定義。此處我不需要把 logger 用在 using 語句塊中,所以直接返回 default(或null)。

3、IsEnabled 方法的功能是分析一下 logLevel 參數指定的日志級別當前是否要輸出日志。如果需要輸出日志,返回 true;不想輸出日志返回 false。后面實現的 Log 方法中也會用到它,如果返回 false,那就不必去處理怎么輸出日志了。

4、Log 方法是核心。在此方法中你盡情發揮吧,你想怎樣輸出日志就在這里完成。比如你要用 Debug 類輸出,那就調用 Debug 類的成員輸出;你用控制臺輸出就調用 Console 類的成員。我這里是要把日志傳給 SignalR Hub 對象,讓其傳回給客戶端,故要調用靜態的?TransferLog 屬性。此屬性是委托類型,可以與方法綁定,因為咱們不能在這里調用 Hub,Hub 是由 SignalR 組件自動激活的。所以要用委托來間接實現傳遞。這個和事件的作用一樣,只是我不用事件成員罷了。

順便說一下,我項目中的類是同時把日志寫入數據庫的(不寫文件了,寫數據庫里好清理),這里老周為了讓示例簡單,沒有加上寫入數據的代碼。其實也沒啥難度的,就是在數據庫中加個表,用 EF Core 往表里 INSERT 一條記錄。

?

第二步,實現 Provider。ILogger 咱們定義好了,但這個 Kingking 日志記錄器可不是直接扔進服務容器,而是通過叫?ILoggerProvider 的對象來創建實例。就相當于一個工廠類。

public class KingkingLoggerProvider : ILoggerProvider
{
    public ILogger CreateLogger(string categoryName)
    {
        return new KingkingLogger(categoryName);
    }

    public void Dispose()
    {
        return;
    }
}

代碼很簡單,沒啥玄機。不過,為了調用方便,咱們可以封裝一個擴展方法。

public static class CustLoggerExtensions
{
    public static ILoggingBuilder AddKingkingLogger(this ILoggingBuilder builder)
    {
        builder.Services.AddSingleton<ILoggerProvider, KingkingLoggerProvider>();
        return builder;
    }
}

這樣就做到了像官方 API 那樣,用 AddXXX 的方法添加日志功能,用法如下:

var builder = WebApplication.CreateBuilder(args);
// 配置日志
builder.Services.AddLogging(o =>
{
    // 清空所有日志提供者
    o.ClearProviders();
    // 添加控制臺日志輸出
    o.AddConsole();
    // 添加咱們自己寫的日志記錄器
    o.AddKingkingLogger();
});

?

第三步,實現 Hub。Hub 是 SignalR 通信的“中心”類,當訪問的 URL 匹配時就會激活咱們的 Hub。自定義 Hub 只要從 Hub 類派生即可。

public class MyHub : Hub
{
    public MyHub() {
        // 這里關聯的就是日志記錄類中的靜態委托
        KingkingLogger.TransferLog = KingkingLogger_TransferLog;
    }

    private void KingkingLogger_TransferLog(string obj)
    {
        // 向所有客戶端發日志
        Clients.All.SendAsync("onLogged", obj);
    }

    protected override void Dispose(bool disposing)
    {
        if(disposing)
        {
            // 實例釋放時移除關聯
            KingkingLogger.TransferLog = null;
        }
        base.Dispose(disposing);
    }
}

邏輯很簡單,就是有日志了就推送給客戶端。Clients.All 是把消息發給所有連接的客戶端。

這里順便提一下:Hub 是支持依賴注入的,即你可以在 MyHub 的構造函數里注入你要用的組件,如 DBContext 等。這里我用不到其他組件,所以沒有注入。

在Web應用程序初始化時要啟用 SignalR 相關服務。

var builder = WebApplication.CreateBuilder(args);
……
builder.Services.AddSignalR();
var app = builder.Build();

還要 Map 一下終結點,以綁定請求 Hub 的地址。

var builder = WebApplication.CreateBuilder(args);
……
var app = builder.Build();

……

// 記得這個
app.MapHub<MyHub>("/hub");

app.Run();

這里我設定的地址是 http://localhost/hub。

?

不要以為這樣就完事了,當你運行后用客戶端一測試,你會發現連毛都接收不到。這是因為 Hub 對象的默認生命周期太短了,僅在用的時候實例化,然后馬上 Dispose 了。然后你會想,那我重寫 OnConnectedAsync 方法,關聯 TransferLog 委托;再重寫 OnDisConnectedAsync 方法,把 TransferLog 委托設置為 null。這個也是不行的,原因還是那個—— Hub 對象生命周期太短。

有什么辦法讓 Hub 長壽一點呢?還真有,直接把 Hub 類型注冊進服務器中,并使用單實例。

var builder = WebApplication.CreateBuilder(args);
……
// 把Hub注冊為單實例
builder.Services.AddSingleton<MyHub>();
builder.Services.AddSignalR();
var app = builder.Build();

?

第四步,客戶端程序。客戶端并不是只能用 JS 來寫,.NET 團隊也做了相關的 Nuget 包。在項目中引用一下。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWindowsForms>true</UseWindowsForms>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.0" />
  </ItemGroup>

</Project>

在主窗口中放一個文本框,兩個按鈕。文本框顯示收到的日志,按鈕用來請求連接和斷開連接。

using Microsoft.AspNetCore.SignalR.Client;

namespace TestClient;

public partial class Form1 : Form
{
    // 連接對象
    HubConnection hubConn;
    public Form1()
    {
        InitializeComponent();
        // 初始化連接
        var connBuilder = new HubConnectionBuilder()
            .WithUrl("http://localhost:6225/hub")
            .WithAutomaticReconnect();
        hubConn = connBuilder.Build();
        // 關聯方法
        hubConn.On<string>("onLogged", OnLogRecv);
    }

    private void OnLogRecv(string msg)
    {
        // 服務器回調,顯示收到的日志
        textBox1.Invoke(() =>
        {
            textBox1.AppendText(msg + Environment.NewLine);
        });
    }

    private async void btnConn_Click(object sender, EventArgs e)
    {
        try
        {
            await hubConn.StartAsync();
            lbMessage.Text = "已建立連接";
        }
        catch(Exception ex) {
            lbMessage.Text = ex.Message;
        }
    }

    private async void btnDisconn_Click(object sender, EventArgs e)
    {
        if(hubConn.State == HubConnectionState.Connected)
        {
            await hubConn.StopAsync();
            lbMessage.Text = "已斷開連接";
        }
    }
}

注意,在調用 On 方法時,onLogged 要與服務器上指定的一致,否則服務器回調無效

/*---------------- 服務器端 ------------------*/
private void KingkingLogger_TransferLog(string obj)
{
    // 向所有客戶端發日志
    Clients.All.SendAsync("onLogged", obj);
}

/*--------------------- 客戶端 -------------------*/
hubConn.On<string>("onLogged", OnLogRecv);

?

為了測試能否真的傳遞了日志,咱們在服務端寫幾個 Mini-API 來驗證。

app.MapGet("/", (ILoggerFactory logFact) =>
{
    ILogger logger = logFact.CreateLogger("MINI Main");
    logger.LogInformation("歡迎來到圓環世界");
    return "Hello Guy";
});
app.MapGet("/start", (ILoggerFactory logFact) =>
{
    ILogger logger = logFact.CreateLogger("MINI Go Go Go");
    logger.LogWarning("游戲開始了,你必須先和QB簽訂契約");
    return "圓神啟動";
});
app.MapGet("/shot", (ILoggerFactory loggerFact) =>
{
    ILogger logger = loggerFact.CreateLogger("MINI Wind");
    logger.LogInformation("干得好,三發入魂");
    return "第一局完勝";
});

?

同時啟動服務端和客戶端試試吧。為了使測試更真實,我啟動了三個客戶端。觸發日志記錄,請調用任意一個 API。

依次點擊三個窗口上的“連接”按鈕,確認全部都連上。

【ASP.NET Core】使用SignalR推送服務器日志

然后依次調用那幾個 mini API 試試。

【ASP.NET Core】使用SignalR推送服務器日志

可以看到,三個客戶端都收到日志推送了。

為了演示,沒有數據存儲,所以如果客戶端沒有及時連接,會丟失前面的日志。老周的實際項目中是用數據庫存起來,用的時候再取出來發給客戶端。默認是發最近的 100 條。如果上位機要看全部,就調用一下 Hub 的方法,Hub 的代碼會 select 整個日志表再發回。

?

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 日本二区 | 亚洲精品一区二区在线观看 | 亚洲色图在线播放 | 国产日韩欧美 | 日韩精品99久久久久中文字幕 | 日韩高清电影 | 日韩精品免费视频 | 一本色道久久综合狠狠躁篇的优点 | 亚洲精品一区二区三区在线观看 | 久久久国产一区 | 亚洲精品视频在线看 | 成人免费视频a | 视频一区在线观看 | 男人天堂v | av亚洲在线| 538在线精品 | 中文字幕一区二区三区精彩视频 | 男女免费视频 | 亚洲五码中文字幕 | 午夜免费福利影院 | 久久久综合色 | 日韩在线观看中文字幕 | 精品免费视频 | 啊v视频 | 黄色免费视频 | 欧美日韩精品免费 | a黄视频 | 天天操一操| 免费成人黄色 | 亚洲自拍偷拍一区 | 激情久久久 | 久久亚洲一区 | 免费的黄色网 | 天操天天干 | 狠狠操电影 | 久播播av| 欧美大片免费高清观看 | 国产欧美一二三区在线粉嫩 | 成人免费在线电影 | 亚洲国产精品一区久久av篠田 | 国产一级久久久久 |