Net 高級調試之三:類型元數據介紹(同步塊表、類型句柄、方法描述符等)

来源:https://www.cnblogs.com/PatrickLiu/archive/2023/10/30/17788818.html
-Advertisement-
Play Games

# Unity進階開發-FSM有限狀態機 前言 我們在進行開發時,到了一定程度上,會遇到數十種狀態,繼續使用Unity的Animator控制器會出現大量的bool,float類型的變數,而這些錯綜複雜的變數與Animatator控制器如同迷宮版連線相結合會變得極其的複雜且無法良好維護擴展,出現一個B ...


一、簡介
    今天是《Net 高級調試》的第三篇文章,壓力還是不小的。上一篇文章,我們淺淺的談了談 CLR 和 Windows 載入器是如何載入 Net 程式集的,如何找到程式的入口點的,有了前面的基礎,我們今天看一點更詳細的東西。既然 Windows 操作系統已經載入了 CLR,初始化了應用程式域,載入了我們的 Net 程式,那我們就看看Net 類型在記憶體中的具體樣子。這一篇文章還是有一點難度的,我看第一遍視頻的時候,也不知道說了個啥,後來又看了《Net 高級調試》,似懂非懂。一遍不行,那就再來一遍,還不行,那就再來一遍,俗話說的好,書讀千遍,其意自現。

    如果在沒有說明的情況下,所有代碼的測試環境都是 Net Framewok 4.8,但是,有時候為了查看源碼,可能需要使用 Net Core 的項目,我會在項目章節里進行說明。好了,廢話不多說,開始我們今天的調試工作。
    調試環境我需要進行說明,以防大家不清楚,具體情況我已經羅列出來。
          操作系統:Windows Professional 10
          調試工具:Windbg Preview(可以去Microsoft Store 去下載)
          開發工具:Visual Studio 2022
          Net 版本:Net Framework 4.8
          CoreCLR源碼:源碼下載

二、相關知識
    我們知道了 CLR,瞭解了 JIT,曉得了 Net 的編譯過程,也真正做到了眼見為實,所有的知識點都有根了,這次好好的研究一下類型的東西,當然,這寫東西平時時很難遇到的,就是不懂,也可以寫出東西。但是,如果要想做到,知其一也要知其二的話,這些只是還是有必要瞭解的,對我們寫出高效的代碼還是很有幫助的,一以下就是相關的知識點,我一一羅列出來。

    棧stack(先進後出)是編譯期間就分配好的記憶體空間,因此你的代碼中必須就棧的大小有明確的定義;

    堆heap(隊列優先,先進先出)是程式運行期間動態分配的記憶體空間,你可以根據程式的運行情況確定要分配的堆記憶體的大小

    1、簡介
        類型是 Net 程式中基本編程單元,類型又可以細分為:值類型,引用類型。
        a)、值類型
          枚舉【enum】,結構【Struct】和其他簡單類型,比如:int,float,double,char,bool等。這些類型占據的空間小,一般存放線上程棧上,當然也可以保存在寄存器中、托管堆中或者是私有堆中。
        b)、引用類型
          介面、數組、類和我們自定義的 Class,都是引用類型,這樣的類型,一般占據的空間比較大,它們存在托管堆中,由 GC 負責分配記憶體和回收記憶體來管理這些引用類型的實例。

    2、值類型佈局
        一般而言,方法的參數、在方法內部聲明的局部變數都是存放在當前的線程棧上,也就是說線上程棧上直接存儲值類型的值。

        

    3、引用類型佈局
        class 類型是一種引用類型,實例對象在托管堆中分配空間,並將對象的首地址存在棧地址上。
        

 


    4、同步塊表
        這個名稱叫的不太準確,叫 ObjectHeader 更好點,因為源碼中就是叫這個名稱。托管堆上的每個對象的前面都有一個同步塊索引,它指向 CLR 中私有堆上的同步塊表,同步塊表中可以包含很多信息,比如:對象散列碼、鎖信息、應用程式域索引。
        
        
    5、類型句柄(方法表)
        類型句柄是針對類型的描述信息,比如:這個類中有多少個方法,方法的結構,方法的欄位信息等。
        
    6、方法描述符
        用來描述C# 方法在 CLR 層面的特征,使用 MethodDesc 類結構來承載,記錄了方法的位元組碼,所屬類,Token 等信息。

    7、模塊
        模塊是包含在程式集中,程式集是一個 Net 程式的部署單元,可以用 !dumpAssembly 和 !dumpmodule 顯示各自的信息。

    8、元數據標記
        因為程式集是自描述的,類型信息都有響應的 Metadata 來表示,可以使用 ILSpy 來查看。可以使用 !token2ee 來檢索對應的方法。
        
    9、EEClass
        EEClass 和 MethodTable 是同級別的,用來描述 C# 的一個類,可以使用 !dumpclass 來顯示類型的 EECLass 信息。

