字节序与网络编程基础
ARM Cortex-M 开发板和 x86 服务器通信,数据发过去读出来全是乱的——不是代码写错了,是字节序没转换。
什么是字节序
一个多字节的数(比如 32 位整数 0x12345678),在内存里怎么存?高位字节放前面还是后面?不同架构的答案不一样。
**大端序(Big-Endian):**高位字节存低地址,“大头在前”。
**小端序(Little-Endian):**低位字节存低地址,“小头在前”。
以 0x12345678 为例,存在地址 0x1000 开始的 4 个字节:
| 地址 | 大端序 | 小端序 |
|---|---|---|
| 0x1000 | 0x12(最高字节) | 0x78(最低字节) |
| 0x1001 | 0x34 | 0x56 |
| 0x1002 | 0x56 | 0x34 |
| 0x1003 | 0x78(最低字节) | 0x12(最高字节) |
常见架构的字节序:
- x86 / x86-64(PC、服务器):小端
- ARM Cortex-M / Cortex-A(嵌入式、手机):默认小端,可配置
- MIPS(部分路由器):默认大端
- PowerPC:大端
代码检测当前机器字节序:
#include <stdio.h>
int main(void) {
uint32_t x = 0x12345678;
uint8_t *p = (uint8_t *)&x;
if (p[0] == 0x78) {
printf("小端序\n");
} else {
printf("大端序\n");
}
return 0;
}网络字节序
TCP/IP 协议规定:网络字节序统一使用大端序。
为什么要统一?不同架构的机器字节序不同,如果不统一,x86(小端)发出去的数据,MIPS(大端)读出来就是乱的。统一成大端,所有机器发送前转换,接收后转换,就能互通。
**哪些字段受影响:**IP 首部、TCP/UDP 首部中所有多字节字段都是网络字节序,包括:
- IP 地址(4 字节)
- 端口号(2 字节)
- 序列号、确认号(4 字节)
- 数据长度等
转换函数(重点)
POSIX 标准提供了一组转换函数,<arpa/inet.h> 或 <netinet/in.h> 中:
| 函数 | 含义 | 用途 |
|---|---|---|
htons(x) | Host to Network Short(16位) | 端口号发送前转换 |
htonl(x) | Host to Network Long(32位) | IP地址、序列号发送前转换 |
ntohs(x) | Network to Host Short(16位) | 端口号接收后转换 |
ntohl(x) | Network to Host Long(32位) | IP地址、序列号接收后转换 |
记忆口诀:发出去用 hton,收进来用 ntoh。
在大端机器上,这些函数什么都不做(直接返回原值),因为本机字节序就是网络字节序。在小端机器上,它们会翻转字节顺序。
IP 地址字符串转换:
#include <arpa/inet.h>
// 字符串 IP → 网络字节序(推荐用 inet_pton,支持 IPv6)
struct in_addr addr;
inet_pton(AF_INET, "192.168.1.1", &addr);
// 网络字节序 → 字符串 IP
char buf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr, buf, sizeof(buf));典型用法:
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(8080); // 端口号转网络字节序
inet_pton(AF_INET, "192.168.1.1", &server.sin_addr); // IP 地址转网络字节序跨平台处理实践
直接 memcpy 结构体的坑:
// 错误做法:直接把结构体发出去
struct Packet {
uint32_t id;
uint16_t length;
uint8_t data[64];
};
struct Packet pkt = {.id = 1, .length = 64};
send(sock, &pkt, sizeof(pkt), 0); // id 和 length 没有转换字节序!对端如果是不同字节序的机器,读出来的 id 和 length 就是错的。
正确做法:逐字段转换
// 正确做法:发送前转换
struct Packet pkt;
pkt.id = htonl(1); // 32位字段用 htonl
pkt.length = htons(64); // 16位字段用 htons
memcpy(pkt.data, src, 64);
send(sock, &pkt, sizeof(pkt), 0);
// 接收后转换
recv(sock, &pkt, sizeof(pkt), 0);
uint32_t id = ntohl(pkt.id);
uint16_t length = ntohs(pkt.length);htons 的实现原理:
// 在小端机器上,htons 就是把两个字节交换
uint16_t htons(uint16_t x) {
return (x >> 8) | (x << 8);
}
// 在大端机器上,直接返回原值笔试题直击
Q:什么是大端序和小端序?网络字节序是哪种?
大端序:高位字节存低地址;小端序:低位字节存低地址。x86/ARM 默认小端,TCP/IP 规定网络字节序为大端。
Q:htons 的实现原理
在小端机器上,把 16 位整数的高低字节交换:(x >> 8) | (x << 8)。在大端机器上直接返回原值。
Q:跨平台通信时如何处理字节序问题?
发送前用 htonl/htons 把多字节字段转成网络字节序,接收后用 ntohl/ntohs 转回主机字节序。不要直接 memcpy 整个结构体发送,要逐字段转换。
如果你正在跟随梳理, 返回 MOC←