接触到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))