三、調試過程
    廢話不多說,這一節是具體的調試操作的過程,有可以說是眼見為實的過程,在開始之前,我還是要啰嗦兩句,這一節分為兩個部分,第一部分是測試的源碼部分,沒有代碼,當然就談不上測試了,調試必須有載體。第二部分就是根據具體的代碼來證實我們學到的知識,是具體的眼見為實。
    1、測試源碼
        1.1、Example_3_1_1
 1 namespace Example_3_1_1
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             int a = 10;
 8             long b = 11;
 9             short c = 12;
10             Console.ReadLine();
11         }
12     }
13 }
View Code
        1.2、Example_3_1_2
 1 namespace Example_3_1_2
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             var person = new Person()
 8             {
 9                 Name = "jack",
10                 Age = 20
11             };
12             Console.ReadLine();
13         }
14     }
15 
16     public class Person
17     {
18         public string Name { get; set; }
19 
20         public int Age { get; set; }
21     }
22 }
View Code
        1.3、Example_3_1_3            
 1 namespace Example_3_1_3
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             var person = new Person() { Name = "jack", Age = 20 };
 8             var hashcode = person.GetHashCode().ToString("x");
 9             Console.WriteLine($"hashcode={hashcode}");
10             Debugger.Break();
11             Console.ReadLine();
12         }
13     }
14 
15     public class Person
16     {
17         public string Name { get; set; }
18 
19         public int Age { get; set; }
20     }
21 }
View Code
        1.4、Example_3_1_4            
 1 namespace Example_3_1_4
 2 {
 3     internal class Program
 4     {
 5         public static Person person=new Person();
 6 
 7         static void Main(string[] args)
 8         {
 9             Task.Run(() =>
10             {
11                 lock (person)
12                 {
13                     Console.WriteLine($"tid={Environment.CurrentManagedThreadId}進入鎖了");
14                     Console.ReadLine();
15                 }
16             });
17             Task.Run(() => {
18                 lock (person)
19                 {
20                     Console.WriteLine($"tid={Environment.CurrentManagedThreadId}進入鎖了");
21                     Console.ReadLine();
22                 }
23             });
24 
25             Console.ReadLine();
26         }
27     }
28 
29     public class Person
30     {
31         public string Name { get; set; }
32 
33         public int Age { get; set; }
34     }
35 }
View Code


        1.5、Example_3_1_5     

 1 namespace Example_3_1_5
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             var person = new Person()
 8             {
 9                 Name = "jack",
10                 Age = 20
11             };
12             Console.WriteLine("Hello World!");
13             Console.ReadLine();
14         }
15     }
16     public class Person
17     {
18         public string Name { get; set; }
19 
20         public int Age { get; set; }
21     }
22 }
View Code

 

        1.6、Example_3_1_5_1(這個項目是 Net 7.0版本的)
 1 namespace Example_3_1_5_1
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             var person = new Person()
 8             {
 9                 Name = "jack",
10                 Age = 20
11             };
12             Console.WriteLine("Hello World!");
13             Console.ReadLine();
14         }
15     }
16     public class Person
17     {
18         public string Name { get; set; }
19 
20         public int Age { get; set; }
21     }
22 }
View Code
  
    2、眼見為實
        2.1、值類型的佈局
            代碼樣例:Example_3_1_1
            我們使用 Windbg Preview 調試器,通過【launch executable】菜單載入【Example_3_1_1.exe】項目,通過【g】命令,運行程式,調試器運行代【Console.ReadLine()】次會暫停執行,然後我們點擊【break】按鈕,進入調試狀態。我們還需要通過【~0s】命令,切換到主線程,當然,我們可以使用【cls】命令清理一下調試器顯示的過多信息,自己來決定,我是會清理的。
             !clrstack -l 這個命令是顯示當前的線程調用棧局部變數,l 表示 local,局部變數,代碼關鍵部分
 1 0:000> !clrstack -l
 2 OS Thread Id: 0x317c (0)
 3 Child SP       IP Call Site
 4 00aff1c4 778e10fc [InlinedCallFrame: 00aff1c4] 
 5 00aff1c0 6fee9b71 ...(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
 6 
 7 ......
 8 
 9 00aff2c0 00d3089e Example_3_1_1.Program.Main(System.String[]) [E:\...\Example_3_1_1\Program.cs @ 12]
10     LOCALS:【表示局部變數】
11         0x00aff2d0 = 0x0000000a【0x00aff2d0是棧地址,0x0000000a 是棧上的值,這是十六進位的】
12         0x00aff2c8 = 0x0000000b0x00aff2c8是棧地址,0x0000000b 是棧上的值,這是十六進位的】
13         0x00aff2c4 = 0x0000000c0x00aff2d0是棧地址,0x0000000c 是棧上的值,這是十六進位的】
14 
15 00aff448 70f1f036 [GCFrame: 00aff448] 

            以上顯示的紅色部分是最重要的,LOCALS 表示局部變數,11,12,13 三行是具體的局部變數,等號前面是 線程棧上的變數地址,後面是具體的值,我們可以使用【?】命令查看具體的值。

1 0:000> ? 0x0000000a
2 Evaluate expression: 10 = 0000000a
3 0:000> ? 0x0000000b
4 Evaluate expression: 11 = 0000000b
5 0:000> ? 0x0000000c
6 Evaluate expression: 12 = 0000000c
View Code             對應 C# 代碼中的賦值操作。
            
            由於棧的特點,先進後出,後進先出,所以說【a】是最先入棧的,在棧底,依次是【b】,最上面的是【c】,所以我們從【c】的地址列印,可以顯示【c、b、a】的值。由此,我們執行【dp】命令,效果如下。
1 0:000> dp 0x00aff2c4 l4
2 00aff2c4  0000000c 0000000b 00000000 0000000a
            我們可以繼續驗證,由於棧的地址是由高到低的分配,所以,【c】的地址加上 0x4,為什麼加4呢,雖然【c】占用2個位元組,但是會按4個位元組算的,就是【b】變數的值,如下:
1 0:000> dp 00aff2c4+0x4 l1
2 00aff2c8  0000000b

            繼續驗證,【b】的地址加上 0x8,就是【a】變數的值,為什麼是加8呢,因為【b】占用8個位元組,如下:

1 0:000> dp 00aff2c8+0x8 l1
2 00aff2d0  0000000a

            當然,我們可以以【c】變數的地址為基準,算出【b】和【a】的值,如下:

1 0:000> dp 0x00aff2c4+0x4 l1(以c 的地址為基準,找到b的地址,加4)
2 00aff2c8  0000000b
3 0:000> dp 0x00aff2c4+0xc l1(以c 的地址為基準,找到a的地址,加12,十六進位就是0xc)
4 00aff2d0  0000000a

        2.2、引用類型的佈局
            代碼樣例:Example_3_1_2
            我們使用 Windbg Preview 調試器,通過【launch executable】菜單載入【Example_3_1_2.exe】項目,通過【g】命令,運行程式,調試器運行代【Console.ReadLine()】次會暫停執行,然後我們點擊【break】按鈕,進入調試狀態。我們還需要通過【~0s】命令,切換到主線程,當然,我們可以使用【cls】命令清理一下調試器顯示的過多信息,自己來決定,我是會清理的。
            我們先使用【!clrstack -a】命令,查看線程棧的局部變數。
 1 0:000> !clrstack -a
 2 OS Thread Id: 0x3930 (0)
 3 Child SP       IP Call Site
 4 0133ee8c 778e10fc [InlinedCallFrame: 0133ee8c] 
 5 0133ee88 6fee9b71
 6 ......
 7 0133ef88 018c08b1 Example_3_1_2.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\......\Example_3_1_2\Program.cs @ 14]
 8     PARAMETERS:
 9         args (0x0133ef94) = 0x033b24bc
