引言:最近在網上看了很多博客,想要深入瞭解大小端問題,主要是做畢設時,RTP包協議的結構體定義有兩種方式,即大端和小端。但是一些博客並沒有講到理解大小端的本質問題,在這裏按自己的理解擴充一下,可能有錯,望理解!!!
1. 字節序
字節序即字節的存儲順序,如果數據都是單字節的,那怎麼存儲無所謂了,但是對於多字節數據,比如int,double等,就要考慮存儲的順序了。字節序是硬件層面的東西,通常只和你使用的處理器架構有關,而和編程語言無關。字節序分爲大端序和小端序。
大端序:數據的高位字節存放在地址的低端 低位字節存放在地址高端。
小端序:數據的高位字節存放在地址的高端 低位字節存放在地址低端。
2. 比特序
字節序是一個對象中的多個字節之間的順序問題,比特序就是一個字節中的8個比特位(bit)之間的順序問題。一般情況下系統的比特序和字節序是保持一致的。以二進制數值10110101爲例,其在不同平臺下的內存位序如下:
大端的含義是數值的最高位1(最左邊的1)放在了內存起始位置上,即數值10110101的大端內存佈局爲10110101。
小端的含義是數值的最低位1(最右邊的1)放在了內存起始位置上,即數值10110101的小端內存佈局爲10101101。
3. 位域
對於位域有一個約定:在C語言的結構體中如果包含了位域,如果位域A定義在位域B之前,那麼位域A總是出現在低地址的比特位。 這就決定了網絡編程中位域在定義時必須處理大小端問題。(同樣,結構體中前面的成員也處於較低的地址)
對於IP頭,可以這樣理解,網絡傳輸時version在前,ihl在後,網絡是大端序,可以認爲version是數字的高位,ihl是低位,所以:
在大端機中,由於低地址是高位,所以位域version必須在前面。
在小端機中,由於高地址是高位,所以位域version必須在後面。
4. RTP頭大小端處理
如下圖這是網絡大端序要接收數據的格式,RTP協議就是這樣規定的,網絡大端序必須按照這樣的順序發送數據,這樣PC機客戶端(支持RTP協議)接收到數據後,也會按照這樣的格式解析。
(1)如果你的服務器是大端系統,所以代碼中不需要轉換,按照正常格式定義變量,在內存中也是按照大端模式存儲。
struct _RTP_FIXED_HEADER
{
unsigned char version:2; /**//* expect 2 */
unsigned char padding:1; /**//* expect 0 */
unsigned char extension:1; /**//* expect 1, see RTP_OP below */
unsigned char csrc_len:4; /**//* expect 0 */
unsigned char marker:1; /**//* expect 1 */
unsigned char payload:7; /**//* RTP_PAYLOAD_RTSP */
unsigned short seq_no;
unsigned long timestamp;
unsigned long ssrc; /**//* stream number is used here. */
} __PACKED__;
(2)如果服務器是小端系統,由第二部分比特序可知,我們服務器的代碼最後是在服務器上運行,所以定義的結構體變量會以小端方式存放在內存中,也就是我們需要將結構體的比特位倒序,然後存放在小端系統時,裏面存放的值的順序纔是大端系統需要的,因爲數據的發送與大小端無關,總是從低地址開始發送,直到數據總長度接收。所以我們在存儲的時候就調整好位置,如下代碼所示:
struct _RTP_FIXED_HEADER
{
/**//* byte 0 */
unsigned char csrc_len:4; /**//* expect 0 */
unsigned char extension:1; /**//* expect 1, see RTP_OP below */
unsigned char padding:1; /**//* expect 0 */
unsigned char version:2; /**//* expect 2 */
/**//* byte 1 */
unsigned char payload:7; /**//* RTP_PAYLOAD_RTSP */
unsigned char marker:1; /**//* expect 1 */
/**//* bytes 2, 3 */
unsigned short seq_no;
/**//* bytes 4-7 */
unsigned long timestamp;
/**//* bytes 8-11 */
unsigned long ssrc; /**//* stream number is used here. */
} __PACKED__;
(3)上述代碼中只對比特序進行了調整,這些比特位的組合爲一個字節,爲單字節序,是不需要轉換的,比如我們發送的視頻流數據不需要轉換,因爲他是單字節流。而short與long是多字節序,所以在發送之前需要使用hton(主機轉網絡字節序)函數對多字節進行轉換,代碼如下:
ssrc = htonl(10);
seq_no = htons(g_rtspClients[is].seqnum++);
總結:內存中如果有一個存好的數,同樣一個數,他在大端系統和小端系統的代碼中的值卻不一樣,如果代碼中有定義一個數,他在大端小端系統內存中存儲的順序卻不一樣。
5、多字節序
如下圖:前6個變量爲char,他們以字節爲單位,各自位成員按大小端比較權重。而short爲2字節,該變量本身以2字節爲單位,內部以一字節按大小端比較權重,這纔有了0x1234與0x3412。long爲4字節,4字節爲單位,一字節比較權重,這纔有了0x12345678與0x78563412。
(1)如果在大小端系統中傳遞數據,所以有了轉換大小端的說法,也出現了像htonl()、htons()這樣的函數去轉換大小端字節序的說法。host to network long/short,字節序的轉換需要我們做。