主机字节序和网络字节序
字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序,分为:
- 大端字节序 (Big endian)
- 小端字节序(Little endian)
比如,0x11223344:
这是一个十六进制数,每一位的存储占4个位,也就是每一个字节存储2个数字。
大端字节序 | 小端字节序 | |||
0xa | 11 | 0001 0001 | 44 | 0100 0100 |
0xa+1 | 22 | 0010 0010 | 33 | 0011 0011 |
0xa+2 | 33 | 0011 0011 | 22 | 0010 0010 |
0xa+3 | 44 | 0100 0100 | 11 | 0001 0001 |
一般主机当中使用小端字节序,网络通信当中必须使用大端字节序。
可以通过编程查看主机的字节序。
方法一:
#include <stdio.h>
int main(int argc, const char *argv[])
{
unsigned int val32 = 0x11223344;
unsigned char val8 = *((unsigned char *)(&val32));
if (val8 == 0x44)
printf("本机是小端字节序\n");
else
printf("本机是大端字节序\n");
return 0;
}
运行结果:
$ gcc test.c -Wall
$ ./a.out
本机是小端字节序
- 定义了一个无符号32位整型变量val32,并且初始化为0x11223344
- 定义一个8位的无符号整型变量val8,并且将强转以后的val32的数据赋值给val8
- 这样做的目的是获取变量val32第一个字节的值,后面就可通过判断val8的值是0x44或者0x11来确定主机字节序是大端还是小端
在这里,需要补充下基础知识:
- 数字在内存中是以补码形式存储,如果是正数,原码和补码一样,比如1在32位机器上为0x00000001,负数则是0xffffffff,最高位为符号位。
- 由长度短的unsigned型转长度长的unsigned型,应为总是为正数,只需要直接高位补0即可,比如unsigned char a=0x01转成unsigned int型,就是0x00000001。
- 由长度长的unsigned型转长度短的unsigned型,也因为总是正数,只需要直接保留低位即可,比如unsigned int a=0x00000001转成unsigned char型,就是0x01,所以unsigned int val32=0x11223344转成unsigned char型,如果是大端存储,则是0x11,如果是小端存储则是0x44。
方法二:
#include <stdio.h>
int main(int argc, const char *argv[])
{
union {
short value;
char bytes[sizeof(short)];
} test;
test.value = 0x0102;
if (test.bytes[0] == 1) {
printf("大端字节序。\n");
} else if (test.bytes[0] == 2) {
printf("小端字节序。\n");
} else {
printf("未知。\n");
}
return 0;
}
运行结果:
$ gcc test1.c -Wall
$ ./a.out
小端字节序。
short型是2个字节,所以bytes的长度是2个字节,test.value=0x0102,一个字节存1,另一个字节存2,通过判断1和2存储的位置,就能判断出主机是小端字节序还是大端字节序。
字节序转换函数
数据在主机和网络之间传递时,需要进行字节序的转换。
//头文件
#include <arpa/inet.h>
//字节序转换函数
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
本机转网络 | 网络转主机 | |
32位数据 | htonl | ntohl |
16位数据 | htons | ntohs |
代码实例:
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, const char *argv[])
{
unsigned long s1 = 0x11223344;
unsigned short s2 = 0x1122;
unsigned long s3 = 0x55667788;
unsigned short s4 = 0x5566;
printf("%x\n", htonl(s1));
printf("%x\n", htons(s2));
printf("%x\n", ntohl(s3));
printf("%x\n", ntohs(s4));
return 0;
}
运行结果:
$ gcc test2.c -Wall
$ ./a.out
44332211
2211
88776655
6655
IP地址字节序转换函数
在转换IP地址之前,要考虑到IP地址可能会存在“点分十进制”的字符串形式。
主机字节序一般采用小端字节序,网络字节序转主机字节序以后通常需要转换成“点分十进制”的字符串。
把ip地址转化为用于网络传输的二进制数值
inet_addr函数:
#include <arpa/inet.h>
//IP地址序转换函数,字符串转32位数据
in_addr_t inet_addr(const char *cp);
inet_addr函数的主要功能是将一个ip地址字符串转换成一个整数值。
返回值:
- 如果失败:返回INADDR_NONE,参数char *cp无效;
- 如果成功:返回IP对应的网络字节序的数;
注意,这个函数在处理地址为255.255.255.255时也返回-1,255.255.255.255是一个有效的地址,不过inet_addr无法处理。
inet_aton函数:
#include <arpa/inet.h>
//IP地址序转换函数,字符串转32位数据
int inet_aton(const char *cp, struct in_addr *addr);
功能:将点分十进制IP转化为网络字节序存放在addr中,并返回该网络字节序对应的整数。
返回值:
- 如果失败:返回0;
- 如果成功:返回网络字节序对应的数;
这个转换完后不能用于网络传输,还需要调用htons或htonl函数才能将主机字节顺序转化为网络字节顺序。
inet_pton函数:
#include <arpa/inet.h>
//IP地址序转换函数,字符串转32位数据
int inet_pton(int af, const char *cp, void *addr);
这个函数是随IPv6出现的函数,对于IPv4地址和IPv6地址都适用,地址的表达格式通常是ASCII字符串,数值格式则是存放到套接字地址结构的二进制值。
功能:将点分十进制的ip地址转化为用于网络传输的数值格式。
返回值:
若成功则为1,
若输入不是有效的表达式则为0,
若出错则为-1。
有几点需要注意:
- 这个函数的af参数既可以是AF_INET(ipv4)也可以是AF_INET6(ipv6)。如果,以不被支持的地址族作为af参数,返回一个错误,并将errno置为EAFNOSUPPORT。
- 函数尝试转换由cp指针所指向的字符串,并通过addr指针存放二进制结果,若成功则返回值为1,否则如果所指定的af而言输入字符串不是有效的表达式格式,那么返回值为0.
将网络传输的二进制数值转化为成点分十进制的ip地址
inet_ntoa函数:
#include <arpa/inet.h>
//IP地址序转换函数,32位数据转字符串
char* inet_ntoa(struct in_addr in);
功能:函数转换网络字节排序的地址为标准的ASCII以点分开的地址。该函数返回指向点分开的字符串地址(如192.168.1.10)的指针,该字符串的空间为静态分配的,这意味着在第二次调用该函数时,上一次调用将会被重写(复盖),所以如果需要保存该串最后复制出来自己管理!
返回值:
- 如果成功,返回1;
- 如果失败,返回0。
inet_ntop函数:
#include <arpa/inet.h>
//IP地址序转换函数,32位数据转字符串
const char *inet_ntop(int af, const void *restrict src,
char dst[restrict .size], socklen_t size);
功能:将数值格式转化为点分十进制的ip地址格式。
返回值:
若成功则为指向结构的指针,
若出错则为NULL。
几个注意事项:
- inet_ntop从数值格式转换到表达式。
- inet_ntop函数的src参数不可以是一个空指针。调用者必须为目标存储单元分配内存并指定其大小,调用成功时,这个指针就是该函数的返回值。size参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区。如果size太小,不足以容纳表达式结果,那么返回一个空指针,并置为errno为ENOSPC。
IP地址字节序转换函数部分参考:inet_pton()和inet_ntop()函数详解