10     LOCALS:
11         0x0133ef90 = 0x033b24e0(0x0133ef90 是棧地址,0x033b24e0 person變數的引用地址)
12 
13 0133f108 70f1f036 [GCFrame: 0133f108] 

            我們可以通過【dp】命令查看棧地址,值是 033b24e0,這個值就是 person變數引用的地址。

1 0:000> dp 0x0133ef90 l1
2 0133ef90  033b24e0(這個地址就是 person變數的地址)

            我們可以使用【!do|!DumpObj】命令,查看對象的詳情。

 1 0:000> !DumpObj /d 033b24e0
 2 Name:        Example_3_1_2.Person
 3 MethodTable: 01874e1c
 4 EEClass:     01871314
 5 Size:        16(0x10) bytes
 6 File:        E:\Visual Studio 2022\Source\Projects\......\Example_3_1_2\bin\Debug\Example_3_1_2.exe
 7 Fields:
 8       MT    Field   Offset                 Type VT     Attr    Value Name
 9 6fa424e4  4000001        4        System.String  0 instance 033b24c8 <Name>k__BackingField
10 6fa442a8  4000002        8         System.Int32  1 instance       20 <Age>k__BackingField

            033b24c8 <Name>k__BackingField,這個是 string 類型的欄位,033b24c8又是一個引用地址,我們繼續【!do】,查看詳情。

 1 0:000> !DumpObj /d 033b24c8
 2 Name:        System.String
 3 MethodTable: 6fa424e4
 4 EEClass:     6fb47690
 5 Size:        22(0x16) bytes
 6 File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
 7 String:      jack(這個就是我們賦值的)
 8 Fields:
 9       MT    Field   Offset                 Type VT     Attr    Value Name
