FreeRTOS教程6 互斥量

来源:https://www.cnblogs.com/lc-guo/p/18068439
-Advertisement-
Play Games

本文主要學習 FreeRTOS 互斥量的相關知識,包括優先順序翻轉問題、優先順序繼承、死鎖現象、創建/刪除互斥量 和 獲取/釋放互斥量等知識 ...


1、準備材料

正點原子stm32f407探索者開發板V2.4

STM32CubeMX軟體(Version 6.10.0

Keil µVision5 IDE(MDK-Arm

野火DAP模擬器

XCOM V2.6串口助手

2、學習目標

本文主要學習 FreeRTOS 互斥量的相關知識,包括優先順序翻轉問題、優先順序繼承、死鎖現象、創建/刪除互斥量 和 獲取/釋放互斥量等知識

3、前提知識

3.1、優先順序翻轉問題

使用二值信號量用於進程間同步時可能會出現優先順序翻轉的問題,什麼是“優先順序翻轉”問題呢?考慮如下所述的任務運行過程

  • 在 t1 時刻,低優先順序的任務 TaskLP 切入運行狀態,並且獲取到了一個二值信號量 Binary Semaphores
  • 在 t2 時刻,高優先順序的任務 TaskHP 請求獲取二值信號量 Binary Semaphores ,但是由於 TaskLP 還未釋放該二值信號量,所以在 t3 時刻,任務 TaskHP 進入阻塞狀態等待二值信號量被釋放
  • 在 t4 時刻,中等優先順序的任務 TaskMP 進入就緒狀態,由於不需要獲取二值信號量,因此搶占低優先順序任務任務 TaskLP 切入運行狀態
  • 在 t5 時刻,任務 TaskMP 運行結束,任務 TaskLP 再次切入運行狀態
  • 在 t6 時刻,任務 TaskLP 運行結束,釋放二值信號量 Binary Semaphores,此時任務 TaskHP 從等待二值信號量的阻塞狀態切入運行狀態
  • 在t7時刻,任務 TaskHP 運行結束

根據上述流程讀者可以發現一個問題,即在 t4 時刻中等優先順序的任務 TaskMP 先於高優先順序的任務 TaskHP 搶占了處理器,這破壞了 FreeRTOS 基於優先順序搶占式執行的原則,我們將這種情況稱為優先順序翻轉問題,上述描述的任務運行過程具體時刻流程圖如下圖所示

優先順序翻轉可能是一個嚴重的問題,但在小型嵌入式系統中,通常可以在系統設計時通過考慮如何訪問資源來避免該問題

3.2、優先順序繼承

為瞭解決使用二值信號量可能會出現的優先順序翻轉問題,對二值信號量做了改進,增加了一種名為 “優先順序繼承” 的機制,改進後的實例稱為了互斥量,註意雖然互斥量可以減緩優先順序翻轉問題的出現,但是並不能完全杜絕

接下來我們來通過例子介紹什麼是優先順序繼承?

仍然考慮由 “3.1、優先順序翻轉問題” 小節中提出的任務運行過程的例子,具體流程如下所述,讀者可以細心理解其中的不同之處

  • 在 t1 時刻,低優先順序的任務 TaskLP 切入運行狀態,並且獲取到了一個互斥量 Mutexes
  • 在 t2 時刻,高優先順序的任務 TaskHP 請求獲取互斥量 Mutexes ,但是由於 TaskLP 還未釋放該互斥量,所以在 t3 時刻,任務 TaskHP 進入阻塞狀態等待互斥量被釋放,但是與二值信號量不同的是,此時 FreeRTOS 將任務 TaskLP 的優先順序臨時提高到與任務 TaskHP 一致的優先順序,也即高優先順序
  • 在 t4 時刻,中等優先順序的任務 TaskMP 進入就緒狀態發生任務調度,但是由於任務 TaskLP 此時優先順序被提高到了高優先順序,因此任務 TaskMP 仍然保持就緒狀態等待優先順序較高的任務執行完畢
  • 在 t5 時刻,任務 TaskLP 執行完畢釋放互斥量 Mutexes,此時任務 TaskHP 搶占處理器切入運行狀態,並恢復任務 TaskLP 原來的優先順序
  • 在 t6 時刻,任務 TaskHP 執行完畢,此時輪到任務 TaskMP 執行
  • 在 t7 時刻,任務 TaskMP 運行結束

根據互斥量的上述任務流程讀者可以發現與二值信號量的不同之處,上述描述的任務運行過程具體時刻流程圖如下圖所示

3.3、什麼是互斥量?

互斥量/互斥鎖是一種特殊類型的二進位信號量,用於控制對在兩個或多個任務之間共用資源的訪問

互斥鎖可以被視為一個與正在共用的資源相關聯的令牌,對於合法訪問資源的任務,它必須首先成功 “獲取” 令牌,成為資源的持有者,當持有者完成對資源的訪問之後,其需要 ”歸還” 令牌,只有 “歸還” 令牌之後,該令牌才可以再次被其他任務所 “獲取” ,這樣保證了互斥的對共用資源的訪問,上述機制如下圖所示 (註釋1)

3.4、死鎖現象

“死鎖” 是使用互斥鎖進行互斥的另一個潛在陷阱,當兩個任務因為都在等待對方占用的資源而無法繼續進行時,就會發生死鎖,考慮如下所述的情況

  1. 任務 A 執行併成功獲取互斥量 X
  2. 任務 A 被任務 B 搶占
  3. 任務 B 在嘗試獲取互斥量 X 之前成功獲取互斥量 Y,但互斥量 X 由任務 A 持有,因此對任務 B 不可用,任務 B 選擇進入阻塞狀態等待互斥量 X 被釋放
  4. 任務 A 繼續執行,它嘗試獲取互斥量 Y,但互斥量 Y 由任務 B 持有,所以對於任務 A 來說是不可用的,任務 A 選擇進入阻塞狀態等待待釋放的互斥量 Y

經過上述的這樣一個過程,讀者可以發現任務 A 在等待任務 B 釋放互斥量 Y ,而任務 B 在等待任務 A 釋放互斥量 X ,兩個任務都在阻塞狀態無法執行,從而導致 ”死鎖“ 現象的發生,與優先順序翻轉一樣,避免 “死鎖” 的最佳方法是在設計時考慮其潛在影響,並設計系統以確保不會發生死鎖

3.5、什麼是遞歸互斥量?

任務也有可能與自身發生死鎖,如果任務嘗試多次獲取相同的互斥體而不首先返回互斥體,就會發生這種情況,考慮以下設想:

  1. 任務成功獲取互斥鎖
  2. 在持有互斥體的同時,任務調用庫函數
  3. 庫函數的實現嘗試獲取相同的互斥鎖,併進入阻塞狀態等待互斥鎖變得可用

在此場景結束時,任務處於阻塞狀態以等待互斥體返回,但任務已經是互斥體持有者。 由於任務處於阻塞狀態等待自身,因此發生了死鎖

通過使用遞歸互斥體代替標準互斥體可以避免這種類型的死鎖,同一任務可以多次 “獲取” 遞歸互斥鎖,並且只有在每次 “獲取” 遞歸互斥鎖之後都調用一次 “釋放” 遞歸互斥鎖,才會返回該互斥鎖

因此遞歸互斥量可以視為特殊的互斥量,一個互斥量被一個任務獲取之後就不能再次獲取,其他任務想要獲取該互斥量必須等待這個任務釋放該互斥連,但是遞歸互斥量可以被一個任務重覆獲取多次,當然每次獲取必須與一次釋放配對使用

註意不管是互斥量,還是遞歸互斥量均存在優先順序繼承機制,但是由於 ISR 並不是任務,因此互斥量和遞歸互斥量不能在中斷中使用

3.5、創建互斥量

互斥量在使用之前必須先創建,因為互斥量分為互斥量和遞歸互斥量兩種,所以 FreeRTOS 也提供了不同的 API 函數,具體如下所述

/**
  * @brief  動態分配記憶體創建互斥信號量函數
  * @retval 創建互斥信號量的句柄
  */
SemaphoreHandle_t xSemaphoreCreateMutex(void);

/**
  * @brief  靜態分配記憶體創建互斥信號量函數
  * @param  pxMutexBuffer:指向StaticSemaphore_t類型的變數,該變數將用於保存互斥鎖型信號量的狀態
  * @retval 返回成功創建後的互斥鎖的句柄,如果返回NULL則表示記憶體不足創建失敗
  */
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer);

/**
  * @brief  動態分配記憶體創建遞歸互斥信號量函數
  * @retval 創建遞歸互斥信號量的句柄,如果返回NULL則表示記憶體不足創建失敗
  */
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void);

