從0開始學FreeRTOS-(列表與列表項)-3

来源:https://www.cnblogs.com/iot-dev/archive/2019/10/15/11681021.html
-Advertisement-
Play Games

FreeRTOS列表&列表項的源碼解讀 第一次看列表與列表項的時候,感覺很像是鏈表,雖然我自己的鏈表也不太會,但是就是感覺很像。 在 中,列表與列表項使用得非常多,是 的一個數據結構,學習過數據結構的同學都知道,數據結構能使我們處理數據更加方便快速,能快速找到數據,在 中,這種列表與列表項更是必不可 ...


FreeRTOS列表&列表項的源碼解讀

   

第一次看列表與列表項的時候,感覺很像是鏈表,雖然我自己的鏈表也不太會,但是就是感覺很像。

FreeRTOS中,列表與列表項使用得非常多,是FreeRTOS的一個數據結構,學習過數據結構的同學都知道,數據結構能使我們處理數據更加方便快速,能快速找到數據,在FreeRTOS中,這種列表與列表項更是必不可少的,能讓我們的系統跑起來更加流暢迅速。

言歸正傳,FreeRTOS中使用了大量的列表(List)與列表項(Listitem),在FreeRTOS調度器中,就是用到這些來跟著任務,瞭解任務的狀態,處於掛起、阻塞態、還是就緒態亦或者是運行態。這些信息都會在各自任務的列表中得到。

看任務控制塊(tskTaskControlBlock)中的兩個列表項:

ListItem_t xStateListItem; / * <任務的狀態列表項目引用的列表表示該任務的狀態(就緒,已阻止,暫停)。*/

ListItem_t xEventListItem; / * <用於從事件列表中引用任務。*/

一個是狀態的列表項,一個是事件列表項。他們在創建任務就會被初始化,列表項的初始化是根據實際需要來初始化的,下麵會說。

FreeRTOS列表&列表項的結構體

既然知道列表與列表項的重要性,那麼我們來解讀FreeRTOS中的list.c與list.h的源碼吧。從頭文件lsit.h開始,看到定義了一些結構體:

struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE / * <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES設置為1,則設置為已知值。* /
configLIST_VOLATILE TickType_t xItemValue; / * <正在列出的值。在大多數情況下,這用於按降序對列表進行排序。 * /
struct xLIST_ITEM * configLIST_VOLATILE pxNext; / * <指向列表中下一個ListItem_t的指針。 * /
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; / * <指向列表中前一個ListItem_t的指針。 * /
void * pvOwner; / * <指向包含列表項目的對象(通常是TCB)的指針。因此,包含列表項目的對象與列表項目本身之間存在雙向鏈接。 * /
void * configLIST_VOLATILE pvContainer; / * <指向此列表項目所在列表的指針(如果有)。 * /
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE / * <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES設置為1,則設置為已知值。* /
};
typedef struct xLIST_ITEM ListItem_t; / *由於某種原因,lint希望將其作為兩個單獨的定義。 * /

列表項結構體的一些註意的地方:

 xItemValue 用於列表項的排序,類似1—2—3—4

 pxNext 指向下一個列表項的指針

pxPrevious 指向上(前)一個列表項的指針

這兩個指針實現了類似雙向鏈表的功能

pvOwner 指向包含列表項目的對象(通常是任務控制塊TCB)的指針。因此,包含列表項目的對象與列表項目本身之間存在雙向鏈接。

pvContainer 記錄了該列表項屬於哪個列表,說白點就是這個兒子是誰生的。。。

        

同時定義了一個MINI的列表項的結構體,MINI列表項是刪減版的列表項,因為很多時候不需要完全版的列表項。就不用浪費那麼多記憶體空間了,這或許就是FreeRTOS是輕量級操作系統的原因吧,能省一點是一點。MINI列表項:

struct xMINI_LIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
    configLIST_VOLATILE TickType_t xItemValue;
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

再定義了一個列表的結構體,可能看到這裡,一些同學已經蒙了,列表與列表項是啥關係啊,按照傑傑的理解,是類似父子關係的,一個列表中,包含多個列表項,就像一個父親,生了好多孩子,而列表就是父親,列表項就是孩子。

typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE / * <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES設置為1,則設置為已知值。* /
configLIST_VOLATILE UBaseType_t uxNumberOfItems;
ListItem_t * configLIST_VOLATILE pxIndex; / * <用於遍歷列表。 指向由listGET_OWNER_OF_NEXT_ENTRY()調用返回的後一個列表項。*/
MiniListItem_t xListEnd; / * <List item包含最大可能的項目值,這意味著它始終在列表的末尾,因此用作標記。*/
listSECOND_LIST_INTEGRITY_CHECK_VALUE / * <如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES設置為1,則設置為已知值。* /
} List_t;