10 6fa442a8  4000283        4         System.Int32  1 instance        4 m_stringLength
11 6fa42c9c  4000284        8          System.Char  1 instance       6a m_firstChar
12 6fa424e4  4000288       70        System.String  0   shared   static Empty
13     >> Domain:Value  0151ca70:NotInit  <<

            每一個引用類型對象都包含兩個附加欄位,一個是同步塊索引,另外一個就是類型句柄。我們通過 !clrstack -l 獲取的 Program.Main 方法的句柄變數,我們可以通過【dp】命令查看一下細節,執行如下命令:dp 0x033b24e0-0x4 l4LOCALS:0x0133ef90 = 0x033b24e0)  

1 0:000> dp 0x033b24e0-0x4 l4
2 033b24dc  00000000 01874e1c 033b24c8 00000014

            033b24dc 00000000 01874e1c 033b24c8 00000014,033b24dc 這個地址就是同步塊的地址,0x033b24e0 person引用地址只想類型句柄01874e1c,類型句柄再用4個位元組,所以 0x033b24e0-0x4,向前移動4個位元組,就是同步塊的指針地址。033b24c8這個部分就是person變數的實例欄位了。

 1 0:000> !do 033b24c8
 2 Name:        System.String
 3 MethodTable: 6fa424e4
 4 EEClass:     6fb47690
 5 Size:        22(0x16) bytes
 6 File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
 7 String:      jack
 8 Fields:
 9       MT    Field   Offset                 Type VT     Attr    Value Name