/**
  * @brief  動態分配記憶體創建二值信號量函數
  * @param  pxMutexBuffer:指向StaticSemaphore_t類型的變數,該變數將用於保存互斥鎖型信號量的狀態
  */
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(
								StaticSemaphore_t pxMutexBuffer);

3.6、獲取互斥量

獲取互斥量直接使用獲取信號量的函數即可,但對於遞歸互斥量需要專門的獲取函數,具體如下所述

/**
  * @brief  獲取信號量函數
  * @param  xSemaphore:正在獲取的信號量的句柄
  * @param  xTicksToWait:等待信號量變為可用的時間
  * @retval 成功獲取信號量則返回pdTRUE, xTicksToWait過期,信號量不可用,則返回pdFALSE
  */
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);

/**
  * @brief  獲取遞歸互斥量
  * @param  xMutex:正在獲得的互斥鎖的句柄
  * @param  xTicksToWait:等待信號量變為可用的時間
  * @retval 成功獲取信號量則返回pdTRUE, xTicksToWait過期,信號量不可用,則返回pdFALSE
  */
BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex,
								   TickType_t xTicksToWait);

3.7、釋放互斥量

釋放互斥量直接使用釋放信號量的函數即可,但對於遞歸互斥量需要專門的釋放函數,具體如下所述

