1. 程式人生 > 實用技巧 >UE4 appBitsCpy函式作用詳解

UE4 appBitsCpy函式作用詳解

函式作用概括:

  對遊戲過程中收到的普通位元組流(遊戲互動的資料包位元組流),在函式CurComponent.Incoming(ProcessedPacketReader);的時候讀了這一串位元組流的第一個位元組的最低位位元,用來判斷是否是bHandshakePacket。這一個位元位被讀取後,自然要丟棄這一位元位,重新組裝這一串位元組流,然後將這一串位元組流轉交給其他函式處理。   因此對於遊戲過程中的普通位元組流(會讀取第一個位元組的最低位位元),函式appBitsCpy的作用是重新組裝被讀取過一個位元的位元組流並將新組裝的位元組流複製到新的空間中。   重新組裝位元組流的規則是(針對第一個位元組最低位位元被讀取的位元組流):丟棄第一個位元組的最後一位,然後將下一個元素的最後一位移到第一個元素的第一位。接下來迴圈將下一個元素的最後一位移到前一個元素的最高位。
  而在這個過程中分成了三個步驟進行:   第一個步驟:Lead-in:捨棄源空間被讀取的第一個位元組的最後一位位元,將第二個位元組的最後一位移到第一個位元組的第一位。最後將新組裝的第一個位元組複製到新的空間Dest內。   第二個步驟:Inner-loop:迴圈執行上述的操作。將源空間內的當前元素的下一個元素的最後一個位元位移到當前元素的最高位,並將當前元素複製到新的空間Dest內。   第三個步驟:Lead-out:處理源空間最後一個元素的複製。   這三個步驟都會將源空間的每個元素按位元位佔位的形式複製到一個變數BitAccu上,從BitAccu中取最低8位元複製到新的空間內。

抓包詳細分析流程和程式碼:

呼叫傳入的引數:

appBitsCpy( uint8* Dest, int32 DestBit, uint8* Src, int32 SrcBit, int32 BitCount );

appBitsCpy(目的空間指標,從目的位置0開始, 源空間指標,源位元偏移, 源資料佔據位元數 );

抓包得到源資料位元流為: 0 1 2 3 4 5 6 7 8 9 10000000 01000000 10011011 01000111 11111110 11111111 11111111 11111111 01111111 00100011 128、64、155、71、254、255、255、255、127、35 最終重新組裝的位元組流: 01000000 10100000 11001101 00100011 11111111 11111111 11111111 11111111 10111111 00010001
64、160、205、35、255、255、255、255、191、17 傳入引數SrcBit=1,說明第一個位元組的最後一個位元位被讀取了用來判斷是否是bHandshakePacket,BitCount=77。也就是說下標為9的元素只有5個有效位元位。

幾個需要用到的變數解析:

//因為DestBit總是0,因此DestIndex總是從0開始;
uint32 DestIndex = DestBit/8;

//由名字可知只對源資料第一個元素作用的數字
//FirstSrcMask總是拿來和其他數字做與運算(&)
uint32 FirstSrcMask  = 0xFF << ( DestBit & 7);  

//如果不能被8整除,那麼LastDest是最後一個有效元素的下標;
//如果能被8整除,那麼LastDest是最後一個有效元素下標的下一個
uint32 LastDest = ( DestBit+BitCount )/8; 

//BitCount&7如果不等於0:說明有向上取整,值是佔據N個位元組多BitCount&7位
//例如:BitCount=9,那麼BitCount&7=1,說明佔據1個位元組多1位
//多1位就將0xFF左移1位:1111 1110
//由名字可知:只對源資料的最後一個元素做Mask
uint32 LastSrcMask   = 0xFF << ((DestBit + BitCount) & 7);

//SrcBit是源資料當前的偏移位置,偏移後在源資料陣列的哪個下標上
uint32 SrcIndex = SrcBit/8;

//源資料最後一個元素的下標;
//如果不能被8整除,那麼LastSrc是最後一個有效元素的下標;
//如果能被8整除,那麼LastSrc是最後一個有效元素下標的下一個;
uint32 LastSrc    = ( SrcBit+BitCount )/8;  

//如果SrcBit等於0,ShiftCount=0
//如果SrcBit等於1,ShiftCount=-1
//ShiftCount為負數用來指示第一個位元組幾位被讀取過,最後ShiftCount+=8就是代表還剩多少位有效
int32   ShiftCount = (DestBit & 7) - (SrcBit & 7); 

//遍歷所有目的資料的陣列需要迴圈的次數
int32   DestLoop = LastDest-DestIndex;

//遍歷所有源資料的陣列需要迴圈的次數 
int32   SrcLoop = LastSrc -SrcIndex;  
uint32 FullLoop;

//BitAccu是對源空間內元素位元位的平鋪,假如源空間內有兩個元素:100(01100100) 、90(01011010),
//那麼BitAccu的可能位元位為:01100100 01011010,值為: 25690
//實際上BitAccu會將兩個元素逆序,這裡只是作BitAccu可能形式的例子;
uint32 BitAccu;
在這組抓包的例子中這些值分別等於: DestIndex=0, FirstSrcMask=255, LastDest=9, LastSrcMask=8160=00011111 11100000(因為最後一個元素只有5位有效,所以左移了5位) SrcIndex=0, LastSrc=9, ShiftCount=-1, DestLoop=9, SrcLoop=9

進行第一個步驟:Lead-in

  捨棄被讀取的第一個位元組的最後一位位元,將第二個位元組的最後一位移到第一個位元組的第一位。最後將第一個位元組複製到新的空間Dest內。

  將資料複製到Dest都是通過先構造BitAccu變數,然後將低8位複製到新空間內。

進行第二個步驟:Inner-loop

  迴圈執行上述的操作。將源空間內的當前元素的下一個元素的最後一個位元位移到當前元素的最高位,並將當前元素複製到新的空間Dest內。

  第一次迴圈,是將源資料的第三個元素右移15位放到了BitAccu=00000000 00000000 00100000 01000000上;然後將BitAccu右移8位丟棄,將源資料的第二個元素(橙色)複製到Dest空間上。   同理到了最後一次迴圈,是將源資料的第10個元素(即最後一個),右移BitAccu8位丟棄,將第9個元素複製到Dest空間上。   因此最後一次迴圈出來以後,BitAccu包含了最後一個元素和倒數第二個元素,此時BitAccu=00010001 10111111,其中源空間最後一個元素(即BitAccu的高8位)還未被複制到Dest空間上。

進行第三個步驟:Lead-Out

  最後一次迴圈出來以後,需要先判斷是不是源空間內最後一個元素是不是已經在BitAccu上了。判斷的辦法如程式碼所示:(SrcBit+BitCount-1)/8==SrcIndex   如果不等於SrcIndex,將BitAccu右移8位,捨棄BitAccu已經拷貝到Dest新空間的低8位,最後將剩餘的8位拷到Dest新空間內。此時源空間所有的元素都經過重新組裝並拷貝到了新空間Dest內。appBitsCpy函式執行完畢。   如果等於(SrcBit+BitCount-1)/8==SrcIndex,說明源空間最後一個元素還沒有在BitAccu上,因此再做一次BitAccu = ( ( (uint32)Src[SrcIndex] << ShiftCount ) + (BitAccu)) >> 8;最後再將源空間最後一個元素拷貝到Dest新空間內。

後續:

  appBitsCpy在多個地方都有用到,後續會繼續分析它在其他地方的用途。