本地字节序和网络字节序
字节序指不同的CPU访问内存中多字节数据的时候,存在大小端的问题,并且一定是访问多字节数据的时候才会存在大小端的问题,如果CPU访问的是字符串,则不存在大小端的问题;
那么如何判断发送端和接收端是大端模式还是小端模式呢,首先:
●小端序(little-endian) - 低序字节存储在低地址;将低字节存储在起始地址,称为“Little-Endian”字节序,Intel、AMD等采用的是这种方式;
●大端序(big-endian)- 高序字节存储在低地址;将高字节存储在起始地址,称为“Big-Endian”字节序,由ARM、Motorola
例如有一个int型的变量:int i = 0x12345678;这里的“12”就是变量的高端,“78”就是变量的低端;
其实本地和网络字节序的转换,主要是为了不同平台之间兼容性的问题,例如两台主机要进行网络数据的传输,有两种情况(首先我们要知道,网络中传输的数据必须按网络字节序,即大端字节序;因为网络传输过程中的中间设备的处理器都是大端模式的,这是规定好的):
1.发送端是大端模式,接收端是小端模式;如果发送端不进行字节序的转换,那么这个应用程序运行到发送端是小端模式的主机上之后,网络传输就会有问题,比如说网络传输过程中的中间设备不能正确的读取收发端的IP地址和MAC地址;
2. 发送端是小端模式,接收端是大端模式;如果接收端不进行字节序的转换,而且发送端还传输了多字节的数据的话,那么这个应用程序运行到接收端是小端模式的主机上之后,接收端接收到的数据就会有问题;
因为两台设备进行网络传输,这两台设备可能不是一个平台上的CPU,不同平台上的两个CPU,它们可能两个都是大端模式的,也有可能都是小端模式的,还有可能一个是大端模式一个是小端模式或者一个是小端模式一个是大端模式,所以两台机器要进行网络传输,必须统一发送、传输和接受的模式,由此课件数据转换的主要目的就是为了保证兼容行;
当然,字节序转换函数里面已经包含了这种方法,函数本身会直接的判断当前操作系统中的CPU的大端模式还是小端模式,需要转换的话就转换,不需要转换的时候就原样输出;
下面是本地字节序到网络字节序转换的函数:
主机字节序到网络字节序
#includ
u_long htonl (u_long hostlong); /这里的"h"指的是:host,"n"指的是network,"l"指的是long型/
u_short htons (u_short short); /这里的"h"和"n"和上面是一样的,这里的"s"指的是short型/
网络字节序到主机字节序
u_long ntohl (u_long hostlong);
u_short ntohs (u_short short);
htonl\htons和ntohl\ntohs这两种类型的函数都是将多字节的数据进行颠倒操作,下面是测试:
int i = 0x00414243;
int j,k,l;
j = htonl(i);
k = htonl(j);
l = ntohl(j);
printf("i:%s\n",(char *) &i);
printf("j:%s\n",(char *) &j);
printf("k:%s\n",(char *) &k);
printf("l:%s\n",(char *) &l);
输出结果是:
linux@ubuntu:~/Embedded_learning/LV6/day2$ ./a.out
i:CBA
j: /这里输出了空,原因是htonl()函数将0x00414243颠倒为0x43424100,因为是小端模式,打印的时候第一k:CBA 个读到的数字是零,而零在ASCII中表示的是’\0’,’\0’代表的是字符串的结束符,所以就输出了空;/
l:CBA
linux@ubuntu:~/Embedded_learning/LV6/day2$
从这个测试看到好像htonl()和ntohl()函数的功能没有上面区别,但是我认为是有区别的:
1.htonl()函数因为是本地字节序到网络字节序,里面应该实现了判断当前使用函数的机器是否为大端模式,如果为大端模式的话就放弃转换,直接返回要转换的多字节数据,否则就将本地字节序转换为网络字节序;
2.ntohl()函数是网络字节序到本地字节序,里面应实现了判断当前使用函数的机器是否为大端模式,如果为大端模式的话就放弃转换,并直接返回要转换的多字节数据,否则就将网络字节序转换为本地字节序;
好像又是一样的啊;求解释这两个函数内部实现的区别;
IP地址的转换函数
#include
#include
#include
in_addr_t inet_addr(const char cp); /cp是点分形式的IP地址,返回值是由点分形式的IP地址转换过来的32位的
IP地址,这里要注意的是,这里面已经包含了本地字节序的转换了,而且这个
函数内部还会自动的判断,如果本地字节序是大端模式的话就直接返回,不进
行转换;如果是小端模式的话才会进行转换,并返回转换后的32位数值;所
以,不用特意的去调用htonl()等函数;/
inet_addr()函数将Internet主机地址cp从IPv4数字和点符号转换成网络字节顺序的二进制数据。如果输入无效,则返回INADDR_NONE(通常是-1)。使用这个函数是有问题的,因为-1是一个有效地址(255.255.255.255)。避免使用它来支持inet_aton()、inet_pton(3)或getaddrinfo(3),它们提供了一种更简洁的方式来指示错误返回。
使用这个函数时有两点需要注意:
1.inet_addr()这个函数只适用于IPV4;
2.inet_addr()这个函数出错时返回-1;但是由于-1在32位的计算机中的补码形式也是四个255(-1在32位系统中的补码为:ffff ffff)所以在使用的时候要特别的注意不能使用255.255.255.255这个IP地址;具体见下面的测试:
if(argc != 2){
perror(“Missing argc”);
exit(-1);
}
unsigned int i;
i = inet_addr((argv+1));
if(i == -1){
perror(“inet_addr failed”);
exit(-1);
}
printf("%s\n",*(argv+1));
printf("%u\n",i);
执行结果:
linux@ubuntu:~/Embedded_learning/LV6/day2$ ./a.out 255.255.255.255
inet_addr failed: Success /可以看出,这样的执行结果会出现误判的情况/
linux@ubuntu:~/Embedded_learning/LV6/day2$
不适用255.255.255.255地址就是正确的:
linux@ubuntu:~/Embedded_learning/LV6/day2$ ./a.out 255.255.255.254
255.255.255.254
4278190079
linux@ubuntu:~/Embedded_learning/LV6/day2$
由于inet_addr()函数的弊端,所以就引进了下面的两个函数:inet_pton()和inet_ntop()
#include
int inet_pton(in af,const char *src,void *dst); /convert IPv4 and IPv6 addresses from text to binary form/
inet_pton()这个函数相比inet_addr()函数的优点有:
1.它能转换IPv4和IPv6两种文本地址到二进制;
2.能正确的处理255.255.255.255的转换问题;
如果成功,inet_pton()将返回1(网络地址已成功转换)。如果src不包含表示指定地址族中的有效网络地址的字符串,则返回0。如果af不包含有效的地址家族,则返回-1并将errno设置为EAFNOSUPPORT。
参数:
af :af参数必须是AF_INET或AF_INET6;
AF_INET : src指向一个字符串,该字符串包含一个点十进制格式的IPv4网络地址,“ddd.ddd.ddd”。ddd”,其中ddd是一个范围为0到255的三位数的小数。地址被转换为struct in_addr并复制到dst,它必须是sizeof(struct in_addr)(4)字节(32位)长。
/*Internet address. */
typedef uint32_t in_addr_t;
struct in_addr{
In_addr_t s_addr;
};
AF_INET6 : src指向一个包含IPv6网络地址的字符串。地址被转换为struct in6_addr并复制到dst,它必须是sizeof(struct in6_addr)(16)字节(128位)长。IPv6地址的允许格式遵循以下规则:
/* IPv6 address */
struct in6_addr
{
union
{
uint8_t __u6_addr8[16];
#if defined __USE_MISC || defined __USE_GNU
uint16_t __u6_addr16[8];
uint32_t __u6_addr32[4];
#endif
} __in6_u;
#define s6_addr __in6_u.__u6_addr8
#if defined __USE_MISC || defined __USE_GNU
#define s6_addr16 __in6_u.__u6_addr16
#define s6_addr32 __in6_u.__u6_addr32
#endif
};
const char *inet_ntop(int af,const void src,char dst,socklen_t size); / convert IPv4 and IPv6 addresses from binary to
text form/
此函数将af地址家族中的网络地址结构src转换为字符串。结果字符串被复制到dst指向的缓冲区中,该缓冲区必须是非空指针。调用者在参数大小中指定此缓冲区中可用的字节数。
inet_ntop()扩展了inet_ntoa(3)函数以支持多个地址族,而inet_ntoa支支持IPv4地址;
AF_INET: src指向一个struct in_addr(按网络字节顺序),它被转换成点状小数格式的IPv4网络地址“ddd.ddd.ddd.ddd”。缓冲区dst必须至少有INET_ADDRSTRLEN字节长(#define INET_ADDRSTRLEN 16)。
AF_INET6: src指向一个struct in6_addr(按网络字节顺序),它被转换成这个地址最合适的IPv6网络地址格式的表示。缓冲区dst必须至少有INET6_ADDRSTRLEN字节长(#define INET6_ADDRSTRLEN 46)。
int inet_aton(const char * cp,struct in_addr * inp); /老师说先不管这个函数/
char *inet_ntoa(struct in_addr in); /老师说先不管这个函数/
#include
#include
#include
可能是因为这两个函数只支持IPv4的格式;