/**
  * @brief  釋放信號量函數
  * @param  xSemaphore:要釋放的信號量的句柄
  * @retval 成功釋放信號量則返回pdTRUE, 若發生錯誤,則返回pdFALSE
  */
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);

/**
  * @brief  釋放遞歸互斥量
  * @param  xMutex:正在釋放或“給出”的互斥鎖的句柄
  * @retval 成功釋放遞歸互斥量後返回pdTRUE
  */
BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex);

3.8、刪除互斥量

直接使用信號量的刪除函數即可,具體如下所述

/**
  * @brief  獲取信號量函數
  * @param  xSemaphore:要刪除的信號量的句柄
  * @retval None
  */
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);

4、實驗一:優先順序翻轉問題

4.1、實驗目標

既然實驗是討論優先順序翻轉問題,那麼我們來複現 “3.1、優先順序翻轉問題” 小節中所描述到的任務運行過程,具體如下所述

  1. 創建一個二值信號量 BinarySem_PI,用於演示優先順序翻轉問題
  2. 創建一個低優先順序任務 Task_Low ,在該任務中獲取二值信號量 BinarySem_PI ,並通過延時模擬長時間連續運行,運行結束後釋放該二值信號量,整個過程會通過串口輸出提示信息
  3. 創建一個中等優先順序任務 Task_Middle,該任務負責在 Task_Low 模擬長時間連續運行期間搶占其處理器控制許可權
  4. 創建一個高優先順序任務 Task_High,該任務總是嘗試獲取二值信號量 BinarySem_PI

4.2、CubeMX相關配置

首先讀者應按照"FreeRTOS教程1 基礎知識"章節配置一個可以正常編譯通過的 FreeRTOS 空工程,然後在此空工程的基礎上增加本實驗所提出的要求

本實驗需要初始化 USART1 作為輸出信息渠道,具體配置步驟請閱讀“STM32CubeMX教程9 USART/UART 非同步通信”,如下圖所示

單擊 Middleware and Software Packs/FREERTOS,在 Configuration 中單擊 Tasks and Queues 選項卡雙擊預設任務修改其參數,然後增加另外兩個不同優先順序的任務,具體如下圖所示