10 6fa442a8  4000283        4         System.Int32  1 instance        4 m_stringLength
11 6fa42c9c  4000284        8          System.Char  1 instance       6a m_firstChar
12 6fa424e4  4000288       70        System.String  0   shared   static Empty
13     >> Domain:Value  0151ca70:NotInit  <<

            00000014是十六進位的,表示的就是20。

1 0:000> ? 00000014
2 Evaluate expression: 20 = 00000014

            如果我們想查看類型句柄的詳情,我們可以使用【!dumpmt】命令。

 1 0:000> !dumpmt 01874e1c
 2 EEClass:         01871314
 3 Module:          01874044
 4 Name:            Example_3_1_2.Person
 5 mdToken:         02000003
 6 File:            E:\Visual Studio 2022\Source\Projects\......\Example_3_1_2\bin\Debug\Example_3_1_2.exe
 7 BaseSize:        0x10
 8 ComponentSize:   0x0
 9 Slots in VTable: 9
10 Number of IFaces in IFaceMap: 0

        2.3、同步塊包含對象散列碼
            代碼樣例:Example_3_1_3
            我們使用 Windbg Preview 調試器,通過【launch executable】菜單載入【Example_3_1_3.exe】項目,通過【g】命令,運行程式,調試器運行代【Debugger.Break()】次會暫停執行,我們程式的輸出結果是:hashcode=2bf8098。
            接下來,我們看看對象頭中是否散列碼,就可以檢驗了。我們先使用【!clrstack -l】命令,看看線程棧。
 1 0:000> !clrstack -l
 2 OS Thread Id: 0x2600 (0)
 3 Child SP       IP Call Site
 4 00dcef18 7696f262 [HelperMethodFrame: 00dcef18] System.Diagnostics.Debugger.BreakInternal()
 5 00dcef94 705bf195 System.Diagnostics.Debugger.Break() [f:\dd\ndp\clr\src\BCL\system\diagnostics\debugger.cs @ 91]
 6 
 7 00dcefbc 02f40905 Example_3_1_3.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\......\Example_3_1_3\Program.cs @ 13]
 8     LOCALS:
 9         0x00dcefd0 = 0x030b2510
10         0x00dcefcc = 0x030b39ac
11         0x00dcefd8 = 0x02bf8098
12 
13 00dcf154 70f1f036 [GCFrame: 00dcf154] 

            0x00dcefd0 = 0x030b2510,這個地址就是我們聲明的 person 變數。既然由了對象的地址,只要用對象的地址,減去 0x4,就是同步塊的地址,然後使用【dp】命令就可以查看了。   

1 0:000> dp 0x030b2510-0x4 l4
2 030b250c  0ebf8098 01414e1c 030b24c8 00000014

            第二行的第二列以前是0,表示沒有任何數據,現在有值了。現在我們用這個值,減去我們得到的散列碼,看看是什麼。

1 0:000> ? 0ebf8098-2bf8098
2 Evaluate expression: 201326592 = 0c000000

            0c000000它就是一個掩碼,告訴CLR 這個欄位中包含的是散列碼的值,起到標識的作用,因為還可以存放其他東西。


        2.4、同步塊包含對象鎖信息
            代碼樣例:Example_3_1_4
            我們使用 Windbg Preview 調試器,通過【launch executable】菜單載入【Example_3_1_4.exe】項目,通過【g】命令,運行程式,調試器運行代【Console.ReadLine()】次會暫停執行,然後我們點擊【break】按鈕,進入調試狀態,此時,我們程式的輸出是:tid=3進入鎖了,說明 Person 被鎖住了。
            接下來,我們就要查看對象的對象頭包含什麼東西,意圖很明顯。
            我們首先找到 Person 對象,可以使用【!dumpheap -type Person】命令獲取對象。
