接觸到socket編程,就一定會接觸字節序轉換。
對於字符串來說,是沒有字節序的差別的,就像我們寫字,內存就像是紙,字符串就從左向右依次寫:
內存地址:00000000 00000001 00000002 00000003 ...
內存數據: 'A' 'B' 'C' 'D'
而任何cpu讀取的時候,也都是從左向右依次讀取。
對於多字節數據(比如short、int、long...),不同字節序是有差別的。
所有x86架構cpu(包括x64)都是用的小端字節序,網絡字節序是大端序:
int a = 0x01020304;
內存地址:00000000 00000001 00000002 00000003 ...
大端序a : 0x01 0x02 0x03 0x04 即cpu讀出的數據從高向低位寫入變量
小端序a : 0x04 0x03 0x02 0x01 即cpu讀出的數據從低向高位寫入變量
如果我們寫的程序不進行字節序轉換會怎麼樣呢:
int a = 0x01020304;
在我們x86架構機器的內存裡,它是這樣寫的0x04030201(注意,這是它在內存裡的真實寫法,當我們的程序以int型讀取的時候才會轉化成0x01020304),此時進行網絡發送,send程序會以字節的方式(可以把它想象成字符串),一字節一字節的發送,到了網絡上,它的順序仍然是0x04030201(此時它的順序已經錯了)。當數據到達對端機器的時候,對端機器也是一字節一字節的收入內存,它的順序仍然是0x04030201。如果對端仍是x86架構,那麼數據讀出來的時候它又被轉化成了0x01020304,這看起來像是對的。但是如果對端是大端序的架構,那麼它的int型數據就變成了0x04030201。
字節序轉換:
linux裡提供了現成的函數:htonl, ntohl ... 這是一系列函數,卻只提供了2字節和4字節的轉換。
判斷字節序:
int is_big_endian(void) { int test = 0x12345678; char *p = (char *)&p; if(*p = 0x12) return 1; else return 0; }
函數雖然簡陋,但是已經夠用了。
字節序轉換宏,順便將數字轉換成數組:
#define rhton16(h, n) {(n)[0]=((unsigned short)(h))>>8; (n)[1]=((unsigned short)(h))<<8>>8;} #define rhton32(h, n) {(n)[0]=((unsigned int)(h))>>24; (n)[1]=((unsigned int)(h))<<8>>24; (n)[2]=((unsigned int)(h))<<16>>24; (n)[3]=((unsigned int)(h))<<24>>24;} #define rhton64(h, n) {(n)[0]=((unsigned long)(h))>>56; (n)[1]=((unsigned long)(h))<<8>>56; (n)[2]=((unsigned long)(h))<<16>>56; (n)[3]=((unsigned long)(h))<<24>>56; (n)[4]=((unsigned long)(h))<<32>>56; (n)[5]=((unsigned long)(h))<<40>>56; (n)[6]=((unsigned long)(h))<<48>>56; (n)[7]=((unsigned long)(h))<<56>>56;}
數組轉換成數字:
#define rntoh16(n) (((unsigned short)((n)[1])) | ((unsigned short)((n)[0])<<8)) #define rntoh32(n) (((unsigned int)((n)[3])) | ((unsigned int)((n)[2])<<8) | ((unsigned int)((n)[1])<<16) | ((unsigned int)((n)[0])<<24)) #define rntoh64(n) (((unsigned long)((n)[7])) | ((unsigned long)((n)[6])<<8) | ((unsigned long)((n)[5])<<16) | ((unsigned long)((n)[4])<<24) | ((unsigned long)((n)[3])<<32) | ((unsigned long)((n)[2])<<40) | ((unsigned long)((n)[1])<<48) | ((unsigned long)((n)[0])<<56))