C#對象二進位序列化優化:位域技術實現極限壓縮

来源:https://www.cnblogs.com/Dotnet9-com/p/17981055
-Advertisement-
Play Games

目錄1. 引言2. 優化過程2.1. 進程對象定義與初步分析2.2. 排除Json序列化2.3. 使用BinaryWriter進行二進位序列化2.4. 數據類型調整2.5. 再次數據類型調整與位域優化3. 優化效果與總結 1. 引言 在操作系統中,進程信息對於系統監控和性能分析至關重要。假設我們需要 ...


目錄

1. 引言

在操作系統中,進程信息對於系統監控和性能分析至關重要。假設我們需要開發一個監控程式,該程式能夠捕獲當前操作系統的進程信息,並將其高效地傳輸到其他端(如服務端或監控端)。在這個過程中,如何將捕獲到的進程對象轉換為二進位數據,併進行優化,以減小數據包的大小,成為了一個關鍵問題。本文將通過逐步分析,探討如何使用位域技術對C#對象進行二進位序列化優化。

操作系統進程信息

首先,我們給出了一個進程對象的欄位定義示例。為了通過網路(TCP/UDP)傳輸該對象,我們需要將其轉換為二進位格式。在這個過程中,如何做到最小的數據包大小是一個挑戰。

欄位名 說明 示例
PID 進程ID 10565
Name 進程名稱 碼界工坊
Publisher 發佈者 沙漠盡頭的狼
CommandLine 命令行 dotnet CodeWF.Tools.dll
CPU CPU(所有內核的總處理利用率) 2.3%
Memory 記憶體(進程占用的物理記憶體) 0.1%
Disk 磁碟(所有物理驅動器的總利用率) 0.1 MB/秒
Network 網路(當前主要網路上的網路利用率 0 Mbps
GPU GPU(所有GPU引擎的最高利用率) 2.2%
GPUEngine GPU引擎 GPU 0 - 3D
PowerUsage 電源使用情況(CPU、磁碟和GPU對功耗的影響)
PowerUsageTrend 電源使用情況趨勢(一段時間內CPU、磁碟和GPU對功耗的影響) 非常低
Type 進程類型 應用
Status 進程狀態 效率模式

2. 優化過程

2.1. 進程對象定義與初步分析

我們根據欄位的示例值確定了每個欄位的數據類型。

欄位名 數據類型 說明 示例
PID int 進程ID 10565
Name string? 進程名稱 碼界工坊
Publisher string? 發佈者 沙漠盡頭的狼
CommandLine string? 命令行 dotnet CodeWF.Tools.dll
CPU string? CPU(所有內核的總處理利用率) 2.3%
Memory string? 記憶體(進程占用的物理記憶體) 0.1%
Disk string? 磁碟(所有物理驅動器的總利用率) 0.1 MB/秒
Network string? 網路(當前主要網路上的網路利用率 0 Mbps
GPU string? GPU(所有GPU引擎的最高利用率) 2.2%
GPUEngine string? GPU引擎 GPU 0 - 3D
PowerUsage string? 電源使用情況(CPU、磁碟和GPU對功耗的影響)
PowerUsageTrend string? 電源使用情況趨勢(一段時間內CPU、磁碟和GPU對功耗的影響) 非常低
Type string? 進程類型 應用
Status string? 進程狀態 效率模式

創建一個C#類SystemProcess表示進程信息:

public class SystemProcess
{
    public int PID { get; set; }
    public string? Name { get; set; }
    public string? Publisher { get; set; }
    public string? CommandLine { get; set; }
    public string? CPU { get; set; }
    public string? Memory { get; set; }
    public string? Disk { get; set; }
    public string? Network { get; set; }
    public string? GPU { get; set; }
    public string? GPUEngine { get; set; }
    public string? PowerUsage { get; set; }
    public string? PowerUsageTrend { get; set; }
    public string? Type { get; set; }
    public string? Status { get; set; }
}

定義測試數據

private SystemProcess _codeWFObject = new SystemProcess()
{
    PID = 10565,
    Name = "碼界工坊",
    Publisher = "沙漠盡頭的狼",
    CommandLine = "dotnet CodeWF.Tools.dll",
    CPU = "2.3%",
    Memory = "0.1%",
    Disk = "0.1 MB/秒",
    Network = "0 Mbps",
    GPU = "2.2%",
    GPUEngine = "GPU 0 - 3D",
    PowerUsage = "低",
    PowerUsageTrend = "非常低",
    Type = "應用",
    Status = "效率模式"
};

2.2. 排除Json序列化

將對象轉為Json欄位串,這在Web開發是最常見的,因為簡潔,前後端都方便處理:

public class SysteProcessUnitTest
{
    private readonly ITestOutputHelper _testOutputHelper;

    private SystemProcess _codeWFObject // 前面已給出定義,這裡省

    public SysteProcessUnitTest(ITestOutputHelper testOutputHelper)
    {
        _testOutputHelper = testOutputHelper;
    }

    /// <summary>
    /// Json序列化大小測試
    /// </summary>
    [Fact]
    public void Test_SerializeJsonData_Success()
    {
        var jsonData = JsonSerializer.Serialize(_codeWFObject);
        _testOutputHelper.WriteLine($"Json長度:{jsonData.Length}");

        var jsonDataBytes = Encoding.UTF8.GetBytes(jsonData);
        _testOutputHelper.WriteLine($"json二進位長度:{jsonDataBytes.Length}");
    }
}
標準輸出: 
Json長度:366
json二進位長度:366

儘管Json序列化在Web開發中非常流行,因為它簡潔且易於處理,但在TCP/UDP網路傳輸中,Json序列化可能導致不必要的數據包大小增加。因此,我們排除了Json序列化,並尋找其他更高效的二進位序列化方法。

{"PID":10565,"Name":"\u7801\u754C\u5DE5\u574A","Publisher":"\u6C99\u6F20\u5C3D\u5934\u7684\u72FC","CommandLine":"dotnet CodeWF.Tools.dll","CPU":"2.3%","Memory":"0.1%","Disk":"0.1 MB/\u79D2","Network":"0 Mbps","GPU":"2.2%","GPUEngine":"GPU 0 - 3D","PowerUsage":"\u4F4E","PowerUsageTrend":"\u975E\u5E38\u4F4E","Type":"\u5E94\u7528","Status":"\u6548\u7387\u6A21\u5F0F"}

2.3. 使用BinaryWriter進行二進位序列化

使用站長前面一篇文章寫的二進位序列化幫助類SerializeHelper轉換,該類使用BinaryWriter將對象轉換為二進位數據。

首先,我們使SystemProcess類實現了一個空介面INetObject,併在類上添加了NetHeadAttribute特性。

/// <summary>
/// 網路對象序列化介面
/// </summary>
public interface INetObject
{
}
[NetHead(1, 1)]
public class SystemProcess : INetObject
{
 	// 省略欄位定義   
}

然後,我們編寫了一個測試方法來驗證序列化和反序列化的正確性,並列印了序列化後的二進位數據長度。

/// <summary>
/// 二進位序列化測試
/// </summary>
[Fact]
public void Test_SerializeToBytes_Success()
{
    var buffer = SerializeHelper.SerializeByNative(_codeWFObject, 1);
    _testOutputHelper.WriteLine($"序列化後二進位長度:{buffer.Length}");

    var deserializeObj = SerializeHelper.DeserializeByNative<SystemProcess>(buffer);
    Assert.Equal("碼界工坊", deserializeObj.Name);
}
標準輸出: 
序列化後二進位長度:152

比Json體積小了一半多(366到152),上面單元測試也測試了數據反序列化後驗證數據是否正確,我們就以這個基礎繼續優化。

2.4. 數據類型調整

為了進一步優化二進位數據的大小,我們對數據類型進行了調整。通過對進程數據示例的分析,我們發現一些欄位的數據類型可以更加緊湊地表示。例如,CPU利用率可以只傳遞數字部分(如2.3),而不需要傳遞百分號。這種調整可以減小數據包的大小。

欄位名 數據類型 說明 示例
PID int 進程ID 10565
Name string? 進程名稱 碼界工坊
Publisher string? 發佈者 沙漠盡頭的狼
CommandLine string? 命令行 dotnet CodeWF.Tools.dll
CPU float CPU(所有內核的總處理利用率) 2.3
Memory float 記憶體(進程占用的物理記憶體) 0.1
Disk float 磁碟(所有物理驅動器的總利用率) 0.1
Network float 網路(當前主要網路上的網路利用率 0
GPU float GPU(所有GPU引擎的最高利用率) 2.2
GPUEngine byte GPU引擎,0:無,1:GPU 0 - 3D 1
PowerUsage byte 電源使用情況(CPU、磁碟和GPU對功耗的影響),0:非常低,1:低,2:中,3:高,4:非常高 1
PowerUsageTrend byte 電源使用情況趨勢(一段時間內CPU、磁碟和GPU對功耗的影響),0:非常低,1:低,2:中,3:高,4:非常高 0
Type byte 進程類型,0:應用,1:後臺進程 0
Status byte 進程狀態,0:正常運行,1:效率模式,2:掛起 1

修改測試數據定義:

[NetHead(1, 2)]
public class SystemProcess2 : INetObject
{
    public int PID { get; set; }
    public string? Name { get; set; }
    public string? Publisher { get; set; }
    public string? CommandLine { get; set; }
    public float CPU { get; set; }
    public float Memory { get; set; }
    public float Disk { get; set; }
    public float Network { get; set; }
    public float GPU { get; set; }
    public byte GPUEngine { get; set; }
    public byte PowerUsage { get; set; }
    public byte PowerUsageTrend { get; set; }
    public byte Type { get; set; }
    public byte Status { get; set; }
}
/// <summary>
/// 普通優化欄位數據類型
/// </summary>
private SystemProcess2 _codeWFObject2 = new SystemProcess2()
{
    PID = 10565,
    Name = "碼界工坊",
    Publisher = "沙漠盡頭的狼",
    CommandLine = "dotnet CodeWF.Tools.dll",
    CPU = 2.3f,
    Memory = 0.1f,
    Disk = 0.1f,
    Network = 0,
    GPU = 2.2f,
    GPUEngine = 1,
    PowerUsage = 1,
    PowerUsageTrend = 0,
    Type = 0,
    Status = 1
};

添加單元測試如下:

/// <summary>
/// 二進位序列化測試
/// </summary>
[Fact]
public void Test_SerializeToBytes2_Success()
{
    var buffer = SerializeHelper.SerializeByNative(_codeWFObject2, 1);
    _testOutputHelper.WriteLine($"序列化後二進位長度:{buffer.Length}");

    var deserializeObj = SerializeHelper.DeserializeByNative<SystemProcess2>(buffer);
    Assert.Equal("碼界工坊", deserializeObj.Name);
    Assert.Equal(2.2f, deserializeObj.GPU);
}

測試結果:

標準輸出: 
序列化後二進位長度:99

又優化了50%左右(152到99),爽不爽?繼續,還有更爽的。

2.5. 再次數據類型調整與位域優化

更進一步地,我們引入了位域技術。位域允許我們更加精細地控制欄位在記憶體中的佈局,從而進一步減小二進位數據的大小。我們重新定義了欄位規則,並使用位域來表示一些枚舉值欄位。通過這種方式,我們能夠顯著地減小數據包的大小。

看前面一張表,部分欄位只是一些枚舉值,使用的byte表示,即8位(bit),其中比如進程類型只有2個狀態(0:應用,1:後臺進程),正好可以用1位即表示;像電源使用情況,無非就是5個狀態,用3位可表示全,按這個規則我們重新定義欄位規則如下:

欄位名 數據類型 說明 示例
PID int 進程ID 10565
Name string? 進程名稱 碼界工坊
Publisher string? 發佈者 沙漠盡頭的狼
CommandLine string? 命令行 dotnet CodeWF.Tools.dll
Data byte[8] 固定大小的幾個欄位,見下表定義

固定欄位(Data)的詳細說明如下:

欄位名 Offset Size 說明 示例
CPU 0 10 CPU(所有內核的總處理利用率),最後一位表示小數位,比如23表示2.3% 23
Memory 10 10 記憶體(進程占用的物理記憶體),最後一位表示小數位,比如1表示0.1%,值可根據基本信息計算 1
Disk 20 10 磁碟(所有物理驅動器的總利用率),最後一位表示小數位,比如1表示0.1%,值可根據基本信息計算 1
Network 30 10 網路(當前主要網路上的網路利用率),最後一位表示小數位,比如253表示25.3%,值可根據基本信息計算 0
GPU 40 10 GPU(所有GPU引擎的最高利用率),最後一位表示小數位,比如253表示25.3 22
GPUEngine 50 1 GPU引擎,0:無,1:GPU 0 - 3D 1
PowerUsage 51 3 電源使用情況(CPU、磁碟和GPU對功耗的影響),0:非常低,1:低,2:中,3:高,4:非常高 1
PowerUsageTrend 54 3 電源使用情況趨勢(一段時間內CPU、磁碟和GPU對功耗的影響),0:非常低,1:低,2:中,3:高,4:非常高 0
Type 57 1 進程類型,0:應用,1:後臺進程 0
Status 58 2 進程狀態,0:正常運行,1:效率模式,2:掛起 1

上面這張表是位域規則表,Offset表示欄位在Data位元組數組中的位置(以bit為單位計算),Size表示欄位在Data中占有的大小(同樣以bit單位計算),如Memory欄位,在Data位元組數組中,占據10到20位的空間。

修改類定義如下,註意看代碼中的註釋:

[NetHead(1, 3)]
public class SystemProcess3 : INetObject
{
    public int PID { get; set; }
    public string? Name { get; set; }
    public string? Publisher { get; set; }
    public string? CommandLine { get; set; }
    private byte[]? _data;
    /// <summary>
    /// 序列化,這是實際需要序列化的數據
    /// </summary>
    public byte[]? Data
    {
        get => _data;
        set
        {
            _data = value;

            // 這是關鍵:在反序列化將byte轉換為對象,方便程式中使用
            _processData = _data?.ToFieldObject<SystemProcessData>();
        }
    }

    private SystemProcessData? _processData;

    /// <summary>
    /// 進程數據,添加NetIgnoreMember在序列化會忽略
    /// </summary>
    [NetIgnoreMember]
    public SystemProcessData? ProcessData
    {
        get => _processData;
        set
        {
            _processData = value;

            // 這裡關鍵:將對象轉換為位域
            _data = _processData?.FieldObjectBuffer();
        }
    }
}

public record SystemProcessData
{
    [NetFieldOffset(0, 10)] public short CPU { get; set; }
    [NetFieldOffset(10, 10)] public short Memory { get; set; }
    [NetFieldOffset(20, 10)] public short Disk { get; set; }
    [NetFieldOffset(30, 10)] public short Network { get; set; }
    [NetFieldOffset(40, 10)] public short GPU { get; set; }
    [NetFieldOffset(50, 1)] public byte GPUEngine { get; set; }
    [NetFieldOffset(51, 3)] public byte PowerUsage { get; set; }
    [NetFieldOffset(54, 3)] public byte PowerUsageTrend { get; set; }
    [NetFieldOffset(57, 1)] public byte Type { get; set; }
    [NetFieldOffset(58, 2)] public byte Status { get; set; }
}

添加單元測試如下:

/// <summary>
/// 極限優化欄位數據類型
/// </summary>
private SystemProcess3 _codeWFObject3 = new SystemProcess3()
{
    PID = 10565,
    Name = "碼界工坊",
    Publisher = "沙漠盡頭的狼",
    CommandLine = "dotnet CodeWF.Tools.dll",
    ProcessData = new SystemProcessData()
    {
        CPU = 23,
        Memory = 1,
        Disk = 1,
        Network = 0,
        GPU = 22,
        GPUEngine = 1,
        PowerUsage = 1,
        PowerUsageTrend = 0,
        Type = 0,
        Status = 1
    }
};

/// <summary>
/// 二進位極限序列化測試
/// </summary>
[Fact]
public void Test_SerializeToBytes3_Success()
{
    var buffer = SerializeHelper.SerializeByNative(_codeWFObject3, 1);
    _testOutputHelper.WriteLine($"序列化後二進位長度:{buffer.Length}");

    var deserializeObj = SerializeHelper.DeserializeByNative<SystemProcess3>(buffer);
    Assert.Equal("碼界工坊", deserializeObj.Name);
    Assert.Equal(23, deserializeObj.ProcessData.CPU);
    Assert.Equal(1, deserializeObj.ProcessData.PowerUsage);
}

測試輸出:

標準輸出: 
序列化後二進位長度:86

99又優化到86個位元組,13個位元組哦,有極限網路環境下非常可觀,比如100萬數據,那不就是12.4MB了?關於位域序列化和反序列的代碼這裡不細說了,很枯燥,站長可能也說不清楚,代碼長這樣:

public partial class SerializeHelper
{
    public static byte[] FieldObjectBuffer<T>(this T obj) where T : class
    {
        var properties = typeof(T).GetProperties();
        var totalSize = 0;

        // 計算總的bit長度
        foreach (var property in properties)
        {
            if (!Attribute.IsDefined(property, typeof(NetFieldOffsetAttribute)))
            {
                continue;
            }

            var offsetAttribute =
                (NetFieldOffsetAttribute)property.GetCustomAttribute(typeof(NetFieldOffsetAttribute))!;
            totalSize = Math.Max(totalSize, offsetAttribute.Offset + offsetAttribute.Size);
        }

        var bufferLength = (int)Math.Ceiling((double)totalSize / 8);
        var buffer = new byte[bufferLength];

        foreach (var property in properties)
        {
            if (!Attribute.IsDefined(property, typeof(NetFieldOffsetAttribute)))
            {
                continue;
            }

            var offsetAttribute =
                (NetFieldOffsetAttribute)property.GetCustomAttribute(typeof(NetFieldOffsetAttribute))!;
            dynamic value = property.GetValue(obj)!; // 使用dynamic類型動態獲取屬性值
            SetBitValue(ref buffer, value, offsetAttribute.Offset, offsetAttribute.Size);
        }

        return buffer;
    }

    public static T ToFieldObject<T>(this byte[] buffer) where T : class, new()
    {
        var obj = new T();
        var properties = typeof(T).GetProperties();

        foreach (var property in properties)
        {
            if (!Attribute.IsDefined(property, typeof(NetFieldOffsetAttribute)))
            {
                continue;
            }

            var offsetAttribute =
                (NetFieldOffsetAttribute)property.GetCustomAttribute(typeof(NetFieldOffsetAttribute))!;
            dynamic value = GetValueFromBit(buffer, offsetAttribute.Offset, offsetAttribute.Size,
                property.PropertyType);
            property.SetValue(obj, value);
        }

        return obj;
    }

    /// <summary>
    /// 將值按位寫入buffer
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="value"></param>
    /// <param name="offset"></param>
    /// <param name="size"></param>
    private static void SetBitValue(ref byte[] buffer, int value, int offset, int size)
    {
        var mask = (1 << size) - 1;
        buffer[offset / 8] |= (byte)((value & mask) << (offset % 8));
        if (offset % 8 + size > 8)
        {
            buffer[offset / 8 + 1] |= (byte)((value & mask) >> (8 - offset % 8));
        }
    }

    /// <summary>
    /// 從buffer中按位讀取值
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="offset"></param>
    /// <param name="size"></param>
    /// <param name="propertyType"></param>
    /// <returns></returns>
    private static dynamic GetValueFromBit(byte[] buffer, int offset, int size, Type propertyType)
    {
        var mask = (1 << size) - 1;
        var bitValue = (buffer[offset / 8] >> (offset % 8)) & mask;
        if (offset % 8 + size > 8)
        {
            bitValue |= (buffer[offset / 8 + 1] << (8 - offset % 8)) & mask;
        }

        dynamic result = Convert.ChangeType(bitValue, propertyType); // 根據屬性類型進行轉換
        return result;
    }
}

3. 優化效果與總結

通過逐步優化,我們從最初的Json序列化366位元組減小到了使用普通二進位序列化的152位元組,再進一步使用位域技術優化到了86位元組。這種優化在網路傳輸中是非常可觀的,尤其是在需要傳輸大量數據的情況下。

本文通過一個示例案例,探討了C#對象二進位序列化的優化方法。通過使用位域技術,我們實現了對數據包大小的極限壓縮,提高了網路傳輸的效率。這對於開發C/S程式來說是一種樂趣,也是追求極致性能的一種體現。

最後,我們提供了本文測試源碼的Github鏈接,供讀者參考和學習。

彩蛋:該倉庫有上篇《C#百萬對象序列化深度剖析:如何在網路傳輸中實現速度與體積的完美平衡 (dotnet9.com)》案例代碼,也附帶了TCP、UDP服務端與客戶端聯調測試程式哦。

時間如流水,只能流去不流回。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、定義 定義一個操作中演算法的框架,而將一些步驟延遲到子類中。模板方法模式使得子類不改變一個演算法的結構即可重定義該演算法的特定步驟。模板方法是一種類行為型模式 二、描述 模板方法模式結構比較簡單,其核心是抽象類和其中的模板方法的設計,包含以下兩個角色: 1、AbstractClass(抽象類):在抽象 ...
  • 本文在原文基礎上有刪減,原文參考泛型、Trait 和生命周期。 目錄泛型數據類型在函數定義中使用泛型結構體定義中的泛型枚舉定義中的泛型方法定義中的泛型泛型代碼的性能Trait:定義共同行為定義 trait為類型實現 trait預設實現trait 作為參數Trait Bound 語法通過 + 指定多個 ...
  • 一、pom.xml需要引入的依賴二、項目開啟熔斷器開關 2.1 註解方式 2.2 xml方式三、依賴類缺失問題四、版本匹配安全檢查問題五、測試驗證六、結論 一、pom.xml需要引入的依賴 pom.xml <!-- springboot升級到2.6.7,同樣適用於2.7.0,2.7.18等 --> ...
  • 看到標題大家可能會有點疑惑吧:OpenFeign 不是挺好用的嗎?尤其是微服務之間的遠程調用,平時用的也挺習慣的,為啥要替換呢? ...
  • 當然,我寫的簡易版協程池還有很多可以優化的地方,比如可以實現動態擴容等功能。今天我們要簡單總結一下協程池的優勢,主要是為了降低資源開銷。協程池的好處在於可以重覆利用協程,避免頻繁創建和銷毀協程,從而減少系統開銷,提高系統性能。此外,協程池還可以提高響應速度,因為一旦接收到任務,可以立即執行,不需要等... ...
  • ZooKeeperServer 實現了單機版zookeeper服務端功能,子類實現了更加豐富的分散式集群功能: ZooKeeperServer |-- QuorumZooKeeperServer |-- LeaderZooKeeperServer |-- LearnerZooKeeperServer ...
  • 臨時接到一個需求說讓根據按照下麵的這個圖片的結構來打包下載指定位置下的文件到指定位置! 實現思路: 1.把已經實現的樹形結構的代碼進行調用,拿到他的數據進行創建對應的文件夾 2.因為結構下方的文件沒有特別直觀的資料庫中的關聯關係,所以還需要對於管理關係進行梳理 3.創建好階級文件,然後調用網上找的工 ...
  • 在該文章中,我們基於OpenVINO™ Python API 向大家展示了包含後處理的RT-DETR模型的部署流程,但在實際工業應用中,我們為了與當前軟體平臺集成更多會採用C++平臺,因此在本文中,我們將基於OpenVINO™ C++ API 向大家展示了不包含後處理的RT-DETR模型的部署流程,... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...