1 0:001> !dumpheap -type Person
2  Address       MT     Size
3 033824c8 014d4e60       16     
4 
5 Statistics:
6       MT    Count    TotalSize Class Name
7 014d4e60        1           16 Example_3_1_4.Person
8 Total 1 objects

            紅色標記的就是Person 對象的地址,然後我們使用這個地址減去 0x4,就可以獲取同步塊索引了。    

1 0:001> dp 033824c8-0x4 l4
2 033824c4  08000007 014d4e60 00000000 00000000

            08000007 就是同步塊索引的值,08是一個掩碼,表示是同步塊索引,07就是線程 Id。我們可以使用【!syncblk】命令來驗證。

 1 0:001> !syncblk
 2 Index         SyncBlock MonitorHeld Recursion Owning Thread Info          SyncBlock Owner
 3     6 015670f0            3         1 01512ba8 3d4c   0   03388210 System.IO.TextReader+SyncTextReader
 4     7 01567124            3         1 0157c340 f8     9   033824c8 Example_3_1_4.Person(被鎖的對象是 person)

              3:(一個線程持有鎖,一個等待鎖)

 5 -----------------------------
 6 Total           7
 7 CCW             1
 8 RCW             2
 9 ComClassFactory 0
10 Free            
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 從配置文件中獲取屬性應該是SpringBoot開發中最為常用的功能之一,但就是這麼常用的功能,仍然有很多開發者抓狂~今天帶大家簡單回顧一下這六種的使用方式: ...
  • wmproxy wmproxy是由Rust編寫,已實現http/https代理,socks5代理, 反向代理,靜態文件伺服器,內網穿透,配置熱更新等, 後續將實現websocket代理等,同時會將實現過程分享出來, 感興趣的可以一起造個輪子法 項目地址 gite: https://gitee.com ...
  • 來源:https://gitee.com/niefy/wx-manage wx-manage wx-manage是一個支持公眾號管理系統,支持多公眾號接入。 wx-manage提供公眾號菜單、自動回覆、公眾號素材、簡易CMS、等管理功能,請註意本項目僅為管理後臺界面,需配合後端程式wx-api一起使 ...
  • 箱型圖(Box Plot),也稱為盒須圖或盒式圖,1977年由美國著名統計學家約翰·圖基(John Tukey)發明。是一種用作顯示一組數據分佈情況的統計圖,因型狀如箱子而得名。 它能顯示出一組數據的最大值、最小值、中位數及上下四分位數。箱子的頂端和底端,分別代表上下四分位數。箱子中間的是中位數線, ...
  • 一款輕量級、高性能、強類型、易擴展符合C#開發者的JAVA自研ORM github地址 easy-query https://github.com/xuejmnet/easy-query gitee地址 easy-query https://gitee.com/xuejm/easy-query 背景 ...
  • 前言 有個項目,需要在前端有個管理終端可以 SSH 到主控機的終端,如果不考慮用戶使用 vim 等需要在控制台內現實界面的軟體的話,其實使用 Process 類型去啟動相應程式就夠了。而這次的需求則需要考慮用戶會做相關設置。 原理 這裡用到的原理是偽終端。偽終端(pseudo terminal)是現 ...
  • 在WPF開發中,預設控制項的樣式常常無法滿足實際的應用需求,我們通常都會採用引入第三方控制項庫的方式來美化UI,使得應用軟體的設計風格更加統一。常用的WPF的UI控制項庫主要有以下幾種,如:Modern UI for WPF,MaterialDesignInXamlToolkit,PanuonUI,New ...
  • 目錄子網劃分定長子網劃分子網劃分的方法子網掩碼可變長子網劃分無類別編址網路首碼路由聚合特殊用途的IP地址專用網路地址鏈路本地地址運營商級NAT共用地址用於文檔的測試網路地址IP地址的規劃和分配IP地址的規劃和分配方法IP地址的規劃和分配實例 子網劃分 定長子網劃分 子網劃分的方法 從IP地址的主機號 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...