Linux 之 【网络套接字编程】(网络字节序、字节序转换函数、套接字编程类型、标准套接字编程的头文件、sockaddr结构、整数IP与字符串IP的转换)

目录

一、网络字节序

定义

字节序转换函数

概览

命名规律

htons

htonl

ntohl

ntohs

二、套接字编程的类型

域间套接字:同一机器内的进程间通信

原始套接字:直接操作网络层及以下的数据包

网络套接字:跨网络的用户间通信

三、标准套接字编程的头文件

四、sockaddr结构

核心设计思想:通用接口与具体实现

[通用地址结构:struct sockaddr](#通用地址结构:struct sockaddr)

[IPv4 专用地址结构:struct sockaddr_in](#IPv4 专用地址结构:struct sockaddr_in)

bzero

[IPv4 地址封装:struct in_addr](#IPv4 地址封装:struct in_addr)

整数IP<--->字符串IP

inet_addr

inet_pton

inet_ntoa

inet_ntop

结构体关系与强制转换示例


一、网络字节序

定义

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_UNIXAF_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 是文件路径字符串

解决方案使用强制类型转换和地址族字段

  1. 定义一个通用的基类结构体 struct sockaddr
  2. 定义具体的派生类结构体 struct sockaddr_in (IPv4)、struct sockaddr_in6 (IPv6)
  3. 所有 API 函数都接受 struct sockaddr * 类型的指针
  4. 程序员在使用时,将具体的结构体指针强制转换为通用指针传给 API
  5. 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,即 -1255.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_addrinet_aton,填充 sin_addrsin6_addr 结构

inet_ntoa

项目 说明
函数名 inet_ntoa
全称 Internet Network to Address(网络格式转地址字符串)
头文件 <arpa/inet.h> (Linux/Unix) <winsock2.h> (Windows)
原型 char *inet_ntoa(struct in_addr in);
参数 instruct 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));
相关推荐
不知名。。。。。。。。1 小时前
Linux---序列化
linux
江畔何人初1 小时前
MySQL 服务器进程的三层结构
linux·运维·服务器·云原生·mysal
陈桴浮海1 小时前
MySQL 主从复制与 GTID 环形复制
linux·mysql·云原生
白太岁1 小时前
C++:(6) 常用 linux 命令:进程管理、日志查看、网络端口与文件权限
linux·运维·服务器
科技块儿1 小时前
【数据亲测】商业IP库在广告ab测试中的roi提升效果分析
网络·tcp/ip·ab测试
MMME~2 小时前
HAProxy:高性能负载均衡实战指南
linux·运维·数据库
sryyd_022 小时前
云原生-高可用集群keepalived
服务器·网络·云原生
野指针YZZ2 小时前
Gstreamer插入第三方plugins流程:rgaconvert
linux·音视频·rk3588
快快起来写代码2 小时前
Arrays.asList方法踩坑
linux