然後在 Configuration 中單擊 Timers and Semaphores ,在 Binary Semaphores 中單擊 Add 按鈕新增加一個名為 BinarySem_PI 的二值信號量,具體如下圖所示

配置 Clock Configuration 和 Project Manager 兩個頁面,接下來直接單擊 GENERATE CODE 按鈕生成工程代碼即可

4.3、添加其他必要代碼

按照 “STM32CubeMX教程9 USART/UART 非同步通信” 實驗 “6、串口printf重定向” 小節增加串口 printf 重定向代碼,具體不再贅述

首先應該在 freertos.c 中添加信號量相關 API 和 printf() 函數的頭文件,如下所述

/*freertos.c中添加頭文件*/
#include "semphr.h"
#include "stdio.h"

然後在該文件中實現三個不同優先順序的任務,主要是一些串口輸出給用戶的提示信息,方便演示實驗目的,具體如下所述

/*低優先順序任務*/
void AppTask_Low(void *argument)
{
  /* USER CODE BEGIN AppTask_Low */
  /* Infinite loop */
	uint8_t str1[]="Task_Low take it\r\n";
	uint8_t str2[]="Task_Low give it\r\n";
	uint8_t str3[]="return Task_Low\r\n";
	for(;;)
	{
		//獲取信號量
		if(xSemaphoreTake(BinarySem_PIHandle, pdMS_TO_TICKS(200))==pdTRUE)  
		{
			printf("%s",str1);
			//模擬任務連續運行
			HAL_Delay(500);		
			printf("%s",str3);
			HAL_Delay(500);
			printf("%s",str2);
			//釋放信號量
			xSemaphoreGive(BinarySem_PIHandle);		
		}
	}
  /* USER CODE END AppTask_Low */
}

/*中等優先順序任務*/
void AppTask_Middle(void *argument)
{
  /* USER CODE BEGIN AppTask_Middle */
  /* Infinite loop */
	uint8_t strMid[]="Task_Middle is running\r\n";
	for(;;)
	{
		printf("%s", strMid);
		vTaskDelay(500);
	}
  /* USER CODE END AppTask_Middle */
}

/*高優先順序任務*/
void AppTask_High(void *argument)
{
  /* USER CODE BEGIN AppTask_High */
  /* Infinite loop */
	uint8_t strHigh1[]="Into Task_High\r\n";
	uint8_t strHigh2[]="Task_High get token\r\n";
	uint8_t strHigh3[]="Task_High give token\r\n";
	for(;;)
	{
		printf("%s",strHigh1);
		//獲取信號量
		if(xSemaphoreTake(BinarySem_PIHandle, portMAX_DELAY)==pdTRUE)  
		{
			printf("%s",strHigh2);
			printf("%s",strHigh3);
			//釋放信號量
			xSemaphoreGive(BinarySem_PIHandle);	
		}
		vTaskDelay(500);
	}
  /* USER CODE END AppTask_High */
}

在 "FreeRTOS教程5 信號量" 文章 ”3.2、創建信號量“ 小節中曾提到,信號量被創建完之後是無效的,但是這裡我們需要讓剛創建的二值信號量有效,否則 Task_High 和 Task_Low 都將無法獲取二值信號量,因此最後修改二值信號量的初始值為 1 即可,具體如下所示

/*將初始值0修改為1*/
BinarySem_PIHandle = osSemaphoreNew(1, 1, &BinarySem_PI_attributes);

4.4、燒錄驗證

