【linux网络编程】字节序
在套接字(Socket)通信中,字节序(Endianness)是指多字节数据在内存或网络传输中的存储顺序。主要有两种字节序:
-
大端字节序(Big-Endian):最高有效字节(MSB,Most Significant Byte)存储在低地址,最低有效字节(LSB,Least Significant Byte)存储在高地址。例如:
int num = 0x12345678;
在大端模式下,内存存储顺序为:
0x12 0x34 0x56 0x78
-
小端字节序(Little-Endian):最低有效字节(LSB)存储在低地址,最高有效字节(MSB)存储在高地址。例如:
0x78 0x56 0x34 0x12
套接字通信中的字节序
网络通信通常涉及不同架构的计算机,而不同架构可能使用不同的字节序。为了保证数据的可移植性,网络协议统一采用大端字节序(即网络字节序,Network Byte Order)。这意味着,在发送数据时,需要将数据转换为大端格式;在接收数据时,需要从大端格式转换回主机字节序。
字节序转换函数
在C/C++的Socket编程中,提供了一组字节序转换函数,用于在主机字节序和网络字节序之间进行转换:
-
主机字节序 → 网络字节序
htons()
:转换16位无符号短整数(short)htonl()
:转换32位无符号整数(long)
-
网络字节序 → 主机字节序
ntohs()
:转换16位无符号短整数(short)ntohl()
:转换32位无符号整数(long)
示例代码
#include <stdio.h>
#include <stdint.h>
#include <arpa/inet.h>
int main() {
uint16_t host_short = 0x1234;
uint32_t host_long = 0x12345678;
uint16_t net_short = htons(host_short);
uint32_t net_long = htonl(host_long);
printf("Host short: 0x%x, Network short: 0x%x\n", host_short, net_short);
printf("Host long: 0x%x, Network long: 0x%x\n", host_long, net_long);
return 0;
}
运行结果(假设主机是小端字节序):
Host short: 0x1234, Network short: 0x3412
Host long: 0x12345678, Network long: 0x78563412
这样,在网络传输时,所有数据都被转换为大端格式,不同架构的计算机都可以正确解析数据。
无论是客户端还是服务器,发送数据时都应该转换为大端(网络字节序)后再发送,接收数据时再转换回主机字节序。这样可以确保不同架构的计算机都能正确解析数据。
发送数据时:
- 先检查主机的字节序(小端或大端)。
- 使用
htonl()
(32位)或htons()
(16位)将数据转换为 大端字节序。 - 通过
send()
或sendto()
发送数据。
接收数据时:
- 通过
recv()
或recvfrom()
接收数据。 - 使用
ntohl()
(32位)或ntohs()
(16位)将数据从 大端字节序转换回主机字节序。 - 解析和使用数据。
代码示例:
服务器端(接收整数并转换回主机字节序)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 8080
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
uint32_t net_data, host_data;
// 创建 socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == 0) {
perror("Socket failed");
exit(EXIT_FAILURE);
}
// 配置服务器地址
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定 socket
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, 3) < 0) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("Waiting for connection...\n");
new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
if (new_socket < 0) {
perror("Accept failed");
exit(EXIT_FAILURE);
}
// 接收数据(网络字节序)
recv(new_socket, &net_data, sizeof(net_data), 0);
// 转换为主机字节序
host_data = ntohl(net_data);
printf("Received data (Network Byte Order): 0x%x\n", net_data);
printf("Converted to Host Byte Order: %u\n", host_data);
close(new_socket);
close(server_fd);
return 0;
}
客户端(发送整数,转换为大端字节序)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 8080
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
uint32_t host_data = 12345; // 主机字节序
uint32_t net_data;
// 创建 socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("Socket creation error");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// 连接服务器
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
perror("Invalid address/ Address not supported");
return -1;
}
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Connection failed");
return -1;
}
// 转换为网络字节序
net_data = htonl(host_data);
// 发送数据
send(sock, &net_data, sizeof(net_data), 0);
printf("Sent data (Host Byte Order): %u\n", host_data);
printf("Converted to Network Byte Order: 0x%x\n", net_data);
close(sock);
return 0;
}
运行流程:
-
服务器端运行,等待连接:
Waiting for connection...
-
客户端连接服务器并发送整数
12345
(转换为大端字节序0x00003039
):Sent data (Host Byte Order): 12345 Converted to Network Byte Order: 0x3039
-
服务器端接收到数据并转换回主机字节序:
Received data (Network Byte Order): 0x3039 Converted to Host Byte Order: 12345
总结:
- 所有数据在发送前都要转换为网络字节序(大端),无论是客户端还是服务器。
- 接收数据后,需要转换回主机字节序,保证程序能够正确解析数据。
- 使用
htonl()
、htons()
进行发送转换,ntohl()
、ntohs()
进行接收转换,确保跨平台兼容性。