目录
[通用地址结构:struct sockaddr](#通用地址结构:struct sockaddr)
[IPv4 专用地址结构:struct sockaddr_in](#IPv4 专用地址结构:struct sockaddr_in)
[IPv4 地址封装:struct in_addr](#IPv4 地址封装:struct in_addr)
一、网络字节序
定义
TCP/IP协议强制规定,网络上传输的数据流必须使用大端字节序(低地址存放高位字节)
通俗理解:像我们写数字一样,左边(低地址)是高位,右边(高地址)是低位。例如 0x12345678,先发 0x12,最后发 0x78。
- 统一原因
互联网由不同架构的机器组成(x86是小端,PowerPC/某些网络设备是大端)
网络传输严格遵循从低地址到高地址的顺序发送和接收 :发送端将数据在内存中从低地址字节开始依次发出,接收端按收到顺序依次存入本地的低地址到高地址。这意味着网络上的字节流顺序完全取决于发送端的内存布局------小端机发送时先发低位字节,大端机先发高位字节 。接收端则原样按序存入内存,最终解释出的数值由本机字节序决定 。这就是为什么直接发送会导致跨平台错误,而使用
htons/ntohs统一转换为网络字节序(大端)发送,能保证无论发送端是什么机器,网络上的字节流都是"高位在前"的标准顺序,接收端再根据自身字节序正确还原
| 机器类型 | 内存表示 (低→高) | 发送顺序 | 网络字节流 | 大端机接收解释 | 小端机接收解释 |
|---|---|---|---|---|---|
| 小端机发 | 78 56 34 12 |
78→56→34→12 | 78 56 34 12 |
0x78563412 ❌ | 0x12345678 ✅ |
| 大端机发 | 12 34 56 78 |
12→34→56→78 | 12 34 56 78 |
0x12345678 ✅ | 0x78563412 ❌ |
- 发送端(编码)
通过将数据从主机字节序转换为网络字节序,保证线上的数据流符合 TCP/IP 标准
如果是大端机:不需要转(或者说转了也不变)
如果是小端机:必须翻转字节顺序
- 接收端(解码)
通过将数据从网络字节序转换回主机字节序,保证本地CPU能够读取到正确的数据
如果是大端机:不需要转
如果是小端机:必须翻转字节顺序
字节序转换函数
概览
| 函数名 | 全称 | 功能 | 应用场景 |
|---|---|---|---|
| htonl | Host to Network Long | 主机序 → 网络序 (32位) | 发送IP地址前转换 |
| htons | Host to Network Short | 主机序 → 网络序 (16位) | 发送端口号前转换 |
| ntohl | Network to Host Long | 网络序 → 主机序 (32位) | 收到IP地址后转换 |
| ntohs | Network to Host Short | 网络序 → 主机序 (16位) | 收到端口号后转换 |
如果主机是大端:函数直接返回原值(空操作,效率高)
如果主机是小端:函数进行字节翻转(Bit Flip)后返回
命名规律
h = Host(主机字节序)
n = Network(网络字节序)
l = Long(32位整数,如IP地址)
s = Short(16位整数,如端口号)
to = 转换方向
htons
| 项目 | 说明 |
|---|---|
| 函数名 | htons |
| 全称 | Host to Network Short |
| 原型 | uint16_t htons(uint16_t hostshort); |
| 参数 | hostshort:待转换的16位主机字节序整数 |
| 返回值 | 转换后的16位网络字节序整数 |
| 头文件 | <arpa/inet.h> |
| 功能 | 将16位整数从主机字节序转换为网络字节序 |
| 应用场景 | 发送端口号前转换 |
htonl
| 项目 | 说明 |
|---|---|
| 函数名 | htonl |
| 全称 | Host to Network Long |
| 原型 | uint32_t htonl(uint32_t hostlong); |
| 参数 | hostlong:待转换的32位主机字节序整数 |
| 返回值 | 转换后的32位网络字节序整数 |
| 头文件 | <arpa/inet.h> |
| 功能 | 将32位整数从主机字节序转换为网络字节序 |
| 应用场景 | 发送IP地址前转换 |
ntohl
| 项目 | 说明 |
|---|---|
| 函数名 | ntohl |
| 全称 | Network to Host Long |
| 原型 | uint32_t ntohl(uint32_t netlong); |
| 参数 | netlong:待转换的32位网络字节序整数 |
| 返回值 | 转换后的32位主机字节序整数 |
| 头文件 | <arpa/inet.h> |
| 功能 | 将32位整数从网络字节序转换回主机字节序 |
| 应用场景 | 接收到IP地址后解析 |
ntohs
| 项目 | 说明 |
|---|---|
| 函数名 | ntohs |
| 全称 | Network to Host Short |
| 原型 | uint16_t ntohs(uint16_t netshort); |
| 参数 | netshort:待转换的16位网络字节序整数 |
| 返回值 | 转换后的16位主机字节序整数 |
| 头文件 | <arpa/inet.h> |
| 功能 | 将16位整数从网络字节序转换回主机字节序 |
| 应用场景 | 接收到端口号后解析 |
二、套接字编程的类型
域间套接字:同一机器内的进程间通信
| 项目 | 说明 |
|---|---|
| 名称 | Unix Domain Socket (UDS) / 本地套接字 |
| 通信范围 | 同一台主机内部 |
| 地址形式 | 文件路径(如 /tmp/test.sock) |
| 核心优势 | 高效:不经过网络协议栈,不进行报文封装和校验,直接在内核层面拷贝数据,比TCP本地环回(127.0.0.1)更快 |
| 典型场景 | Nginx与FastCGI通信、MySQL本地连接、Docker守护进程与客户端通信 |
| 编程特点 | 用法与网络套接字类似,但地址族使用 AF_UNIX 或 AF_LOCAL |
原始套接字:直接操作网络层及以下的数据包
| 项目 | 说明 |
|---|---|
| 名称 | Raw Socket |
| 通信范围 | 跨网络(但工作在更底层) |
| 核心能力 | 允许程序员自己构造IP头部、ICMP头部等,绕过TCP/UDP协议栈 |
| 典型场景 | Ping工具(构造ICMP包)、Traceroute、Wireshark等抓包工具、网络攻击与防御工具 |
| 编程特点 | 需要root权限,地址族使用 AF_PACKET(Linux)或 AF_INET 配合 SOCK_RAW,需要自己处理协议细节 |
| 注意事项 | 不经过传输层,所以没有TCP的可靠性和端口概念 |
网络套接字:跨网络的用户间通信
| 项目 | 说明 |
|---|---|
| 名称 | Network Socket / Internet Socket |
| 通信范围 | 不同主机之间(局域网或互联网) |
| 地址形式 | IP地址 + 端口号 |
| 核心协议 | TCP (SOCK_STREAM) 和 UDP (SOCK_DGRAM) |
| 典型场景 | 浏览器访问网站、微信聊天、远程登录SSH、视频直播 |
| 编程特点 | 地址族使用 AF_INET(IPv4)或 AF_INET6(IPv6),最常用的套接字类型 |
三、标准套接字编程的头文件
// 标准套接字编程包含
#include <sys/socket.h> // socket(), bind(), connect() 等
#include <netinet/in.h> // sockaddr_in, sockaddr_in6 结构体
#include <arpa/inet.h> // inet_pton(), inet_ntop() 等地址转换函数
#include <string.h> // memset()
#include <unistd.h> // close()
| 头文件 | 作用 | 关键函数/结构体 |
|---|---|---|
<sys/socket.h> |
提供套接字基础API | socket(), bind(), connect(), listen(), accept(), send(), recv() |
<netinet/in.h> |
定义IPv4/IPv6地址结构 | struct sockaddr_in (IPv4), struct sockaddr_in6 (IPv6) |
<arpa/inet.h> |
地址转换函数(字符串↔二进制) | inet_pton() (字符串→二进制), inet_ntop() (二进制→字符串) |
<string.h> |
内存操作(如初始化结构体) | memset(), memcpy() |
<unistd.h> |
系统调用(如关闭套接字) | close() |
四、sockaddr****结构

核心设计思想:通用接口与具体实现
Socket API 的设计者面临一个问题:如何用一套统一的函数处理不同协议的地址
- IPv4 地址是 4 字节 + 2 字节端口
- IPv6 地址是 16 字节 + 2 字节端口
- Unix Domain Socket 是文件路径字符串
解决方案 :使用强制类型转换和地址族字段
- 定义一个通用的基类结构体 struct sockaddr
- 定义具体的派生类结构体 struct sockaddr_in (IPv4)、struct sockaddr_in6 (IPv6)
- 所有 API 函数都接受 struct sockaddr * 类型的指针
- 程序员在使用时,将具体的结构体指针强制转换为通用指针传给 API
- API 内部通过检查第一个字段 sa_family 来判断实际传入的是哪种结构体,并解析数据
通用地址结构:struct sockaddr
这是所有 socket 地址结构的**"基类"**,主要用于函数参数的类型统一
| 成员 | 类型 | 说明 |
|---|---|---|
sa_family |
sa_family_t |
地址族 (关键字段!)。标识协议类型: - AF_INET (IPv4) - AF_INET6 (IPv6) - AF_UNIX / AF_LOCAL (Unix Domain Socket) |
sa_data |
char[14] |
地址数据 。存放具体的地址信息(如IP、端口、路径等)。 注意 :程序不应直接访问 此数组,需通过具体结构体解析。 |
作用 :提供统一的指针类型
struct sockaddr *,使bind()、connect()等函数能接受任何协议的地址
IPv4 专用地址结构:struct sockaddr_in
这是 IPv4 的**"派生类"** ,包含具体的 IPv4 地址信息。定义在头文件
<netinet/in.h>中
| 成员 | 类型 | 说明 |
|---|---|---|
sin_family |
sa_family_t |
必须设为 AF_INET 。对应基类的 sa_family。 |
sin_port |
uint16_t |
16位端口号 。必须使用网络字节序 (大端序)! 需用 htons() 函数转换。 |
sin_addr |
struct in_addr |
32位 IPv4 地址 。必须使用网络字节序 ! 需用 htonl() 或 inet_addr() 转换。 |
sin_zero |
char[8] |
填充字段 。为了保持与 struct sockaddr 大小一致而存在。 必须用 memset 或 bzero 清零,无实际意义。 |
关键点:
- 在代码中我们操作的是 sockaddr_in。
- 传给 API 时强制转换为 (struct sockaddr *)&addr。
- 字节序:sin_port 和 sin_addr 必须是网络字节序(大端序)
- 必须清零
对 struct sockaddr_in 清零的核心原因在于:该结构体中的 sin_zero[8] 是内核强制要求的填充字段,必须全为0 ;由于结构体是分配在栈上的局部变量,其内存内容为随机的垃圾数据,若不通过
memset或初始化显式清零,这些残留数据(尤其是sin_zero中的随机值)会被内核读取,导致bind()等系统调用不可预期地失败,同时也带来非确定性行为、调试困难和潜在的安全隐患
bzero
| 项目 | 说明 |
|---|---|
| 函数名 | bzero |
| 全称 | Byte Zero(将字节区域清零) |
| 头文件 | <strings.h> (注意:不是 <string.h>) |
| 原型 | void bzero(void *s, size_t n); |
| 参数1 | s:指向要清零的内存区域的指针 |
| 参数2 | n:要清零的字节数 |
| 返回值 | 无返回值(void) |
| 功能 | 将指定内存区域的前 n 个字节设置为 0 |
| 起源 | BSD 系统引入的函数,起源于早期 Unix |
| 标准状态 | 废弃函数(在 POSIX.1-2001 中标记为过时,POSIX.1-2008 中已移除) |
与 memset 的对比
| 对比维度 | bzero |
memset |
|---|---|---|
| 原型 | void bzero(void *s, size_t n); |
void *memset(void *s, int c, size_t n); |
| 功能 | 将内存区域清零 | 将内存区域设置为指定值 |
| 参数2 | 只填 0(固定) | 可填任意字节(如 0、'\0' 等) |
| 返回值 | 无 | 返回指向 s 的指针 |
| 头文件 | <strings.h> |
<string.h> |
| 标准 | 废弃(legacy) | ANSI C / ISO C 标准 |
| 可移植性 | 差(非标准) | 好(所有平台支持) |
IPv4 地址封装:struct in_addr
专门用于表示 32 位 IPv4 地址的结构体



| 结构体 | 作用 | 关键字段 | 字节序要求 |
| struct sockaddr | 通用基类(API接口用) | sa_family | 无 |
| struct sockaddr_in | IPv4 专用结构 | sin_family, sin_port, sin_addr | 端口和IP必须网络字节序 |
struct in_addr |
封装 IPv4 地址 | s_addr (32位整数) |
必须网络字节序 |
|---|
| 类型 | 变量 | 说明 |
|---|---|---|
struct in_addr |
sin_addr |
封装 IPv4 地址 |
uint32_t |
s_addr(sin_addr的成员) |
32位 IPv4 地址 (网络字节序)。 注意 :不能直接赋值为 192.168.1.1 这样的十进制数,必须使用 htonl() 或 inet_addr()。 |
IP地址设计为结构体的原因:
| 原因 | 说明 |
|---|---|
| 历史兼容性 | 早期的 Unix 实现中,IP 地址可能有多种表达方式,结构体便于扩展 |
| 抽象与封装 | 隐藏底层数据类型,允许未来修改实现而不影响上层代码 |
| 语义清晰 | 明确表示这是一个 IP 地址,而不是一个普通的 32 位整数 |
| 支持多种地址族 | 为后续支持 IPv6 等不同长度的地址奠定基础 |
| 操作便利性 | 可以方便地定义针对 IP 地址的操作函数 |
整数IP<--->字符串IP
整数IP与字符串IP转换的核心思想是利用IP地址的32位二进制与点分十进制的一一对应关系 。转换时有两种方法:一是通过位运算 直接操作整数的各字节(整数→字符串:右移提取各字节;字符串→整数:左移拼接各字节),容易实现;二是利用内存映射定义四字段结构体 ,通过指针强转直接访问各字节,较难实现
实际开发中应优先使用标准库函数inet_pton/inet_ntop
// 网络字节序整数 -> 点分十进制字符串
std::string ip_to_str(uint32_t net_ip) noexcept {
uint8_t bytes[4] = {
static_cast<uint8_t>(net_ip >> 24),
static_cast<uint8_t>(net_ip >> 16),
static_cast<uint8_t>(net_ip >> 8),
static_cast<uint8_t>(net_ip)
};
char buffer[16] = {0};
snprintf(buffer, sizeof(buffer), "%hhu.%hhu.%hhu.%hhu",
bytes[0], bytes[1], bytes[2], bytes[3]);
return buffer;
}
// 点分十进制字符串 -> 网络字节序整数
// 返回 bool 表示成功/失败,结果通过参数返回
bool str_to_ip(const std::string& ip, uint32_t& result) noexcept {
unsigned int a, b, c, d; // 用unsigned避免溢出
int pos = 0;
// 使用 %n 检查完全匹配
if (sscanf(ip.c_str(), "%u.%u.%u.%u%n",
&a, &b, &c, &d, &pos) == 4 &&
pos == static_cast<int>(ip.length()) &&
(a | b | c | d) <= 0xFF) { // 快速范围检查
result = (a << 24) | (b << 16) | (c << 8) | d;
return true;
}
return false;
}
inet_addr
| 项目 | 说明 |
|---|---|
| 函数名 | inet_addr |
| 全称 | Internet Address (IPv4) |
| 头文件 | <arpa/inet.h> (Linux/Unix) <winsock2.h> (Windows) |
| 原型 | in_addr_t inet_addr(const char *cp); |
| 参数 | cp:点分十进制格式的 IPv4 地址字符串(如 "192.168.1.100") |
| 返回值 | 成功:返回 32位IPv4地址 (网络字节序) 失败:返回 INADDR_NONE (通常是 0xFFFFFFFF,即 -1 或 255.255.255.255) |
| 功能 | 将点分十进制字符串转换为 32位网络字节序的二进制值 |
| 适用场景 | 在 sockaddr_in 结构体中填充 sin_addr.s_addr 字段 |
inet_pton
| 项目 | 说明 |
|---|---|
| 函数名 | inet_pton |
| 全称 | Internet Presentation to Network (地址呈现格式转网络格式) |
| 头文件 | <arpa/inet.h> (Linux/Unix) <ws2tcpip.h> (Windows) |
| 原型 | int inet_pton(int af, const char *src, void *dst); |
| 参数1 | af:地址族,AF_INET (IPv4) 或 AF_INET6 (IPv6) |
| 参数2 | src:点分十进制 (IPv4) 或十六进制 (IPv6) 格式的地址字符串 |
| 参数3 | dst:指向存储二进制结果的指针(struct in_addr * 或 struct in6_addr *) |
| 返回值 | 1 :成功 0 :输入的字符串不是有效的IP地址格式 -1:地址族不支持或发生错误(并设置 errno) |
| 功能 | 将人可读的IP地址字符串转换为网络字节序的二进制格式 |
| 适用场景 | 现代网络编程中替代 inet_addr 和 inet_aton,填充 sin_addr 或 sin6_addr 结构 |
inet_ntoa
| 项目 | 说明 |
|---|---|
| 函数名 | inet_ntoa |
| 全称 | Internet Network to Address(网络格式转地址字符串) |
| 头文件 | <arpa/inet.h> (Linux/Unix) <winsock2.h> (Windows) |
| 原型 | char *inet_ntoa(struct in_addr in); |
| 参数 | in:struct in_addr 结构体,包含网络字节序的32位IPv4地址 |
| 返回值 | 返回指向点分十进制字符串的指针(如 "192.168.1.100") |
| 功能 | 将网络字节序的二进制IP地址转换为点分十进制字符串 |
| 适用场景 | 打印IP地址、日志记录、调试输出 |
| 线程安全 | 非线程安全(使用静态缓冲区) |
inet_ntop
| 项目 | 说明 |
|---|---|
| 函数名 | inet_ntop |
| 全称 | Internet Network to Presentation(网络格式转呈现格式) |
| 头文件 | <arpa/inet.h> (Linux/Unix) <ws2tcpip.h> (Windows) |
| 原型 | const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); |
| 参数1 | af:地址族,AF_INET (IPv4) 或 AF_INET6 (IPv6) |
| 参数2 | src:指向二进制IP地址的指针(struct in_addr * 或 struct in6_addr *) |
| 参数3 | dst:调用者提供的缓冲区,用于存储转换后的字符串 |
| 参数4 | size:缓冲区大小(字节数) |
| 返回值 | 成功:返回 dst 指针 失败:返回 NULL,并设置 errno |
| 功能 | 将网络字节序的二进制IP地址转换为人可读的字符串格式(点分十进制或十六进制) |
| 适用场景 | 打印IP地址、日志记录、调试输出、响应构造 |
| 线程安全 | 线程安全(调用者提供缓冲区) |
结构体关系与强制转换示例
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// 1. 定义并初始化 IPv4 具体结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); // 清零,包括 sin_zero
server_addr.sin_family = AF_INET; // 地址族:IPv4
server_addr.sin_port = htons(8080); // 端口号:8080 (主机序转网络序)
server_addr.sin_addr.s_addr = inet_addr("192.168.1.100"); // IP地址 (字符串转网络序)
// 或者: server_addr.sin_addr.s_addr = htonl(0xC0A80164);
// 2. 调用 bind 时,强制转换为通用指针
// 注意第三个参数是具体结构体的大小
int ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));