燒錄程式,打開串口助手,按住開發板複位按鍵,目的是為了讓串口助手接收程式從最開始輸出的信息,這裡我們只分析第一輪,因為延時、語句執行等微小的時間差異會導致第二輪任務進入阻塞和退出阻塞的時間與第一輪有差異,如下所述為第一輪詳細的任務執行流程

  1. 當創建完三個不同優先順序的任務後不會立即得到執行,而是進入就緒狀態等待調度器的啟動
  2. 當調度器啟動之後會按照優先順序從最高優先順序開始執行,因此串口輸出 “Into Task_High” 表示進入高優先順序任務,然後在高優先順序任務 Task_High 中獲得二值信號量,然後立馬釋放二值信號量,最後進入 500ms 的阻塞狀態
  3. 當高優先順序任務進入阻塞狀態後,接下來會執行就緒狀態的中等優先順序任務 Task_Middle ,該任務無具體功能,僅僅通過串口輸出 “Task_Middle is running”,然後同樣進入 500ms 的阻塞狀態
  4. 由於高優先順序和中等優先順序任務都進入阻塞狀態,這時才輪到低優先順序任務 Task_Low 執行,低優先順序任務 Task_Low 成功獲取到二值信號量並通過串口輸出 “Task_Low take it” ,然後利用 500ms 的 HAL 庫延時函數模擬連續運行
  5. 在 Task_Low 連續運行期間,在其即將執行完第一個 HAL_Delay(500); 時,高優先順序任務 Task_High 從 500ms 的阻塞狀態恢復,然後嘗試獲取已經被 Task_Low 獲取的二值信號量,結果就是進入阻塞狀態等待 Task_Low 釋放二值信號量
  6. 緊接著 Task_Middle 從 500ms 的阻塞狀態恢復,通過串口輸出 “Task_Middle is running”,接著再次進入 500ms 阻塞狀態
  7. 由於高優先順序和中等優先順序任務再次進入阻塞狀態,因此調度器返回 Task_Low 被搶占時的程式處繼續執行,因此 Task_Low 通過串口輸出 “return Task_Low” ,然後利用第二個 HAL_Delay(500); 繼續模擬長時間運行
  8. 在 Task_Low 第二個 HAL_Delay(500); 即將執行完畢時,Task_Middle 再次從 500ms 的阻塞狀態恢復,通過串口輸出 “Task_Middle is running” ,然後再次進入 500ms 阻塞狀態(這裡 Task_High 由於不是因為延時進入的阻塞狀態所以未恢復運行狀態)
  9. 最後返回 Task_Low 任務,釋放二值信號量,一旦 Task_Low 任務釋放二值信號量,等待二值信號量的高優先順序任務 Task_High 會立馬退出阻塞狀態成功獲取到二值信號量,並會通過串口輸出 “Task_High get token“

從上述過程可知,從 Task_Low 獲取二值信號量之後到第一輪結束,Task_High 等待 Task_Low 釋放二值信號量,等待期間中等優先順序的任務 Task_Middle 卻先於高優先順序任務 Task_High 得到了執行,這就是所謂的優先順序翻轉問題,上述過程所述的實際串口輸出如下圖所示

4.5、互斥量的應用

首先在 STM32CubeMX 中單擊 Middleware and Software Packs/FREERTOS,在 Configuration 中單擊 Mutexes 選項卡,單擊 Add 按鈕增加互斥量 Mutex_PI ,具體如下圖所示

然後將上述實驗使用的所有二值信號量句柄 BinarySem_PIHandle 修改為互斥量 Mutex_PIHandle,不需要做其他任何操作,燒錄程式即可

