跨主机传输
字节序
字节序(Endianness)是指多字节数据在内存中的存储顺序, 字节序是处理器架构特性,决定了多字节数据类型内部字节的排列顺序。
主要分为大端序和小端序两种:
- 大端序(Big Endian):将数据的高位字节存储在内存的低地址处,低位字节存储在高地址处,符合人类的读写习惯。
- 小端序(Little Endian):将数据的低位字节存储在内存的低地址处,高位字节存储在高地址处。
常见平台字节序:
操作系统 | 处理器架构 | 字节序 |
---|---|---|
FreeBSD 8.0 | Intel Pentium | 小端 |
Linux 3.2.0 | Intel Core i5 | 小端 |
Mac OS X 10.6.8 | Intel Core 2 Duo | 小端 |
Solaris 10 | Sun SPARC | 大端 |
Android 10 | ARM Cortex-A53 | 小端 |
主机字节序host:
主机字节序是指计算机系统或处理器架构内部使用的字节排列方式,可能是大端序(Big-endian)或小端序(Little-endian)。它由主机硬件决定,影响数据在内存中的存储顺序和处理方式。由于不同主机使用的字节序可能不同,在进行网络通信时,通常需要将主机字节序转换为网络字节序以保证数据的一致性。
网络字节序network:
网络字节序是一种标准化的字节排列方式,统一采用大端序(Big-endian),即数据的高位字节存储在低地址处。这种字节序被广泛应用于网络协议(如TCP/IP),目的是确保在不同架构的计算机系统之间进行数据交换时,传输的数据格式保持一致,从而避免字节序差异引发的通信错误。
字节序转换函数
#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);
作用
这一组函数用于在主机字节序和网络字节序之间进行转换。在网络编程中,由于不同计算机系统可能使用不同的字节序来存储数据(如小端模式或大端模式),因此为了确保不同系统之间的兼容性,需要进行字节序转换。
参数与返回值
函数名 | 参数类型 | 参数说明 | 返回值类型 | 返回值说明 |
---|---|---|---|---|
htonl | uint32_t | 主机字节序的32位整数 | uint32_t | 转换后的网络字节序32位整数 |
htons | uint16_t | 主机字节序的16位整数 | uint16_t | 转换后的网络字节序16位整数 |
ntohl | uint32_t | 网络字节序的32位整数 | uint32_t | 转换后的主机字节序32位整数 |
ntohs | uint16_t | 网络字节序的16位整数 | uint16_t | 转换后的主机字节序16位整数 |
详细说明
htonl
:将32位无符号整数从主机字节序转换为网络字节序。htons
:将16位无符号整数从主机字节序转换为网络字节序。ntohl
:将32位无符号整数从网络字节序转换为主机字节序。ntohs
:将16位无符号整数从网络字节序转换为主机字节序。
数据对齐
内存对齐(Memory Alignment)是指数据在内存中的存储位置要满足特定的地址要求,通常是为了提高CPU访问内存的效率。
规则:
- 结构体第一个成员偏移量为0
- 其他成员需要对齐到自身大小的整数倍地址
- 整个结构体大小是最大成员大小的整数倍 示例:
struct Example { char a; // 1字节 double b; // 4字节 char c; // 1字节};
上述Example结构体内存布局如下
偏移量: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23内容: a 填充(7字节) b(8字节) c 填充(7字节)
sizeof(struct Example)为24
在不同的体系架构上数据对齐方式不同,结构体数据对齐会带来严重的兼容性问题。因此一般禁用数据对齐来进行网络传输。
类型长度
不同硬件体系架构的系统在数据类型的字节大小表示上存在显著差异,这给网络数据传输带来了一系列兼容性问题。
例如int类型:在16位系统中为16位,32位系统中为32位,64位系统中仍可能为32位或64位
解决方案:使用标准固定大小类型
#include <stdint.h>
// 使用固定大小的整数类型int8_t // 8位有符号整数uint8_t // 8位无符号整数int16_t // 16位有符号整数uint16_t // 16位无符号整数int32_t // 32位有符号整数uint32_t // 32位无符号整数int64_t // 64位有符号整数uint64_t // 64位无符号整数
socket
socket
int socket(int domain, int type, int protocol);
作用
socket
函数用于创建一个用于通信的端点,并返回一个引用该端点的文件描述符。成功调用后返回的文件描述符将是当前进程中未打开的最低编号的文件描述符。
参数
int domain
:指定通信域,选择用于通信的协议族。这些族在<sys/socket.h>
中定义。Linux 内核目前支持的协议族包括:
名称 | 用途 |
---|---|
AF_UNIX/AF_LOCAL | 本地通信 |
AF_INET | IPv4 Internet 协议 |
AF_AX25 | 业余无线电 AX.25 协议 |
AF_INET6 | IPv6 Internet 协议 |
AF_KEY | 密钥管理协议,最初为 IPsec 开发 |
AF_NETLINK | 内核用户接口设备 |
AF_PACKET | 低级数据包接口网卡驱动层 |
AF_RDS | 可靠数据报套接字 (RDS) 协议 |
AF_PPPOX | 通用 PPP 传输层,用于设置 L2 隧道 |
AF_CAN | 控制器局域网汽车总线协议 |
AF_BLUETOOTH | 蓝牙低级套接字协议 |
int type
:指定套接字的类型,定义通信语义。当前定义的类型包括:
类型 | 说明 |
---|---|
SOCK_STREAM | 提供有序、可靠、双向、基于连接的字节流。可能支持带外数据传输机制。 |
SOCK_DGRAM | 支持数据报(无连接、不可靠的固定最大长度消息)。 |
SOCK_SEQPACKET | 提供有序、可靠、双向、基于连接的数据传输路径,适用于固定最大长度的数据报。 |
SOCK_RAW | 提供对原始网络协议的访问。 |
SOCK_RDM | 提供可靠的不保证排序的数据报层。 |
type
参数还有第二个用途:除了指定套接字类型之外,它还可以包含以下值的按位或,以修改 socket()
的行为:
选项 | 说明 |
---|---|
SOCK_NONBLOCK | 在新文件描述符引用的打开文件描述上设置 O_NONBLOCK 文件状态标志。 |
SOCK_CLOEXEC | 在新文件描述符上设置 close-on-exec (FD_CLOEXEC) 标志。 |
int protocol
:指定与套接字一起使用的特定协议。通常为0表示默认的协议。
返回值
- 成功时返回新套接字的文件描述符。
- 错误时返回 -1,并设置
errno
以指示错误。
socket地址
每一个地址都代表着某个通信域中唯一的套接字端点,地址的具体格式则会根据所处的通信域而有所不同。为了能够让各种不同格式的地址都能统一地传递给套接字相关的函数,系统会将这些地址强制转换为一种通用的结构——sockaddr结构体。这样做可以方便地处理和传递各种网络地址信息。
IP协议地址结构体如下:
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */};
/* Internet address */struct in_addr { uint32_t s_addr; /* address in network byte order */};
关联地址bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作用
bind
函数用于将 addr
指定的地址分配给由文件描述符 sockfd
引用的套接字。
在 SOCK_STREAM
套接字可以接收连接之前,通常需要使用 bind()
分配本地地址。
参数
int sockfd
:套接字的文件描述符。const struct sockaddr *addr
:指向要绑定到套接字的地址结构的指针。传递的实际结构取决于地址族。socklen_t addrlen
:指定addr
指向的地址结构的大小(以字节为单位)。
返回值
- 成功时返回 0。
- 失败时返回 -1,并设置
errno
以指示错误类型。