linux: 讨论一下网络字节序--------大端与小端的差别
数据存储优先顺序的转换
计算机数据存储有两种字节优先顺序:高位字节优先(称为大端模式)和低位字节优先(称为小端模式)。内存的低地址存储数据的低字节,高地址存储数据的高字节的方式叫小端模式。内存的高地址存储数据的低字节,低地址存储数据高字节的方式称为大端模式。
eg:对于内存中存放的数0x12 34 56 78来说(注意,对于数据而言,此处12是高字节,78是低字节;对于地址而言,左边是低地址,右边是高地址)
如果是采用大端模式存放的,则其真实的数是:0x12 34 56 78 (此处注意,12是一个字节 (一个字节是8个bit位,无符号是表达0~255,有符号是表达-127~128))
如果是采用小端模式存放的,则其真实的数是:0x78 56 34 12
综上:小端模式,先存低字节;大端模式先存高字节。
如果称某个系统所采用的字节序为主机字节序,则它可能是小端模式的,也可能是大端模式的。通常我们的电脑存放数据,都是以小端模式存放。而端口号和IP地址都是以网络字节序存储的,不是主机字节序,网络字节序都是大端模式。要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。
Pra1
原题
实现将字符串形式存放的ip地址(点分十进制数)转换为主机字节序的32位二进制数值。IP为:“180.97.33.107”。十六进制为b4.61.21.6b
思路
一般我们的主机字节序均为小端字节序,也就是选存放数据的低字节。
1. 将字符串中的每个数取出来
2. 分别左移(注意,左移时一定会补0)
180 : 00 00 00 b4 左移0 位 00 00 00 b4
97 : 00 00 00 61 左移8 位 00 00 61 00
33 : 00 00 00 21 左移16位 00 21 00 00
107 : 00 00 00 6b 左移24位 6b 00 00 00
3. 或操作
代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #define IP "180.97.33.107" /* 将ip地址转换为主机字节序的二进制数值,输出用16进制表示 */ static int my_atoh(char *ip) { int arr[4]; /* 用于存放从ip地址中扣出的4个整数 */ int my_ip; sscanf(IP,"%d.%d.%d.%d",arr,arr+1,arr+2,arr+3); my_ip = (arr[3] << 24) | (arr[2] << 16) | (arr[1] << 8) | arr[0]; return my_ip; } int main(int argc, char *argv[]) { int my_host = my_atoh(IP); printf("ip : %s \n",IP); printf("host: %x \n",my_host); return 0; }
运行结果如下:
[purple@localhost 0827]$ gcc -o main my_atoh.c -Wall [purple@localhost 0827]$ ./main ip : 180.97.33.107 host: 6b2161b4 [purple@localhost 0827]$
注意
提一个与本题无关的注意点,当所有scanf函数,格式化读入时,如果遇到%s,会自己加‘\0’。
Pra2
原题
实现将主机字节序(小端模式存放)转换为网络字节序(大端模式存放)。即主机字节序为6b 21 61 b4 –> b4 61 21 6b (本文中都是按字节为单位讨论的)
思路1
将6b与b4交换,21与61交换。
代码
#include <stdio.h> int my_hton(int ip) { /* &ip指向用32个bit表示的整型数ip,将int*类型的指针转换为char*类型的指针, * 则ptr指向用8个bit表示(第0-7位)的整型数, * ptr+1指向用8个bit表示(第8-15位)的整型数, * ptr+2指向用8个bit表示(第16-23位)的整型数, * ptr+3指向用8个bit表示(第24-31位)的整型数。*/
char *ptr = (char*)&ip; char tmp; tmp = ptr[0]; ptr[0] = ptr[3]; ptr[3] = tmp; tmp = ptr[1]; ptr[1] = ptr[2]; ptr[2] = tmp; return ip; } int main(int argc, char *argv[]) { int my_host = 0x6b2161b4; int my_net = my_hton(my_host); printf("my_host: %x \n",my_host); printf("my_net : %x \n",my_net); return 0; }
运行结果如下:
[purple@localhost 0827]$ gcc -o main my_hton.c -Wall [purple@localhost 0827]$ ./main my_host: 6b2161b4 my_net : b461216b
注意
实际上,ip地址为点分十进制,每个字节的表示范围都是0-255。而我们的char类型通常默认为有符号数,也就是说其表示范围是-128-127。那么我在代码中标记为黄色的部分是不是出错了呢?实际上并没有错,因为在此处我们只是关心ip每一位的存储情况,并不要求用到具体每个字节的实际十进制表示数值。在用printf格式化输出时,是根据数据在内存中的二进制形式来格式化的,而16进制形式是没有负数的。
上句的意思就是,不管有符号还是无符号, 在该字节在内存中的保存方式都是一样的,只是我们的解释不用而已,它是ip时我们解释为无符号数(例如通常ip第一位都是192.xxx.xx.xx)当做ip解释就是192
当做char解释就是-64 其实它们在内存中的形式是一样的,只是我们的解释不同,这一点十分容易迷惑我们。
思路2
1. 对于0x6b2161b4,先右移并进行与操作,取出各字节
例如:要取出6b,则0x6b2161b4右移24位,之后与0xff进行与操作即可
注意:千万不能先进行与操作,再右移。如,要取出6b,先使0x6b2161b4与0xff000000进行与操作,之后右移24位。这是错误的,因为右移会补符号位。(tips:左移补0,并舍弃符号位)
2. 分别再左移,调整到适当位置。 (以下两步类似于Pra1)
3. 或操作
代码
#include <stdio.h> int my_hton2(int ip) { int my_net; my_net = ((ip&0xff)<<24) | (((ip>>8)&0xff) << 16) | (((ip>>16)&0xff) << 8) | ((ip>>24)&0xff); return my_net; } int main(int argc, char *argv[]) { int my_host = 0x6b2161b4; int my_net = my_hton2(my_host); printf("my_host: %x \n", my_host); printf("my_net : %x \n", my_net); return 0; }
Pra3
原题
实现将网络字节序转换为点分十进制
代码
#include <stdio.h> static char* my_ntoa(int ip) { static char buf[1024] = ""; sprintf(buf,"%d.%d.%d.%d",(ip >> 24) & 0xff,(ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); return buf; } int main(int argc,char *argv[]) { int my_net = 0xb461216b; printf("my_net: %x \n", my_net); printf("IP : %s \n", my_ntoa(my_net)); return 0; }