字节序
字节序就是数据在计算机内存或者网络中的字节存储顺序。
大多数计算机(x86架构)都采用小端字节序(little-endian),即将低位字节存储在内存的低地址处,高位字节存储在内存的高地址处。
而网络通信使用的则是大端字节序(big-endian),即将高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。
1.主机字节序
主机内部,内存中数据的处理方式,可以分为两种:
- 大端字节序( big-endian):按照内存的增长方向,高位数据存储于低位内存中(最直观的字节序 )
- 小端字节序(little-endian):按照内存的增长方向,低位数据存储于低位内存中
理解心得
这里“大”和“小”可以理解为数据的高位(big)和低位(little)。
大端和小端就是在内存中的低地址处存放的是数据的高位还是低位。
比如0x12345678 这个数的高位是0x12, 低位是0x78。
如果我们要将0x12345678这个十六进制数放入内存中:
例如,内存从左往右增长,在小端字节序下,整数值0x12345678被表示为以下四个连续的8位二进制数:
0x78 0x56 0x34 0x12
而在大端字节序下,则应该按照以下顺序进行排列:
0x12 0x34 0x56 0x78
2.网络字节序
网络字节序(Network Byte Order)是指在网络通信中使用的一种数据传输顺序。由于不同计算机的处理器架构和操作系统的不同,它们对于多字节整数类型(如int、long等)所采用的字节序也可能会有所不同,因此在进行网络通信时需要统一规定一个标准的字节序。
网络数据流的地址规定: 先发出的数据是低地址,后发出的数据是高地址。
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,为了不使数据流乱序,接收主机也会把从网络上接收的数据按内存地址从低到高的顺序保存在接收缓冲区中。
TCP/IP协议规定:网络数据流应采用大端字节序,即低地址高字节。
因此,在进行网络通信时,需要将数据转换成网络字节序,才能保证不同机器之间正确地解析和传递数据。常用函数如htons、htonl、ntohs和ntohl等都可以用来进行大小端之间的转换。
3.字节序转换
windows和Linux通用函数
//htons函数 发 将主机字节序的端口 转换成 网络字节序的端口
uint16_t htons(uint16_t host16bitvalue); //返回网络字节序的值
//htonl函数 发 将主机字节序的IP地址 转换成网络字节序的IP地址
uint32_t htonl(uint32_t host32bitvalue); //返回网络字节序的值
//ntohs函数 收 将网络字节序的端口 转换成 主机字节序的端口
uint16_t ntohs(uint16_t net16bitvalue); //返回主机字节序的值
//ntohl函数 收 将网络字节序的IP地址
uint32_t ntohl(uint32_t net32bitvalue); //返回主机字节序的值
h代表host,n代表network,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取
linux下
#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);
或者Linux下专用函数
#include <endian.h>
uint16_t htobe16(uint16_t host_16bits);
uint16_t htole16(uint16_t host_16bits);
uint16_t be16toh(uint16_t big_endian_16bits);
uint16_t le16toh(uint16_t little_endian_16bits);
uint32_t htobe32(uint32_t host_32bits);
uint32_t htole32(uint32_t host_32bits);
uint32_t be32toh(uint32_t big_endian_32bits);
uint32_t le32toh(uint32_t little_endian_32bits);
uint64_t htobe64(uint64_t host_64bits);
uint64_t htole64(uint64_t host_64bits);
uint64_t be64toh(uint64_t big_endian_64bits);
uint64_t le64toh(uint64_t little_endian_64bits);
获取大小端示例
#include <iostream>
bool big_endian() {
int x = 0x12345678;
char *p0 = reinterpret_cast<char *>(&x);
return *p0 == 0x12;
}
bool little_endian() {
int x = 0x12345678;
char *p0 = reinterpret_cast<char *>(&x);
return *p0 == 0x78;
}
union Endian {
int value;
char arr[4];
};
bool union_big_endian() {
Endian en;
en.value = 0x12345678;
return en.arr[0] == 0x12;
}
bool union_little_endian() {
Endian en;
en.value = 0x12345678;
return en.arr[0] == 0x78;
}
int main() {
if (big_endian()) {
std::cout << "big endian" << std::endl;
} else {
std::cout << "little endian" << std::endl;
}
if (!little_endian()) {
std::cout << "big endian" << std::endl;
} else {
std::cout << "little endian" << std::endl;
}
if (union_big_endian()) {
std::cout << "big endian" << std::endl;
} else {
std::cout << "little endian" << std::endl;
}
if (!union_little_endian()) {
std::cout << "big endian" << std::endl;
} else {
std::cout << "little endian" << std::endl;
}
return 0;
}