打開串口助手,觀察串口助手的輸出,如下所述為第一輪詳細的任務執行流程

  1. 前4個步驟與 ”4.4、燒錄驗證“ 小節一致,只不過從二值信號量修改為互斥量
  2. 在第5步時,高優先順序任務 Task_High 從 500ms 的阻塞狀態恢復,輸出 ”Into Task_High“ ,然後嘗試獲取已經被 Task_Low 獲取的互斥量,結果就是進入阻塞狀態等待 Task_Low 釋放互斥量,同時將 Task_Low 的優先順序臨時提高到和高優先順序任務 Task_High 一樣的優先順序
  3. 緊接著 Task_Middle 從 500ms 的阻塞狀態恢復,但是由於現在 Task_Low 任務的優先順序要高於中等優先順序任務 Task_Middle ,因此不能搶占 Task_Low 任務,故無法執行任務體輸出 ”Task_Middle is running“ ,所以其狀態變為就緒狀態,它將等待所有高優先順序的任務執行完後才會執行
  4. 於是優先順序被臨時提高到高優先順序的任務 Task_Low 繼續執行其函數體內容,輸出 ”return Task_Low“ ,然後執行第二個 HAL_Delay(500); ,最後釋放互斥量,通過串口輸出 ”Task_Low give it“
  5. 一旦互斥量被 Task_Low 釋放,處於阻塞狀態的 Task_High 就會立馬恢復運行狀態獲取到互斥量,所以會通過串口輸出 ”Task_High get token“ 和 ”Task_High give token“ ,同時當互斥量被 Task_High 任務成功獲取之後,會將任務 Task_Low 臨時提高的優先順序恢復到其原來的低優先順序,最後 Task_High 調用延時函數進入 500ms 的阻塞狀態
  6. 當高優先順序任務 Task_High 進入阻塞狀態後,系統內現在剩餘就緒狀態的中等優先順序任務 Task_Middle 和 低優先順序任務 Task_Low ,所以輪到 Task_Middle 任務執行,其將通過串口輸出 ”Task_Middle is runing“ ,至此一輪結束

讀者可以自行對比將二值信號量更換為互斥量之後的串口輸出結果,可以發現在步驟4中,中等優先順序的任務 Task_Middle 不再先於高優先順序的任務 Task_High 得到執行,上述整個過程串口數據的完整輸出如下圖所示

5、註釋詳解

註釋1:圖片來源於 Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf

參考資料

STM32Cube高效開發教程(基礎篇)

Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 渲染系統概述 WPF 採用保留模式渲染系統 (Retained Mode Rendering System),該系統可分為 UI 線程和複合線程兩個主要部分,兩者協作完成 WPF 應用程式的渲染工作。 1.1 立即模式GUI和保持模式GUI 圖形 API 可分為保留模式API 和即時模式API ...
  • 在開發各種應用程式時,我們經常需要對文件系統中的文件或文件夾進行實時監測,以便在文件內容改變、文件被創建或刪除時能夠及時做出反應。在 C# 中,System.IO.FileSystemWatcher 類為我們提供了這樣一個強大的功能。 一、引入 FileSystemWatcher 類 首先,在項目中 ...
  • 我個人編寫的庫,在我個人網站,小程式等很多地方都在使用中,大家可以搜索小程式 什邡市宅貓君網路工作室 或者到我的網站 store.zhaimaojun.cn 去體驗支付和登錄效果。 本庫主要實現了native pay(二維碼支付)jsapi pay(小程式直接調起支付),需要註意的是這是基於api3 ...
  • 這是我自己個人編寫的日誌記錄,主要使用在只需要記錄日誌,偶爾到文件中查看一下日誌記錄的情況。我自己寫的一些服務之類的是使用了這個的,代碼很少,使用很簡單。 第一步 搜索和安裝我的Nuget包 搜索和安裝zmjtool這個包,我寫的,如下圖: 第二步 引入namespace和創建logger對象 1 ...
  • 支持.Net/.Net Core/.Net Framework,可以部署在Docker, Windows, Linux, Mac。 RabbitMQ作為一款主流的消息隊列工具早已廣受歡迎。相比於其它的MQ工具,RabbitMQ支持的語言更多、功能更完善。 1.發送消息、獲取消息、使用消息 本文提供一 ...
  • 前言 異常的處理在我們應用程式中是至關重要的,在 dotNet 中有很多異常處理的機制,比如MVC的異常篩選器, 管道中間件定義try catch捕獲異常處理亦或者第三方的解決方案Hellang.Middleware.ProblemDetails等。MVC異常篩選器不太靈活,對管道的部分異常捕獲不到 ...
  • 讓我先把相關的報錯信息通過文字貼到下方,方便被檢索出來 出錯了! (warning!) curl error code=403; 系統錯誤 (explorer.editor.fileGet) explorer/editor.class.php[64] IO::fileSubstr(0,1,2) bi ...
  • 通過視頻幀圖片提取,圖片批量裁剪,轉換為BMP文件並取模,獲得顯示屏代碼,基於STC32單片機,在8x8點陣LED模塊上實現動畫播放。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...