网络字节序和本地字节序的理解和实现

工作中经常用到这两个概念,看了APUE关于大端模式和小端模式的说明,和博文:htonl、ntohl、htons、ntohs函数实现 在这里概括归纳一下,权当备忘。以下所有假设都是在32位x86系统上。
大端模式:网络字节序采用的模式,TCP/IP协议栈支持的模式
小端模式:linux主机采用小端模式存储。

这里要注意不管是大端还是小端存储,MSB(数据最高字节)始终是在最左边,LSB(数据最低字节)始终是在最右边
比如以十六进制的0x12345678为例,首先要了解十六进制一位表示4个二进制位,所以这个代表一个32位(8*4),4字节的数据,0x12是MSB,0x78是LSB
对于大端模式MSB存储在低位字节地址n,LSB存储在高位字节地址n+3,这里的字节地址也是32位的,数据按照地址偏移按单字节存放。
32整数内部的字节序

所以要将一个字符指针char* cp强制转换为这个整形地址,由于字节序的不同会带来差异。在小端机器上cp[0]指向最低位地址,存放的是0x78,然而cp[3]指向最高位地址,存放的是0x12;大端反之。
可以用gdb调试查看,内存内部的变量分布,gdb内存查看命令:
x /nfu addr
说明
x 是 examine 的缩写
n表示要显示的内存单元的个数
f表示显示方式, 可取如下值
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
i 指令地址格式
c 按字符格式显示变量。
f 按浮点数格式显示变量。
u表示一个地址单元的长度
b表示单字节,
h表示双字节,
w表示四字节,
g表示八字节
参考博文:gdb查看内存区命令

小端模式内存查看实验:

int main(int arc, char* arg[])
{
    unsingned int a = 0x12345678;
    while(1);
}

编译:
编译必须加-g选项才能生成可调试的文件
gcc littleEnding.c -g -o littleEnding

调试:
(gdb)gdb ./littleEnding
(gdb)b 4 //源文件第四行设置断点
(gdb)r //执行程序
(gdb)p &a //打印变量地址
$1 = (unsingned int *)0xbffff543
(gdb)x 0xbffff543 //查看内存单元内变量
0xbffff543: 0x12345678
(gdb) x /4xb 0xbffff543 //单字节查看4个内存单元变量的值
0xbffff543: 0x78 0x56 0x34 0x12
(gdb) x /1xb 0xbffff543
0xbffff543: 0x78
(gdb) x /2xb 0xbffff543
0xbffff543: 0x56
(gdb) x /3xb 0xbffff543
0xbffff543: 0x34
(gdb) x /4xb 0xbffff543
0xbffff543: 0x78 0x56 0x34 0x12
//可以看到内存最低地址单元存放的是数据的LSB 0x78
//显然linux x86下是小端的存放方式
typedef unsigned short int uint16;

typedef unsigned long int uint32;



// 短整型大小端互换

#define BigLittleSwap16(A)  ((((uint16)(A) & 0xff00) >> 8) | \

                            (((uint16)(A) & 0x00ff) << 8))

 // 长整型大小端互换



#define BigLittleSwap32(A)  ((((uint32)(A) & 0xff000000) >> 24) | \

                            (((uint32)(A) & 0x00ff0000) >> 8) | \

                            (((uint32)(A) & 0x0000ff00) << 8) | \

                            (((uint32)(A) & 0x000000ff) << 24))




 // 本机大端返回1,小端返回0

int checkCPUendian()

{

       union{

              unsigned long int i;

              unsigned char s[4];

       }c;



       c.i = 0x12345678;
//利用了联合的内存分配规则,共享内存仅分配一种数据结构
       return (0x12 == c.s[0]);

}



// 模拟htonl函数,本机字节序转网络字节序

unsigned long int t_htonl(unsigned long int h)

{

       // 若本机为大端,与网络字节序同,直接返回

       // 若本机为小端,转换成大端再返回

       return checkCPUendian() ? h : BigLittleSwap32(h);

}



// 模拟ntohl函数,网络字节序转本机字节序

unsigned long int t_ntohl(unsigned long int n)

{

       // 若本机为大端,与网络字节序同,直接返回

       // 若本机为小端,网络数据转换成小端再返回

       return checkCPUendian() ? n : BigLittleSwap32(n);

}



// 模拟htons函数,本机字节序转网络字节序

unsigned short int t_htons(unsigned short int h)

{

       // 若本机为大端,与网络字节序同,直接返回

       // 若本机为小端,转换成大端再返回

       return checkCPUendian() ? h : BigLittleSwap16(h);

}



// 模拟ntohs函数,网络字节序转本机字节序

unsigned short int t_ntohs(unsigned short int n)

{

       // 若本机为大端,与网络字节序同,直接返回

       // 若本机为小端,网络数据转换成小端再返回

       return checkCPUendian() ? n : BigLittleSwap16(n);

}
Logo

更多推荐