列表的結構體中值得註意的是:
uxNumberOfItems  是用來記錄列表中列表項的數量的,就是記錄父親有多少個兒子,當然女兒也行~。

pxIndex 是索引編號,用來遍歷列表的,調用巨集listGET_OWNER_OF_NEXT_ENTRY()之後索引就會指向返回當前列表項的下一個列表項。

xListEnd 指向的是最後一個列表項,並且這個列表項是MiniListItem屬性的,是一個迷你列表項。

列表的初始化

  函數:

void vListInitialise( List_t * const pxList )
{
    pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );           /*lint The mini list structure is used as the list end to save RAM.  This is checked and valid. */
    pxList->xListEnd.xItemValue = portMAX_DELAY;
    pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );   /*lint The mini list structure is used as the list end to save RAM.  This is checked and valid. */
    pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint The mini list structure is used as the list end to save RAM.  This is checked and valid. */
    pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
    listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
    listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}

將列表的索引指向列表中的xListEnd,也就是末尾的列表項(迷你列表項)

列表項的xItemValue數值為portMAX_DELAY,也就是0xffffffffUL,如果在16位處理器中則為0xffff

列表項的pxNext與pxPrevious這兩個指針都指向自己本身xListEnd

初始化完成的時候列表項的數目為0個。因為還沒添加列表項嘛~。

freertos4

列表項的初始化

 函數:

void vListInitialiseItem( ListItem_t * const pxItem )
{
    /* Make sure the list item is not recorded as being on a list. */
    pxItem->pvContainer = NULL;
    /* Write known values into the list item if
    configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
    listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
    listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

只需要讓列表項的pvContainer指針指向NULL即可,這樣子就使得列表項不屬於任何一個列表,因為列表項的初始化是要根據實際的情況來進行初始化的。

例如任務創建時用到的一些列表項初始化:

pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
pxNewTCB->uxPriority = uxPriority;
pxNewTCB->uxBasePriority = uxPriority;
pxNewTCB->uxMutexesHeld = 0;

vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );

又或者是在定時器相關的初始化中:

pxNewTimer->pcTimerName = pcTimerName;
pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;
pxNewTimer->uxAutoReload = uxAutoReload;
pxNewTimer->pvTimerID = pvTimerID;
pxNewTimer->pxCallbackFunction = pxCallbackFunction;

vListInitialiseItem( &( pxNewTimer->xTimerListItem ) );

列表項的末尾插入

  函數:

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    ListItem_t * const pxIndex = pxList->pxIndex;
    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
    listGET_OWNER_OF_NEXT_ENTRY(). */
    pxNewListItem->pxNext = pxIndex;    //  1 
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;    //  2
    /* Only used during decision coverage testing. */
    mtCOVERAGE_TEST_DELAY();
    pxIndex->pxPrevious->pxNext = pxNewListItem;        //  3 
    pxIndex->pxPrevious = pxNewListItem;                //  4
    /* Remember which list the item is in. */
    pxNewListItem->pvContainer = ( void * ) pxList;
    ( pxList->uxNumberOfItems )++;
}

傳入的參數:

pxList:列表項要插入的列表。

pxNewListItem:要插入的列表項是什麼。

從末尾插入,那就要先知道哪裡是頭咯,我們在列表中的成員pxIndex就是用來遍歷列表項的啊,那它指向的地方就是列表項的頭,那麼既然FreeRTOS中的列表很像數據結構中的雙向鏈表,那麼,我們可以把它看成一個環,是首尾相連的,那麼函數中說的末尾,就是列表項頭的前一個,很顯然其結構圖應該是下圖這樣子的(初始化結束後pxIndex指向了xListEnd):

freertos5

為什麼是這樣子的呢,一句句代碼來解釋:

一開始:

ListItem_t * const pxIndex = pxList->pxIndex;

保存了一開始的索引列表項(xListEnd)的指向。

pxNewListItem->pxNext = pxIndex;         //  1

新列表項的下一個指向為索引列表項,也就是綠色的箭頭。

pxNewListItem->pxPrevious = pxIndex->pxPrevious;    //  2

剛開始我們初始化完成的時候pxIndex->pxPrevious的指向為自己xListEnd,那麼xNewListItem->pxPrevious的指向為xListEnd。如2紫色的箭頭。

pxIndex->pxPrevious->pxNext = pxNewListItem;        //  3

索引列表項(xListEnd)的上一個列表項還是自己,那麼自己的下一個列表項指向就是指向了pxNewListItem

pxIndex->pxPrevious = pxNewListItem;            //  4

這句就很容易理解啦。如圖的4橙色的箭頭。

插入完畢的時候標記一下新的列表項插入了哪個列表,並且將uxNumberOfItems進行加一,以表示多了一個列表項。

