https://blog.csdn.net/qscftqwe/article/details/156236643
上节课链接,大家可以点击进行观看
一.sockaddr结构
socket API 是一层抽象的网络编程接口 ,适用于各种底层网络协议,如 IPv4、IPv6 ,以及后面要讲的 UNIX Domain Socket 。然而,各种网络协议的地址格式并不相同 。

- Pv4 和 IPv6 的地址格式定义在
<netinet/in.h>中:IPv4 使用sockaddr_in结构体 ,包含 16 位地址族、16 位端口号和 32 位 IP 地址。- 地址族常量
AF_INET和AF_INET6用于标识协议类型。- socket API 使用
struct sockaddr *作为通用地址指针 ,实际使用时需根据协议族强制转换为具体类型 (如sockaddr_in*)。- 为支持多协议,可使用
sockaddr_storage作为缓冲区 ,并通过sa_family字段判断地址类型后再进行安全转换
1.1 sockaddr结构
cpp
// 通用套接字地址结构 用于网络 API 的地址参数
// 实际使用时通常用 sockaddr_in(IPv4)或 sockaddr_in6(IPv6)填充
#include <sys/socket.h>
struct sockaddr
{
sa_family_t sa_family; // 地址族(如 AF_INET)
char sa_data[14]; // 地址数据(协议相关)
};
// 说明:
// 该结构是抽象基类 不直接使用
// 调用 bind/connect/accept 时 将具体地址结构(如 sockaddr_in)强制转换为 sockaddr *
1.2 sockaddr_in结构
cpp
// IPv4 专用套接字地址结构 用于 bind/connect/accept 等函数
// 实际传参时需强制转换为 struct sockaddr *
#include <netinet/in.h>
struct sockaddr_in
{
sa_family_t sin_family; // 地址族 必须为 AF_INET
in_port_t sin_port; // 端口号(网络字节序)
struct in_addr sin_addr; // IPv4 地址(网络字节序)
char sin_zero[8]; // 填充字段 保持与 sockaddr 长度一致
};
// 成员说明:
// sin_family - 固定设为 AF_INET
// sin_port - 用 htons() 转换主机端口到网络序
// sin_addr - 用 inet_pton() 或 INADDR_ANY 设置 IP
// 表示 IPv4 地址的结构体 通常作为 sockaddr_in 的成员使用
// 实际地址以网络字节序(大端)存储
#include <netinet/in.h>
struct in_addr
{
in_addr_t s_addr; // 32 位 IPv4 地址(网络字节序)
};
// 说明:
// s_addr 是 uint32_t 类型 存储如 0x0100007f(即 127.0.0.1)
// 应使用 inet_pton() / inet_ntop() 或 htonl() 等函数转换
二.地址转换函数
2.1 字符串转in_addr的函数
1.inet_addr
cpp
// 将点分十进制 IPv4 字符串(如 "192.168.1.1")转换为 32 位网络字节序整数
// 已过时 建议改用 inet_pton
#include <arpa/inet.h>
in_addr_t inet_addr(const char *strptr);
// 参数:
// strptr - 指向 IPv4 地址字符串
// 返回值:
// 成功:返回网络字节序的 32 位地址(in_addr_t)
// 失败或输入为 "255.255.255.255":返回 INADDR_NONE(通常为 -1)
// 案例:
in_addr_t ip = inet_addr("192.168.1.1");
2.inet_aton
cpp
// 将点分十进制 IPv4 字符串安全地转换为 struct in_addr
// 推荐替代 inet_addr 因其能明确区分错误与合法地址
#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);
// 参数:
// strptr - 指向 IPv4 地址字符串
// addrptr - 指向 struct in_addr 的指针 用于存储结果
// 返回值:
// 成功:返回 1
// 失败:返回 0
// 案例:
struct in_addr ip;
if (inet_aton("192.168.1.1", &ip)) {
// 转换成功
}
3.inet_pton
cpp
// 通用地址字符串转二进制(支持 IPv4 和 IPv6)
// 现代推荐方式 可移植性好
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr);
// 参数:
// family - 地址族(AF_INET 或 AF_INET6)
// strptr - 指向 IP 字符串(如 "192.168.1.1" 或 "::1")
// addrptr - 指向目标地址缓冲区(如 &in_addr 或 &in6_addr)
// 返回值:
// 成功:返回 1
// 输入无效:返回 0
// 不支持的 family:返回 -1
// 案例:
struct in_addr ip4;
inet_pton(AF_INET, "192.168.1.1", &ip4);
2.2 in_addr转字符串的函数
1.inet_ntoa
cpp
// 将 IPv4 地址(struct in_addr)转换为点分十进制字符串(如 "192.168.1.1")
// 返回静态缓冲区指针 非线程安全 且后续调用会覆盖结果
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr inaddr);
// 参数:
// inaddr - 要转换的 IPv4 地址(网络字节序)
// 返回值:
// 成功:返回指向静态字符串的指针(如 "192.168.0.1")
// 失败:行为未定义(通常仍返回字符串)
// 案例:
struct in_addr ip;
ip.s_addr = htonl(INADDR_LOOPBACK);
printf("IP: %s\n", inet_ntoa(ip));
2.inet_ntop
cpp
// 通用二进制地址转字符串(支持 IPv4 和 IPv6)
// 线程安全 需提供输出缓冲区 推荐替代 inet_ntoa
#include <arpa/inet.h>
const char *inet_ntop(
int family,
const void *addrptr,
char *strptr,
size_t len
);
// 参数:
// family - 地址族(AF_INET 或 AF_INET6)
// addrptr - 指向二进制地址(如 &in_addr 或 &in6_addr)
// strptr - 输出缓冲区(用于存放结果字符串)
// len - 缓冲区大小(IPv4 至少 INET_ADDRSTRLEN,IPv6 至少 INET6_ADDRSTRLEN)
// 返回值:
// 成功:返回 strptr
// 失败:返回 NULL 并设置 errno
// 案例:
char str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &ip.s_addr, str, sizeof(str));
inet_ntoa相关知识
inet_ntoa将 IPv4 地址转换为点分十进制字符串,其返回值是指向内部静态缓冲区的指针。- 该缓冲区位于静态存储区,无需手动释放;
- 但多次调用会覆盖之前的结果;
- 在多线程环境下不安全。
推荐使用 inet_ntop 替代:它由调用者提供输出缓冲区,线程安全,且同时支持 IPv4 和 IPv6。
三.UDP
3.1 服务器
cpp
// 1. 创建UDP套接字
int sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
// 2. 配置服务器地址结构
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_); // 转换端口号为网络字节序
local.sin_addr.s_addr = inet_addr(ip_.c_str()); // 转换IP地址
// 3. 绑定套接字到本地地址
int ret = bind(sockfd_, (struct sockaddr*)&local, sizeof(local)); // 修正变量名sockfd_
// 4. 接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
// 5. 发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
3.2 客户端
补充知识:
- 客户端通常无需显式调用
bind(),操作系统会在需要时(TCP 在connect()时,UDP 在首次发送时 )自动为其分配一个唯一的临时端口。- 该临时端口同样受"端口独占 "规则约束:在同一协议和本地地址下,不能被多个 socket 同时绑定。
- 客户端端口号无需关心 ,只要在本机唯一即可 ,用于服务器回传数据。
cpp
// 1. 创建UDP套接字
fd_ = socket(AF_INET, SOCK_DGRAM, 0);
// 2. 配置服务器地址结构
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport); // 端口号转网络字节序
inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr)); // IP地址转换
// 3. 发送数据
string message;
char buffer[1024];
sendto(fd_, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
// 4. 接收数据
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t s = recvfrom(fd_, buffer, 1023, 0,
(struct sockaddr*)&temp, &len); // 保留1字节给\0
if (s > 0) buffer[s] = '\0'; // 确保字符串终止
四.补充知识
端口知识
- 0--1023:特权端口,需 root 权限;
- 1024--49151:注册端口,普通用户可用,许多数据库/服务在此范围
- 49152--65535 :临时端口,由系统自动分配。
建议服务器选用 1024 以上且未被占用的端口,开发中常用 8000+ 以避免冲突,但非强制。UDP 套接字性质
UDP 套接字支持同时收发数据,是全双工的。
环回地址
环回地址(如 127.0.0.1)的数据包在内核中被拦截并重定向回协议栈上层,不经过物理网络 ,但完整经历协议栈处理。
好了今天的知识就讲到这里了,今天的内容还是比较多的,后面我会带来TCP相关套接字的实现,以及最后就是二者的实现了,希望让大家有所收获!