一、字节序的概念
字节序是指不同类型CPU主机,内存存储 多字节整数 序列的方式。
float, char, 字符串均没有字节序的说法
short, int , long , long long 有字节序的说法
小端字节序:低字节存储在低地址,高字节存储在高地址
大端字节序:低字节存储在高地址,高字节存储在低地址。
ps :
1. 取地址,获取的都是变量的首地址,即获取的是变量的最低的地址。
2. 内存中的数据读取,都是从低地址往高地址读取,读取完毕后经过大小端转换得到结果。
用共用体(联合体)的方式,判断本机的字节序?
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
union
{
unsigned int a;
char b;
}s;
int main(int argc, const char *argv[])
{
/*
unsigned int a = 0x87654321;
int* pa = &a;
char b = 0;
b = a;
printf("%#x\n",*pa);
printf("%#x\n",b);
*/
/*
unsigned int a =0x87654321;
char *pa = (char*)&a;
printf("%#x\n",*pa);
if(0x21 == *pa)
printf("这是一个小端字节序\n");
else if(0x87 == *pa)
printf("这是一个大端字节序\n");
*/
s.a=0x87654321;
printf("%#x\n",s.b);
if(0x21 == s.b)
printf("这是一个小端字节序\n");
else if(0x87 == s.b)
printf("这是一个大端字节序\n");
return 0;
}
#include <stdio.h>
union t
{
int a;
char b;
};
int main(int argc, const char *argv[])
{
unsigned int a = 0x87654321;
char *pa = (char*)&a;
if(0x21 == *pa)
printf("这是一个小端\n");
else if(0x87 == *pa)
printf("这是一个大端\n");
union t text;
text.a = 1;
if(1 == text.b)
printf("这是一个小端\n");
else if(0 == text.b)
printf("这是一个大端\n");
return 0;
}
二、本地字节序与网络字节序
本地字节数:主机字节序(Host Byte Order) HBO
网络字节序(Network Byte Order) NBO,网络字节序规定使用大端字节序。
在跨主机传输过程中,需要使用统一的字节序,即网络字节序,避免兼容性问题。
三、字节序转换函数
1)htons htonl 主机字节序-->网络字节序
头文件:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
参数:指定要转换成网络字节序的整型:分别是32bit和16bit;
返回值:成功,返回转换后网络字节序的整型
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, const char *argv[])
{
unsigned int a = 0x87654321;
printf("%#x\n", a); //0x87654321
printf("%#x\n", htonl(a)); //0x21436587
printf("%#x\n", htons(a)); //0x2143
return 0;
}
2)ntohs ntohl 网络字节序---->主机字节序
头文件:
#include <arpa/inet.h>
原型:
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
参数:
uint32_t hostlong:32位网络字节序整型;
uint16_t hostshort:16位网络字节序整型;
返回值:成功,返回转换成主机字节序的整型;
四、结构体对齐
编译器会对结构体进行对齐,加速CPU取值周期,由于数据对齐也是与操作系统相关,不同的主机如果使用不同的对齐方式,会导致数据无法解析。
所以网络传输结构体的时候需要取消结构体对齐;
#include <stdio.h>
#pragma pack(1) //设置默认对齐系数 :()中的参数只能填2^n (n=0,1,2,3,4,5......)
typedef struct
{
char a; //1
int b; //4
int d; //4
}_A;
#pragma pack() //重置默认对其系数,重新置为8
typedef struct
{
char a; //1
int b; //4
int d; //4
} __attribute__((packed)) B; //取消结构体对齐
typedef struct
{
char a; //1
//3
int b; //4
int d; //4
}_C;
int main(int argc, const char *argv[])
{
printf("%ld\n", sizeof(_A)); //9
printf("%ld\n", sizeof(_B)); //9
printf("%ld\n", sizeof(_C)); //12
return 0;
}
五、类型长度
int long int不同操作系统这两个数据类型所占的字节数可能是不一样的
解决方式:可以通过通用类型:uint8_t uint16_t uint32_t
因为涉及到跨平台,不同平台会有不同的字长
#include <stdint.h>
typedef struct
{
uint8_t a; //1
uint32_t b; //4
uint16_t d; //2
}_A;
六、IP转换
由于IP地址本质上是一个4个字节的无符号整数,所以在跨主机传输中也有字节序的概念。
所以需要将IP地址转换成网络字节序。
"192.168.8.189" ---->本机字节序的整型 0xC0A808BD---->网络字节序0xBD08A8C0
"192.168.31.42"----> 0xC0A81F2A ---->0x2A1FA8C0
6.1点分十进制--->网络字节序
1)inet_aton
头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
原型:
int inet_aton(const char *cp, struct in_addr *inp);
参数:
char *cp:源IP地址的点分十进制字符串,例如 “192.168.1.10”;
struct in_addr *inp:存储转换成网络字节序的IP;
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
返回值:
成功,返回非0;
失败,返回0;
只能转换IPv4
例子:
#define IP "192.168.1.10" //0xC0A8010A
int main(int argc, const char *argv[])
{
struct in_addr inp;
if(inet_aton(IP, &inp) == 0)
{
printf("转换失败\n");
return -1;
}
printf("%#X\n", inp.s_addr); //0X0A01A8C0
return 0;
}
2)inet_pton(既可以转IPv4也能处理IPv6)
头文件:
#include <arpa/inet.h>
原型:
int inet_pton(int af, const char *src, void *dst);
参数:
int af:协议族
AF_INET IPV4
AF_INET6 IPV6
char *src:指定要转换成网络字节序的点分十进制字符串;
void* dst
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
af == AF_INETa;
struct in6_addr
{
}
返回值:
成功,返回1;
失败,返回0或者-1,更新errno;
2)inet_addr 最常用
头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
原型:
uint32_t inet_addr(const char *cp);
参数:
char *cp:源IP地址的点分十进制字符串,例如 “192.168.1.10”;
返回值:
成功,返回转换后的网络字节序IP地址;
typedef uint32_t in_addr_t;
失败,返回INADDR_NONE (usually -1);
6.2网络字节序--->点分十进制
1)inet_ntoa 常用
头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
原型:
char *inet_ntoa(struct in_addr in);
参数:
struct in_addr in:指定要转换成点分十进制字符串的IP地址;
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
返回值:
成功,返回点分十进制字符串的首地址;
2)inet_ntop
头文件:
#include <arpa/inet.h>
原型:
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
参数:
int af:协议族
AF_INET IPV4
AF_INET6 IPV6
void* src:存储要转换成点分十进制字符串的IP首地址;
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
af == AF_INETa;
struct in6_addr
{
}
char *dst:存储转换后的结果,点分十进制的首地址;
socklen_t size:缓冲区大小,其实就是指定多大的空间用于转换IP;
返回值:
成功,返回字符串的首地址,就是dst;
失败,返回NULL,更新errno;