為什麼源碼要這樣子寫呢?因為這隻是兩個列表項,一個列表含有多個列表項,那麼這段代碼的通用性就很強了。無論原本列表中有多少個列表項,也無論pxIndex指向哪個列表項!

freertos6

freertos7

看看是不是按照源碼中那樣插入呢?

列表項的插入

 源碼:

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
     if( xValueOfInsertion == portMAX_DELAY )
   {
        pxIterator = pxList->xListEnd.pxPrevious;
    }
    else
    {
        for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
        {
            /* There is nothing to do here, just iterating to the wanted
            insertion position. */
        }
    }
    pxNewListItem->pxNext = pxIterator->pxNext;
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
    pxNewListItem->pxPrevious = pxIterator;
    pxIterator->pxNext = pxNewListItem;
    /* Remember which list the item is in.  This allows fast removal of the
    item later. */
    pxNewListItem->pvContainer = ( void * ) pxList;
    ( pxList->uxNumberOfItems )++;
}

傳入的參數:

pxList:列表項要插入的列表。
pxNewListItem:要插入的列表項是什麼。

pxList決定了插入哪個列表,pxNewListItem中的xItemValue值決定了列表項插入列表的位置。

ListItem_t *pxIterator;  
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;

定義一個輔助的列表項pxIterator,用來迭代找出插入新列表項的位置,並且保存獲取要插入的列表項pxNewListItem的xItemValue。

如果打開了列表項完整性檢查,就要用戶實現configASSERT(),源碼中有說明。

既然是要插入列表項,那麼肯定是要知道列表項的位置了,如果新插入列表項的xItemValue是最大的話(portMAX_DELAY),就直接插入列表項的末尾。否則就需要比較列表中各個列表項的xItemValue的大小來進行排列。然後得出新列表項插入的位置。

for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext )

上面源碼就是實現比較的過程。

與上面的從列表項末尾插入的源碼一樣,FreeRTOS的代碼通用性很強,邏輯思維也很強。

如果列表中列表項的數量為0,那麼插入的列表項就是在初始化列表項的後面。如下圖所示:

freertos8

過程分析:

新列表項的pxNext指向pxIterator->pxNext,也就是指向了xListEnd(pxIterator)

pxNewListItem->pxNext = pxIterator->pxNext;

而xListEnd(pxIterator)的pxPrevious指向則為pxNewListItem。

pxNewListItem->pxNext->pxPrevious = pxNewListItem;

新列表項的(pxPrevious)指針指向xListEnd(pxIterator)

pxIterator 的 pxNext 指向了列表項

pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;

與從末尾插入列表項其實是一樣的,前提是當前列表中列表項的數目為0。

假如列表項中已經有了元素呢,過程又是不一樣的了。原來的列表是下圖這樣子的:

freertos9

假設插入的列表項的xItemValue2,而原有的列表項的xItemValue值是3,那麼,按照源碼,我們插入的列表項是在中間了。而pxIterator則是①號列表項。

插入後的效果:

freertos10

分析一下插入的過程:

新的列表項的pxNext指向的是pxIterator->pxNext,也就是③號列表項。因為一開始pxIterator->pxNext=指向的就是③號列表項!!

pxNewListItem->pxNext = pxIterator->pxNext;

而pxNewListItem->pxNext 即③號列表項的指向上一個列表項指針(pxPrevious)的則指向新插入的列表項,也就是②號列表項了。

pxNewListItem->pxNext->pxPrevious = pxNewListItem;

新插入列表項的指向上一個列表項的指針pxNewListItem->pxPrevious指向了輔助列表項pxIterator。很顯然要連接起來嘛!

pxNewListItem->pxPrevious = pxIterator;     

同理,pxIterator列表項的指向下一個列表項的指針則指向新插入的列表項了pxNewListItem

pxIterator->pxNext = pxNewListItem;

而其他沒改變指向的地方不需改動。(圖中的兩條直線做的連接線是不需要改動的)

當插入完成的時候,記錄一下新插入的列表項屬於哪個列表。並且讓該列表下的列表項數目加一。

pxNewListItem->pvContainer = ( void * ) pxList;
         ( pxList->uxNumberOfItems )++;

刪除列表項

    源碼:

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* The list item knows which list it is in.  Obtain the list from the list
item. */
List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
    /* Only used during decision coverage testing. */
    mtCOVERAGE_TEST_DELAY();
    /* Make sure the index is left pointing to a valid item. */
    if( pxList->pxIndex == pxItemToRemove )
    {
        pxList->pxIndex = pxItemToRemove->pxPrevious;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
    pxItemToRemove->pvContainer = NULL;
    ( pxList->uxNumberOfItems )--;
    return pxList->uxNumberOfItems;
}

  其實刪除是很簡單的,不用想都知道,要刪除列表項,那肯定要知道該列表項是屬於哪個列表吧,pvContainer就是記錄列表項是屬於哪個列表的。

  刪除就是把列表中的列表項從列表中去掉,其本質其實就是把他們的連接關係刪除掉,然後讓刪除的列表項的前後兩個列表連接起來就行了,假如是只有一個列表項,那麼刪除之後,列表就回到了初始化的狀態了。

pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

這兩句代碼就實現了將刪除列表項的前後兩個列表項連接起來。

按照上面的講解可以理解這兩句簡單的代碼啦。

假如刪除的列表項是當前索引的列表項,那麼在刪除之後,列表中的pxIndex就要指向刪除列表項的上一個列表項了。

if( pxList->pxIndex == pxItemToRemove )  
{
     pxList->pxIndex = pxItemToRemove->pxPrevious;
}

當然還要把當前刪除的列表項的pvContainer指向NULL,讓它不屬於任何一個列表,因為,刪除的本質是刪除的僅僅是列表項的連接關係,其記憶體是沒有釋放掉的,假如是動態記憶體分配的話。

並且要把當前列表中列表項的數目返回一下。

至此,列表的源碼基本講解完畢。

最後

大家還可以瞭解一下遍歷列表的巨集,它在list.h文件中:

define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                                        \
{                                                                                            \
List_t * const pxConstList = ( pxList );                                                    \
    /* Increment the index to the next item and return the item, ensuring */                \
    /* we don't return the marker used at the end of the list.  */                          \
    ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                            \
    if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )  \
    {                                                                                       \
         ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                        \
    }                                                                                       \
    ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                                          \
}

這是一個巨集,用於列表的遍歷,返回的是列表中列表項的pxOwner成員,每次調用這個巨集(函數)的時候,其pxIndex索引會指向當前返回列表項的下一個列表項。

歡迎關註我公眾號

更多資料歡迎關註“物聯網IoT開發”公眾號!


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

-Advertisement-
Play Games
更多相關文章
  • 對稱加密演算法 非對稱加密演算法 單向散列(hash演算法) CA和證書 證書獲取 安全協議 OpenSSL openssl命令 創建CA和申請證書 ...
  • awk介紹 awk的工作原理 awk基本格式 awk之print格式 awk變數 awk之printf awk操作符 awk PATTERN awk action awk控制語句 awk控制語句if-else awk控制語句 while迴圈 awk控制語句do-while迴圈 awk控制語句 for ...
  • 準備 在移植之前,我們首先要獲取到FreeRTOS的官方的源碼包。這裡我們提供兩個下載鏈接: 一個是官網:http://www.freertos.org/ 另外一個是代碼托管網站:https://sourceforge.net/projects/freertos/files/FreeRTOS/ 這裡 ...
  • 寫在前面:傑傑這個月很忙~所以並沒有時間更新,現在健身房閉館裝修,晚上有空就更新一下!其實在公眾號沒更新的這段日子,每天都有兄弟在來關註我的公眾號,這讓我受寵若驚,在這裡謝謝大家的支持啦!!謝謝^ 在這裡我們就跟著火哥的書來學習一下FreeRTOS的消息隊列,這本書我覺得寫得很好,基本都講解到了,關 ...
  • 寫在前面 主要是為剛接觸 FreeRTOS 的用戶指出那些新手通常容易遇到的問題。這裡把最主要的篇幅放在棧溢出以及棧溢出j檢測上,因為棧相關的問題是初學者遇到最多的問題。 printf stdarg.c 當調用 C 標準庫 的函數時,棧空間使用量可能會急劇上升,特別是 IO 與字元串處理函數,比如 ...
  • 沒研究過操作系統的源碼都不算學過操作系統 FreeRTOS 時間管理 時間管理包括兩個方面:系統節拍以及任務延時管理。 系統節拍: 在前面的文章也講得很多,想要系統正常運行,那麼時鐘節拍是必不可少的, 的時鐘節拍通常由 提供,它周期性的產生定時中斷,所謂的時鐘節拍管理的核心就是這個定時中斷的服務程式 ...
  • FreeRTOS列表&列表項的源碼解讀 第一次看列表與列表項的時候,感覺很像是鏈表,雖然我自己的鏈表也不太會,但是就是感覺很像。 在FreeRTOS中,列表與列表項使用得非常多,是FreeRTOS的一個數據結構,學習過數據結構的同學都知道,數據結構能使我們處理數據更加方便快速,能快速找到數據,在Fr ...
  • 問題解答 曾經有人問我,FreeRTOS那麼多API,到底怎麼記住呢? 我想說,其實API不難記,就是有點難找,因為FreeRTOS的API很多都是帶參巨集,所以跳來跳去的比較麻煩,而且註釋也很多,要找還真不是那麼容易,不過也沒啥,一般都會有API手冊的,我就告訴大家一下: FreeRTOS Kern ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...