目录
[一、 SocketsOps.h](#一、 SocketsOps.h)
[1. SocketsOps 的整体定位与核心设计目标](#1. SocketsOps 的整体定位与核心设计目标)
[2. 核心接口分类解析](#2. 核心接口分类解析)
[1. 非阻塞 fd 创建:高性能事件驱动的基础](#1. 非阻塞 fd 创建:高性能事件驱动的基础)
[2. 核心连接操作:分层错误处理](#2. 核心连接操作:分层错误处理)
[3. IO 操作:封装原生读写](#3. IO 操作:封装原生读写)
[4. fd 管理:安全关闭](#4. fd 管理:安全关闭)
[5. 地址转换:协议无关的字符串 / 结构体互转](#5. 地址转换:协议无关的字符串 / 结构体互转)
[6. 类型转换:安全的地址结构体转换](#6. 类型转换:安全的地址结构体转换)
[7. 辅助工具:fd 信息查询与校验](#7. 辅助工具:fd 信息查询与校验)
[3. 核心设计亮点总结](#3. 核心设计亮点总结)
[二、 SocketsOps.cc](#二、 SocketsOps.cc)
[1. 代码整体核心逻辑回顾](#1. 代码整体核心逻辑回顾)
[2. 逐模块拆解核心实现](#2. 逐模块拆解核心实现)
[1. 匿名命名空间:跨平台辅助函数](#1. 匿名命名空间:跨平台辅助函数)
[2. 地址类型转换:安全的类型转换封装](#2. 地址类型转换:安全的类型转换封装)
[3. 非阻塞 fd 创建:高性能 + 降级适配](#3. 非阻塞 fd 创建:高性能 + 降级适配)
[4. bind/listen:致命错误直接终止](#4. bind/listen:致命错误直接终止)
[5. accept:跨平台 + 精细化错误处理(核心)](#5. accept:跨平台 + 精细化错误处理(核心))
[6. 基础 IO / 管理操作:简单封装 + 错误日志](#6. 基础 IO / 管理操作:简单封装 + 错误日志)
[7. 地址转换:协议无关 + 可读格式化](#7. 地址转换:协议无关 + 可读格式化)
[8. fd 信息查询:错误状态 + 地址获取](#8. fd 信息查询:错误状态 + 地址获取)
[9. 自连接检测:避免无效连接](#9. 自连接检测:避免无效连接)
[3. 核心设计亮点总结](#3. 核心设计亮点总结)
一、 SocketsOps.h
先贴出完整代码,再逐部分解释:
cpp
// Copyright 2010, Shuo Chen. All rights reserved.
// http://code.google.com/p/muduo/
//
// 本源代码的使用受 BSD 风格许可证约束
// 该许可证可在 License 文件中查阅。
// 作者:陈硕 (chenshuo at chenshuo dot com)
//
// 这是一个内部头文件,你不应该直接包含它。
#ifndef MUDUO_NET_SOCKETSOPS_H
#define MUDUO_NET_SOCKETSOPS_H
#include <arpa/inet.h> // 包含 sockaddr_in/sockaddr_in6、inet_pton/inet_ntop 等定义
namespace muduo
{
namespace net
{
namespace sockets
{
///
/// 创建一个非阻塞的 socket 文件描述符(fd)
/// 核心特性:1. 设置为非阻塞模式;2. 设置 FD_CLOEXEC(避免 fork 后子进程继承);
/// 3. 若创建失败,直接终止程序(abort)
/// @param family 地址族(AF_INET=IPv4,AF_INET6=IPv6)
/// @return 成功:非阻塞 + FD_CLOEXEC 的 socket fd;失败:终止程序
int createNonblockingOrDie(sa_family_t family);
/// 建立 socket 连接(客户端)
/// 封装 connect 系统调用,处理 EINTR(被信号中断后重试)
/// @param sockfd 已创建的 socket fd
/// @param addr 要连接的对等方地址(sockaddr 通用类型,兼容 IPv4/IPv6)
/// @return 成功:0;失败:-1(errno 标识具体错误)
int connect(int sockfd, const struct sockaddr* addr);
/// 绑定地址到 socket(服务端)
/// 若绑定失败,直接终止程序(abort)
/// @param sockfd 已创建的 socket fd
/// @param addr 要绑定的本地地址(sockaddr 通用类型)
void bindOrDie(int sockfd, const struct sockaddr* addr);
/// 监听 socket(服务端,进入被动连接状态)
/// 若监听失败,直接终止程序(abort)
/// @param sockfd 已绑定地址的 socket fd
void listenOrDie(int sockfd);
/// 接受一个客户端连接(服务端)
/// 核心特性:1. 封装 accept4 系统调用;2. 返回的 connfd 已设为非阻塞 + FD_CLOEXEC;
/// 3. 处理 EINTR(被信号中断后重试)
/// @param sockfd 监听用的 socket fd
/// @param addr 输出参数,存储客户端的地址信息(sockaddr_in6 兼容 IPv4/IPv6)
/// @return 成功:新的连接 fd(非阻塞 + FD_CLOEXEC);失败:-1
int accept(int sockfd, struct sockaddr_in6* addr);
/// 从 socket 读取数据(封装 read 系统调用)
/// 处理 EINTR(被信号中断后重试),不处理 EAGAIN(非阻塞模式下的无数据)
/// @param sockfd 已连接的 socket fd
/// @param buf 存储读取数据的缓冲区
/// @param count 要读取的字节数
/// @return 成功:实际读取的字节数;失败:-1(errno 标识具体错误)
ssize_t read(int sockfd, void *buf, size_t count);
/// 分散读(封装 readv 系统调用)
/// 将数据读取到多个不连续的缓冲区,减少系统调用次数
/// @param sockfd 已连接的 socket fd
/// @param iov 指向 iovec 结构体数组的指针(存储多个缓冲区的地址和长度)
/// @param iovcnt iovec 数组的元素个数
/// @return 成功:实际读取的总字节数;失败:-1
ssize_t readv(int sockfd, const struct iovec *iov, int iovcnt);
/// 向 socket 写入数据(封装 write 系统调用)
/// 处理 EINTR(被信号中断后重试),不处理 EAGAIN(非阻塞模式下的不可写)
/// @param sockfd 已连接的 socket fd
/// @param buf 存储要写入数据的缓冲区
/// @param count 要写入的字节数
/// @return 成功:实际写入的字节数;失败:-1
ssize_t write(int sockfd, const void *buf, size_t count);
/// 关闭 socket fd(封装 close 系统调用)
/// 核心处理:1. 确保 fd 被正确关闭;2. 避免重复关闭(fd 设为 -1 无副作用);
/// 3. 处理 EINTR(被信号中断后重试)
/// @param sockfd 要关闭的 socket fd
void close(int sockfd);
/// 关闭 socket 的写端(TCP 半关闭,封装 shutdown 系统调用)
/// 向对等方发送 FIN 包,表明本端不再发送数据,但仍可接收数据
/// @param sockfd 已连接的 socket fd
void shutdownWrite(int sockfd);
/// 将 socket 地址结构体转换为「IP:端口」格式的字符串(便于日志/调试)
/// @param buf 存储结果的缓冲区
/// @param size 缓冲区长度
/// @param addr 要转换的 socket 地址结构体(sockaddr 通用类型,兼容 IPv4/IPv6)
void toIpPort(char* buf, size_t size,
const struct sockaddr* addr);
/// 将 socket 地址结构体转换为 IP 字符串(仅 IP,不含端口)
/// @param buf 存储结果的缓冲区
/// @param size 缓冲区长度
/// @param addr 要转换的 socket 地址结构体(兼容 IPv4/IPv6)
void toIp(char* buf, size_t size,
const struct sockaddr* addr);
/// 将「IP + 端口」转换为 IPv4 地址结构体(sockaddr_in)
/// @param ip 点分十进制的 IPv4 字符串(如 "127.0.0.1")
/// @param port 端口号(主机序)
/// @param addr 输出参数,存储转换后的 sockaddr_in 结构体(网络序)
void fromIpPort(const char* ip, uint16_t port,
struct sockaddr_in* addr);
/// 将「IP + 端口」转换为 IPv6 地址结构体(sockaddr_in6)
/// @param ip 冒号分隔的 IPv6 字符串(如 "::1")
/// @param port 端口号(主机序)
/// @param addr 输出参数,存储转换后的 sockaddr_in6 结构体(网络序)
void fromIpPort(const char* ip, uint16_t port,
struct sockaddr_in6* addr);
/// 获取 socket 的错误状态(封装 getsockopt)
/// 用于非阻塞 connect 等场景,检测连接是否成功建立
/// @param sockfd 要查询的 socket fd
/// @return 成功:socket 的错误码(0 表示无错误);失败:-1
int getSocketError(int sockfd);
/// 类型转换:sockaddr_in* → const sockaddr*(兼容通用地址类型)
/// 用于函数参数的类型适配(避免强制类型转换)
const struct sockaddr* sockaddr_cast(const struct sockaddr_in* addr);
/// 类型转换:sockaddr_in6* → const sockaddr*(兼容通用地址类型)
const struct sockaddr* sockaddr_cast(const struct sockaddr_in6* addr);
/// 类型转换:sockaddr_in6* → sockaddr*(非 const 版本)
struct sockaddr* sockaddr_cast(struct sockaddr_in6* addr);
/// 类型转换:const sockaddr* → const sockaddr_in*(IPv4 专用)
/// 需确保输入 addr 实际是 IPv4 地址,否则会出错
const struct sockaddr_in* sockaddr_in_cast(const struct sockaddr* addr);
/// 类型转换:const sockaddr* → const sockaddr_in6*(IPv6 专用)
/// 需确保输入 addr 实际是 IPv6 地址,否则会出错
const struct sockaddr_in6* sockaddr_in6_cast(const struct sockaddr* addr);
/// 获取 socket 绑定的本地地址(封装 getsockname)
/// 返回 sockaddr_in6 兼容 IPv4/IPv6,内核会自动转换
/// @param sockfd 已绑定/连接的 socket fd
/// @return 本地地址结构体(sockaddr_in6)
struct sockaddr_in6 getLocalAddr(int sockfd);
/// 获取 socket 对等方的地址(封装 getpeername)
/// 返回 sockaddr_in6 兼容 IPv4/IPv6
/// @param sockfd 已连接的 socket fd
/// @return 对等方地址结构体(sockaddr_in6)
struct sockaddr_in6 getPeerAddr(int sockfd);
/// 检测 socket 是否发生「自连接」(客户端连接到自身)
/// 自连接场景:客户端绑定端口后,连接的地址恰好是自身的地址+端口
/// @param sockfd 已连接的 socket fd
/// @return true=是自连接;false=非自连接
bool isSelfConnect(int sockfd);
} // namespace sockets
} // namespace net
} // namespace muduo
#endif // MUDUO_NET_SOCKETSOPS_H
1. SocketsOps 的整体定位与核心设计目标
SocketsOps 是 Muduo 网络层的最底层工具集(无类封装,纯函数接口),也是所有 socket 原生系统调用的 "唯一入口",核心设计目标:
- 统一错误处理 :对 "致命错误"(如创建监听 fd 失败)直接
abort,对 "非致命错误"(如 connect 失败)返回错误码,避免上层重复处理系统调用错误; - 协议兼容 :无缝适配 IPv4/IPv6,通过
sockaddr_cast系列函数统一地址结构体转换,上层无需区分协议版本; - 高性能默认配置 :创建的 fd 默认带
O_NONBLOCK(非阻塞)+O_CLOEXEC(exec 时关闭),适配 epoll 事件驱动模型; - 工具化封装:将地址转换、fd 错误查询、自连接检测等高频操作封装为函数,简化上层代码,减少手动转换 / 校验的错误。
在 Muduo 网络层中,SocketsOps 是 "承上启下" 的核心:
cpp
上层组件(Socket/Acceptor/TcpConnection) → SocketsOps → 原生socket系统调用
所有底层 socket 操作(如创建 fd、bind、accept)均通过 SocketsOps 完成,上层组件仅做逻辑封装,不直接调用原生系统调用。
2. 核心接口分类解析
1. 非阻塞 fd 创建:高性能事件驱动的基础
cpp
/// 创建非阻塞、close-on-exec的socket fd,失败则abort(致命错误)
int createNonblockingOrDie(sa_family_t family);
- 核心参数 :
family为协议族(AF_INET/AF_INET6),支持 IPv4/IPv6; - 关键特性 :
- 非阻塞(
O_NONBLOCK):适配 epoll 事件驱动,避免 fd 阻塞导致事件循环卡住; - close-on-exec(
O_CLOEXEC):fork+exec 后自动关闭 fd,避免子进程继承无用 fd 导致泄漏;
- 非阻塞(
- 错误处理 :创建失败直接
abort------socket fd 创建失败属于系统级致命错误,无法恢复,需立即终止进程。
2. 核心连接操作:分层错误处理
| 接口 | 作用 | 错误处理 | 适用场景 |
|---|---|---|---|
int connect(int sockfd, const struct sockaddr* addr) |
发起连接(客户端) | 返回错误码(如 ECONNREFUSED),不 abort |
客户端 TcpClient 发起连接(连接失败非致命,可重试) |
void bindOrDie(int sockfd, const struct sockaddr* addr) |
绑定地址(服务端) | 失败则 abort | 服务端监听 socket 绑定端口(绑定失败如端口被占,是致命错误) |
void listenOrDie(int sockfd) |
开始监听 | 失败则 abort | 服务端监听 socket(监听失败是致命错误) |
int accept(int sockfd, struct sockaddr_in6* addr) |
接受连接 | 返回新 fd(成功)/-1(失败) | 服务端 Acceptor 接收新连接(单个连接失败不影响整体,非致命) |
核心设计:
- "OrDie" 后缀接口(bind/listen):失败则
abort,因为监听 socket 的 bind/listen 失败属于不可恢复的致命错误; - 无 "OrDie" 接口(connect/accept):返回错误码,因为客户端连接失败、服务端单个连接接收失败属于非致命错误,上层可处理(如重试、忽略)。
3. IO 操作:封装原生读写
cpp
// 读数据(封装::read,处理EINTR等可重试错误)
ssize_t read(int sockfd, void *buf, size_t count);
// 分散读(封装::readv,适配Buffer的双缓冲区)
ssize_t readv(int sockfd, const struct iovec *iov, int iovcnt);
// 写数据(封装::write,处理EINTR等可重试错误)
ssize_t write(int sockfd, const void *buf, size_t count);
- 核心价值 :封装原生
read/readv/write,自动处理EINTR(系统调用被信号中断)等可重试错误,上层无需手动重试; - 适配 Buffer 类:
readv是 Buffer::readFd 的底层依赖,支持一次读取到多个缓冲区。
4. fd 管理:安全关闭
cpp
// 关闭fd(封装::close,处理EINTR,避免重复关闭)
void close(int sockfd);
// 关闭写端(TCP半关闭,封装::shutdown(SHUT_WR))
void shutdownWrite(int sockfd);
close:保证 fd 被正确关闭(即使多次调用也不会出错),避免原生::close因EINTR导致关闭失败;shutdownWrite:仅关闭写端,实现 TCP 优雅关闭,是 TcpConnection 断开连接的核心操作。
5. 地址转换:协议无关的字符串 / 结构体互转
cpp
// 将sockaddr(IPv4/IPv6)转换为 "ip:port" 字符串(如 "127.0.0.1:8080")
void toIpPort(char* buf, size_t size, const struct sockaddr* addr);
// 仅转换为IP字符串(如 "127.0.0.1")
void toIp(char* buf, size_t size, const struct sockaddr* addr);
// 将 "ip+port" 转换为 sockaddr_in(IPv4)
void fromIpPort(const char* ip, uint16_t port, struct sockaddr_in* addr);
// 将 "ip+port" 转换为 sockaddr_in6(IPv6)
void fromIpPort(const char* ip, uint16_t port, struct sockaddr_in6* addr);
核心价值 :上层无需区分 IPv4/IPv6,直接调用 toIpPort 即可将地址转为可读字符串(日志 / 调试),fromIpPort 可快速构建地址结构体。
6. 类型转换:安全的地址结构体转换
原生 socket 系统调用中,sockaddr/sockaddr_in/sockaddr_in6 需强制转换,易出错,SocketsOps 封装了安全的转换函数:
cpp
// sockaddr_in → sockaddr(const)
const struct sockaddr* sockaddr_cast(const struct sockaddr_in* addr);
// sockaddr_in6 → sockaddr(const)
const struct sockaddr* sockaddr_cast(const struct sockaddr_in6* addr);
// sockaddr_in6 → sockaddr(非const)
struct sockaddr* sockaddr_cast(struct sockaddr_in6* addr);
// sockaddr → sockaddr_in(const,IPv4)
const struct sockaddr_in* sockaddr_in_cast(const struct sockaddr* addr);
// sockaddr → sockaddr_in6(const,IPv6)
const struct sockaddr_in6* sockaddr_in6_cast(const struct sockaddr* addr);
核心价值 :避免手动 reinterpret_cast,减少类型转换错误(如将 IPv6 地址转为 IPv4),保证协议无关性。
7. 辅助工具:fd 信息查询与校验
cpp
// 获取fd的错误状态(封装::getsockopt(SO_ERROR))
int getSocketError(int sockfd);
// 获取fd的本地地址(IPv6格式,兼容IPv4)
struct sockaddr_in6 getLocalAddr(int sockfd);
// 获取fd的对端地址(IPv6格式,兼容IPv4)
struct sockaddr_in6 getPeerAddr(int sockfd);
// 检测fd是否是自连接(如客户端连接到自身监听端口)
bool isSelfConnect(int sockfd);
getSocketError:非阻塞 connect 后,通过该函数判断连接是否成功(核心逻辑);getLocalAddr/getPeerAddr:快速获取 fd 的本地 / 对端地址,用于日志、连接校验;isSelfConnect:检测 "自连接"(如客户端误连自身),避免无效连接占用资源。
3. 核心设计亮点总结
| 设计点 | 解决的问题 | 价值 |
|---|---|---|
| 分层错误处理(OrDie / 返回错误码) | 所有错误都 abort 或都返回码,导致致命错误静默 / 非致命错误终止 | 区分致命 / 非致命错误,保证系统稳定性 + 上层灵活性 |
| 协议无关封装(sockaddr_cast/IPv6 兼容) | 上层需区分 IPv4/IPv6,代码冗余且易出错 | 一套代码支持双协议,适配未来网络升级 |
| 非阻塞 + close-on-exec 默认配置 | 手动设置 fd 属性易出错,且多一次系统调用 | 适配 epoll 事件驱动,减少 fd 泄漏风险 |
| 工具化纯函数设计 | 底层操作分散在各个组件,代码重复且易出错 | 统一底层操作,减少重复代码,降低维护成本 |
| 处理可重试错误(EINTR) | 原生系统调用被信号中断后,上层需手动重试 | 简化上层逻辑,避免因 EINTR 导致操作失败 |
二、 SocketsOps.cc
先贴出完整代码,再逐部分解释:
cpp
// Copyright 2010, Shuo Chen. All rights reserved.
// http://code.google.com/p/muduo/
//
// 本源代码的使用受 BSD 风格许可证约束
// 该许可证可在 License 文件中查阅。
// 作者:陈硕 (chenshuo at chenshuo dot com)
#include "muduo/net/SocketsOps.h"
#include "muduo/base/Logging.h" // 日志库(LOG_SYSFATAL/LOG_SYSERR 等)
#include "muduo/base/Types.h" // 基础类型定义(如 memZero)
#include "muduo/net/Endian.h" // 字节序转换(hostToNetwork16 等)
#include <errno.h> // 错误码定义
#include <fcntl.h> // fcntl/F_GETFL/F_SETFL 等
#include <stdio.h> // snprintf // 格式化字符串
#include <sys/socket.h> // socket 系统调用
#include <sys/uio.h> // readv // 分散读
#include <unistd.h> // close/read/write 等
using namespace muduo;
using namespace muduo::net;
// 匿名命名空间:仅当前文件可见的辅助函数/类型定义
namespace
{
// 类型别名:简化 sockaddr 类型书写
typedef struct sockaddr SA;
/// 编译条件:VALGRIND(内存检测工具)或 NO_ACCEPT4(系统不支持 accept4)
/// 辅助函数:设置 socket fd 为非阻塞 + FD_CLOEXEC
/// 用于替代 accept4/socket 的直接参数设置(兼容旧系统/调试工具)
#if VALGRIND || defined (NO_ACCEPT4)
void setNonBlockAndCloseOnExec(int sockfd)
{
// 1. 设置非阻塞模式
int flags = ::fcntl(sockfd, F_GETFL, 0); // 获取当前文件状态标志
flags |= O_NONBLOCK; // 追加非阻塞标志
int ret = ::fcntl(sockfd, F_SETFL, flags); // 应用新标志
// FIXME:实际项目中应检查返回值,确保设置成功
// 2. 设置 FD_CLOEXEC(exec 时关闭 fd)
flags = ::fcntl(sockfd, F_GETFD, 0); // 获取文件描述符标志
flags |= FD_CLOEXEC; // 追加 FD_CLOEXEC 标志
ret = ::fcntl(sockfd, F_SETFD, flags); // 应用新标志
// FIXME:实际项目中应检查返回值
(void)ret; // 抑制未使用变量警告
}
#endif
} // namespace
/// 类型转换:sockaddr_in6* → const sockaddr*(兼容通用地址类型)
/// 使用 implicit_cast 确保类型安全(仅允许合法的隐式转换)
const struct sockaddr* sockets::sockaddr_cast(const struct sockaddr_in6* addr)
{
return static_cast<const struct sockaddr*>(implicit_cast<const void*>(addr));
}
/// 类型转换:sockaddr_in6* → sockaddr*(非 const 版本)
struct sockaddr* sockets::sockaddr_cast(struct sockaddr_in6* addr)
{
return static_cast<struct sockaddr*>(implicit_cast<void*>(addr));
}
/// 类型转换:sockaddr_in* → const sockaddr*(IPv4 专用)
const struct sockaddr* sockets::sockaddr_cast(const struct sockaddr_in* addr)
{
return static_cast<const struct sockaddr*>(implicit_cast<const void*>(addr));
}
/// 类型转换:const sockaddr* → const sockaddr_in*(IPv4 专用)
const struct sockaddr_in* sockets::sockaddr_in_cast(const struct sockaddr* addr)
{
return static_cast<const struct sockaddr_in*>(implicit_cast<const void*>(addr));
}
/// 类型转换:const sockaddr* → const sockaddr_in6*(IPv6 专用)
const struct sockaddr_in6* sockets::sockaddr_in6_cast(const struct sockaddr* addr)
{
return static_cast<const struct sockaddr_in6*>(implicit_cast<const void*>(addr));
}
/// 创建非阻塞 + FD_CLOEXEC 的 socket fd(失败则终止程序)
/// 编译兼容:
/// - VALGRIND 模式:先创建普通 fd,再通过 fcntl 设置属性(避免 valgrind 误报)
/// - 普通模式:直接通过 socket 参设置 SOCK_NONBLOCK | SOCK_CLOEXEC
/// @param family 地址族(AF_INET=IPv4,AF_INET6=IPv6)
/// @return 成功:非阻塞 + FD_CLOEXEC 的 fd;失败:终止程序
int sockets::createNonblockingOrDie(sa_family_t family)
{
#if VALGRIND
// VALGRIND 模式:分步创建
int sockfd = ::socket(family, SOCK_STREAM, IPPROTO_TCP); // 创建 TCP socket
if (sockfd < 0)
{
LOG_SYSFATAL << "sockets::createNonblockingOrDie"; // 致命错误日志,终止程序
}
setNonBlockAndCloseOnExec(sockfd); // 手动设置非阻塞 + FD_CLOEXEC
#else
// 普通模式:一次性设置所有属性(高效)
int sockfd = ::socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
if (sockfd < 0)
{
LOG_SYSFATAL << "sockets::createNonblockingOrDie";
}
#endif
return sockfd;
}
/// 绑定地址到 socket(失败则终止程序)
/// 注意:地址长度固定为 sockaddr_in6 大小(兼容 IPv4/IPv6)
/// @param sockfd 已创建的 socket fd
/// @param addr 要绑定的地址(通用 sockaddr 类型)
void sockets::bindOrDie(int sockfd, const struct sockaddr* addr)
{
int ret = ::bind(sockfd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
if (ret < 0)
{
LOG_SYSFATAL << "sockets::bindOrDie"; // 绑定失败,终止程序
}
}
/// 监听 socket(失败则终止程序)
/// backlog 设置为 SOMAXCONN(系统最大待连接队列长度)
/// @param sockfd 已绑定的 socket fd
void sockets::listenOrDie(int sockfd)
{
int ret = ::listen(sockfd, SOMAXCONN);
if (ret < 0)
{
LOG_SYSFATAL << "sockets::listenOrDie"; // 监听失败,终止程序
}
}
/// 接受客户端连接(返回非阻塞 + FD_CLOEXEC 的 connfd)
/// 编译兼容:
/// - VALGRIND/NO_ACCEPT4:调用 accept + 手动设置属性
/// - 普通模式:调用 accept4 直接设置属性
/// 错误处理:区分「预期错误」(重试/忽略)和「非预期错误」(终止程序)
/// @param sockfd 监听用的 socket fd
/// @param addr 输出参数,存储客户端地址(sockaddr_in6 兼容 IPv4/IPv6)
/// @return 成功:connfd;失败:-1(errno 设为对应错误码)
int sockets::accept(int sockfd, struct sockaddr_in6* addr)
{
socklen_t addrlen = static_cast<socklen_t>(sizeof *addr); // 地址结构体长度
#if VALGRIND || defined (NO_ACCEPT4)
// 兼容模式:调用普通 accept,再手动设置属性
int connfd = ::accept(sockfd, sockaddr_cast(addr), &addrlen);
setNonBlockAndCloseOnExec(connfd); // 设置非阻塞 + FD_CLOEXEC
#else
// 普通模式:调用 accept4,直接设置非阻塞 + FD_CLOEXEC
int connfd = ::accept4(sockfd, sockaddr_cast(addr),
&addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
#endif
if (connfd < 0) // 接受连接失败
{
int savedErrno = errno; // 保存 errno(避免日志调用覆盖)
LOG_SYSERR << "Socket::accept"; // 输出系统错误日志
// 分类处理错误码
switch (savedErrno)
{
// 预期错误:可重试/忽略,仅恢复 errno
case EAGAIN: // 非阻塞模式下暂无连接
case ECONNABORTED:// 连接被中断
case EINTR: // 系统调用被信号中断
case EPROTO: // 协议错误(可忽略)
case EPERM: // 权限不足(临时)
case EMFILE: // 进程打开文件数达到上限
errno = savedErrno;
break;
// 非预期错误:致命,终止程序
case EBADF: // 无效 fd
case EFAULT: // 地址空间无效
case EINVAL: // 参数无效(如未 listen)
case ENFILE: // 系统打开文件数达到上限
case ENOBUFS: // 无缓冲区可用
case ENOMEM: // 内存不足
case ENOTSOCK: // fd 不是 socket
case EOPNOTSUPP: // 操作不支持
LOG_FATAL << "unexpected error of ::accept " << savedErrno;
break;
// 未知错误:终止程序
default:
LOG_FATAL << "unknown error of ::accept " << savedErrno;
break;
}
}
return connfd;
}
/// 建立 socket 连接(客户端)
/// 地址长度固定为 sockaddr_in6 大小(兼容 IPv4/IPv6)
/// @param sockfd 已创建的 socket fd
/// @param addr 对等方地址(通用 sockaddr 类型)
/// @return 成功:0;失败:-1
int sockets::connect(int sockfd, const struct sockaddr* addr)
{
return ::connect(sockfd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
}
/// 从 socket 读取数据(封装 read 系统调用)
ssize_t sockets::read(int sockfd, void *buf, size_t count)
{
return ::read(sockfd, buf, count);
}
/// 分散读(封装 readv 系统调用)
ssize_t sockets::readv(int sockfd, const struct iovec *iov, int iovcnt)
{
return ::readv(sockfd, iov, iovcnt);
}
/// 向 socket 写入数据(封装 write 系统调用)
ssize_t sockets::write(int sockfd, const void *buf, size_t count)
{
return ::write(sockfd, buf, count);
}
/// 关闭 socket fd(失败则输出错误日志)
void sockets::close(int sockfd)
{
if (::close(sockfd) < 0)
{
LOG_SYSERR << "sockets::close";
}
}
/// 关闭 socket 写端(TCP 半关闭)
void sockets::shutdownWrite(int sockfd)
{
if (::shutdown(sockfd, SHUT_WR) < 0)
{
LOG_SYSERR << "sockets::shutdownWrite";
}
}
/// 将 socket 地址转换为「IP:端口」格式字符串(兼容 IPv4/IPv6)
/// IPv6 格式:[::1]:8080;IPv4 格式:127.0.0.1:8080
/// @param buf 输出缓冲区
/// @param size 缓冲区长度
/// @param addr 要转换的地址(通用 sockaddr 类型)
void sockets::toIpPort(char* buf, size_t size,
const struct sockaddr* addr)
{
if (addr->sa_family == AF_INET6) // IPv6 地址
{
buf[0] = '['; // IPv6 地址用 [] 包裹
toIp(buf+1, size-1, addr); // 先转换 IP 部分
size_t end = ::strlen(buf); // IP 部分的长度
const struct sockaddr_in6* addr6 = sockaddr_in6_cast(addr);
uint16_t port = sockets::networkToHost16(addr6->sin6_port); // 网络序转主机序
assert(size > end); // 确保缓冲区足够
snprintf(buf+end, size-end, "]:%u", port); // 拼接端口部分
return;
}
// IPv4 地址
toIp(buf, size, addr); // 先转换 IP 部分
size_t end = ::strlen(buf);
const struct sockaddr_in* addr4 = sockaddr_in_cast(addr);
uint16_t port = sockets::networkToHost16(addr4->sin_port);
assert(size > end);
snprintf(buf+end, size-end, ":%u", port); // 拼接端口部分
}
/// 将 socket 地址转换为 IP 字符串(仅 IP,不含端口)
/// @param buf 输出缓冲区
/// @param size 缓冲区长度
/// @param addr 要转换的地址(通用 sockaddr 类型)
void sockets::toIp(char* buf, size_t size,
const struct sockaddr* addr)
{
if (addr->sa_family == AF_INET) // IPv4
{
assert(size >= INET_ADDRSTRLEN); // 确保缓冲区足够(INET_ADDRSTRLEN=16)
const struct sockaddr_in* addr4 = sockaddr_in_cast(addr);
// 网络序二进制 IP → 点分十进制字符串
::inet_ntop(AF_INET, &addr4->sin_addr, buf, static_cast<socklen_t>(size));
}
else if (addr->sa_family == AF_INET6) // IPv6
{
assert(size >= INET6_ADDRSTRLEN); // 确保缓冲区足够(INET6_ADDRSTRLEN=46)
const struct sockaddr_in6* addr6 = sockaddr_in6_cast(addr);
// 网络序二进制 IP → 冒号分隔字符串
::inet_ntop(AF_INET6, &addr6->sin6_addr, buf, static_cast<socklen_t>(size));
}
}
/// 将「IP + 端口」转换为 IPv4 地址结构体(主机序 → 网络序)
/// @param ip 点分十进制 IPv4 字符串
/// @param port 主机序端口号
/// @param addr 输出参数,存储转换后的 sockaddr_in 结构体
void sockets::fromIpPort(const char* ip, uint16_t port,
struct sockaddr_in* addr)
{
addr->sin_family = AF_INET; // 设置地址族为 IPv4
addr->sin_port = hostToNetwork16(port); // 端口:主机序 → 网络序
// 点分十进制字符串 → 网络序二进制 IP
if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0)
{
LOG_SYSERR << "sockets::fromIpPort";
}
}
/// 将「IP + 端口」转换为 IPv6 地址结构体(主机序 → 网络序)
/// @param ip 冒号分隔 IPv6 字符串
/// @param port 主机序端口号
/// @param addr 输出参数,存储转换后的 sockaddr_in6 结构体
void sockets::fromIpPort(const char* ip, uint16_t port,
struct sockaddr_in6* addr)
{
addr->sin6_family = AF_INET6; // 设置地址族为 IPv6
addr->sin6_port = hostToNetwork16(port); // 端口:主机序 → 网络序
// 冒号分隔字符串 → 网络序二进制 IP
if (::inet_pton(AF_INET6, ip, &addr->sin6_addr) <= 0)
{
LOG_SYSERR << "sockets::fromIpPort";
}
}
/// 获取 socket 的错误状态(封装 getsockopt SO_ERROR)
/// 用于非阻塞 connect 等场景,检测连接是否成功
/// @param sockfd 要查询的 socket fd
/// @return 成功:socket 错误码(0=无错误);失败:errno
int sockets::getSocketError(int sockfd)
{
int optval;
socklen_t optlen = static_cast<socklen_t>(sizeof optval);
// 获取 SOL_SOCKET 层级的 SO_ERROR 选项(socket 错误状态)
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0)
{
return errno; // 获取失败,返回 errno
}
else
{
return optval; // 获取成功,返回 socket 错误码
}
}
/// 获取 socket 绑定的本地地址(封装 getsockname)
/// @param sockfd 已绑定/连接的 socket fd
/// @return 本地地址结构体(sockaddr_in6 兼容 IPv4/IPv6)
struct sockaddr_in6 sockets::getLocalAddr(int sockfd)
{
struct sockaddr_in6 localaddr;
memZero(&localaddr, sizeof localaddr); // 初始化结构体(清零)
socklen_t addrlen = static_cast<socklen_t>(sizeof localaddr);
// 获取 socket 绑定的本地地址
if (::getsockname(sockfd, sockaddr_cast(&localaddr), &addrlen) < 0)
{
LOG_SYSERR << "sockets::getLocalAddr";
}
return localaddr;
}
/// 获取 socket 对等方的地址(封装 getpeername)
/// @param sockfd 已连接的 socket fd
/// @return 对等方地址结构体(sockaddr_in6 兼容 IPv4/IPv6)
struct sockaddr_in6 sockets::getPeerAddr(int sockfd)
{
struct sockaddr_in6 peeraddr;
memZero(&peeraddr, sizeof peeraddr); // 初始化结构体(清零)
socklen_t addrlen = static_cast<socklen_t>(sizeof peeraddr);
// 获取 socket 对等方的地址
if (::getpeername(sockfd, sockaddr_cast(&peeraddr), &addrlen) < 0)
{
LOG_SYSERR << "sockets::getPeerAddr";
}
return peeraddr;
}
/// 检测 socket 是否发生自连接(客户端连接到自身)
/// 核心逻辑:对比本地地址和对等方地址的「IP + 端口」是否完全一致
/// @param sockfd 已连接的 socket fd
/// @return true=自连接;false=非自连接
bool sockets::isSelfConnect(int sockfd)
{
struct sockaddr_in6 localaddr = getLocalAddr(sockfd); // 本地地址
struct sockaddr_in6 peeraddr = getPeerAddr(sockfd); // 对等方地址
if (localaddr.sin6_family == AF_INET) // IPv4 地址
{
// 转换为 sockaddr_in 结构体(IPv4 兼容存储在 sockaddr_in6 中)
const struct sockaddr_in* laddr4 = reinterpret_cast<struct sockaddr_in*>(&localaddr);
const struct sockaddr_in* raddr4 = reinterpret_cast<struct sockaddr_in*>(&peeraddr);
// 对比端口和 IP 地址
return laddr4->sin_port == raddr4->sin_port
&& laddr4->sin_addr.s_addr == raddr4->sin_addr.s_addr;
}
else if (localaddr.sin6_family == AF_INET6) // IPv6 地址
{
// 对比端口和 IPv6 地址(memcmp 比较 16 字节的 sin6_addr)
return localaddr.sin6_port == peeraddr.sin6_port
&& memcmp(&localaddr.sin6_addr, &peeraddr.sin6_addr, sizeof localaddr.sin6_addr) == 0;
}
else // 未知地址族
{
return false;
}
}
1. 代码整体核心逻辑回顾
SocketsOps.cpp 是 SocketsOps.h 接口的具体实现,核心围绕 **"跨平台兼容 + 安全错误处理 + 高性能默认配置"** 展开:
- 编译条件适配:针对 VALGRIND(内存检测工具)或无 accept4 系统调用的环境,降级使用
accept + fcntl替代accept4,socket + fcntl替代带 flags 的socket; - 错误精细化处理:区分 "预期错误"(如 accept 的 EAGAIN)和 "非预期错误"(如 EBADF),预期错误仅重置 errno,非预期错误直接 FATAL 终止;
- 协议无关设计:所有地址操作基于 sockaddr_in6 封装,兼容 IPv4(自动映射),避免区分协议版本的冗余代码;
- 高性能配置:优先用系统调用 flags 直接设置非阻塞 /close-on-exec(减少 fcntl 调用),降级场景才用 fcntl 补充配置。
2. 逐模块拆解核心实现
1. 匿名命名空间:跨平台辅助函数
cpp
namespace
{
typedef struct sockaddr SA; // 简化类型名,兼容传统socket代码
// VALGRIND/NO_ACCEPT4场景下,手动设置非阻塞+close-on-exec(降级方案)
#if VALGRIND || defined (NO_ACCEPT4)
void setNonBlockAndCloseOnExec(int sockfd)
{
// 第一步:设置非阻塞(O_NONBLOCK)
int flags = ::fcntl(sockfd, F_GETFL, 0); // 获取当前flags
flags |= O_NONBLOCK; // 追加非阻塞标记
int ret = ::fcntl(sockfd, F_SETFL, flags);
// 第二步:设置close-on-exec(FD_CLOEXEC)
flags = ::fcntl(sockfd, F_GETFD, 0); // 获取文件描述符flags
flags |= FD_CLOEXEC; // 追加exec时关闭标记
ret = ::fcntl(sockfd, F_SETFD, flags);
(void)ret; // 抑制未使用变量警告(FIXME:可加错误检查)
}
#endif
} // namespace
核心背景:
- VALGRIND 场景:valgrind 对
socket/accept4直接带SOCK_NONBLOCK|SOCK_CLOEXEC的支持不佳,需通过fcntl手动设置; - NO_ACCEPT4 场景:部分老旧 Linux 内核 / 系统无
accept4系统调用,需先用accept获取 fd,再用fcntl配置属性; - 性能对比:
socket/accept4直接带 flags 只需 1 次系统调用,fcntl方案需 3 次(F_GETFL + F_SETFL + F_SETFD),优先用前者,降级用后者。
2. 地址类型转换:安全的类型转换封装
cpp
// sockaddr_in6 → sockaddr(const)
const struct sockaddr* sockets::sockaddr_cast(const struct sockaddr_in6* addr)
{
return static_cast<const struct sockaddr*>(implicit_cast<const void*>(addr));
}
// sockaddr_in6 → sockaddr(非const)
struct sockaddr* sockets::sockaddr_cast(struct sockaddr_in6* addr)
{
return static_cast<struct sockaddr*>(implicit_cast<void*>(addr));
}
// sockaddr_in → sockaddr(const)
const struct sockaddr* sockets::sockaddr_cast(const struct sockaddr_in* addr)
{
return static_cast<const struct sockaddr*>(implicit_cast<const void*>(addr));
}
// sockaddr → sockaddr_in(IPv4)
const struct sockaddr_in* sockets::sockaddr_in_cast(const struct sockaddr* addr)
{
return static_cast<const struct sockaddr_in*>(implicit_cast<const void*>(addr));
}
// sockaddr → sockaddr_in6(IPv6)
const struct sockaddr_in6* sockets::sockaddr_in6_cast(const struct sockaddr* addr)
{
return static_cast<const struct sockaddr_in6*>(implicit_cast<const void*>(addr));
}
关键细节:
implicit_cast:Muduo 自定义的安全类型转换(仅允许隐式转换,禁止危险的强制转换),比reinterpret_cast更安全,避免类型错误;- 统一转换接口:上层无需手动写
static_cast/reinterpret_cast,减少类型转换错误(如 IPv6 地址误转为 IPv4)。
3. 非阻塞 fd 创建:高性能 + 降级适配
cpp
int sockets::createNonblockingOrDie(sa_family_t family)
{
#if VALGRIND // valgrind场景:降级用socket + fcntl
int sockfd = ::socket(family, SOCK_STREAM, IPPROTO_TCP);
if (sockfd < 0)
{
LOG_SYSFATAL << "sockets::createNonblockingOrDie"; // 致命错误,终止进程
}
setNonBlockAndCloseOnExec(sockfd); // 手动设置非阻塞+close-on-exec
#else // 正常场景:直接带flags,1次系统调用搞定
int sockfd = ::socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
if (sockfd < 0)
{
LOG_SYSFATAL << "sockets::createNonblockingOrDie";
}
#endif
return sockfd;
}
核心设计:
- 正常场景优先用
SOCK_NONBLOCK | SOCK_CLOEXEC:这是 Linux 2.6.27+ 支持的 flags,直接在socket调用中设置属性,比fcntl少 2 次系统调用,性能更高; - 致命错误处理:
LOG_SYSFATAL会打印系统错误信息并调用abort()------socket 创建失败是系统级致命错误,无法恢复,必须终止进程。
4. bind/listen:致命错误直接终止
cpp
void sockets::bindOrDie(int sockfd, const struct sockaddr* addr)
{
// addrlen用sockaddr_in6的大小:兼容IPv4/IPv6(IPv4地址会自动填充到sockaddr_in6)
int ret = ::bind(sockfd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
if (ret < 0)
{
LOG_SYSFATAL << "sockets::bindOrDie"; // 绑定失败(如端口被占),致命错误
}
}
void sockets::listenOrDie(int sockfd)
{
// backlog设为SOMAXCONN:由OS决定最大监听队列长度(通常128/1024)
int ret = ::listen(sockfd, SOMAXCONN);
if (ret < 0)
{
LOG_SYSFATAL << "sockets::listenOrDie"; // 监听失败,致命错误
}
}
关键细节:
bind的addrlen用sockaddr_in6大小:无论传入的是 IPv4 还是 IPv6 地址,都能兼容,避免区分协议版本;listen的backlog设为SOMAXCONN:不硬编码数值,由操作系统根据内核配置决定,适配不同系统。
5. accept:跨平台 + 精细化错误处理(核心)
cpp
int sockets::accept(int sockfd, struct sockaddr_in6* addr)
{
socklen_t addrlen = static_cast<socklen_t>(sizeof *addr);
#if VALGRIND || defined (NO_ACCEPT4) // 降级场景:accept + fcntl
int connfd = ::accept(sockfd, sockaddr_cast(addr), &addrlen);
setNonBlockAndCloseOnExec(connfd); // 手动配置非阻塞+close-on-exec
#else // 正常场景:accept4直接设置属性
int connfd = ::accept4(sockfd, sockaddr_cast(addr),
&addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
#endif
if (connfd < 0) // 接收失败,处理错误
{
int savedErrno = errno; // 保存errno(避免后续调用覆盖)
LOG_SYSERR << "Socket::accept"; // 打印系统错误日志
// 错误分类:预期错误 → 仅重置errno;非预期错误 → FATAL终止
switch (savedErrno)
{
// 预期错误:网络编程中常见,可忽略/重试
case EAGAIN: // 监听队列为空(非阻塞accept)
case ECONNABORTED:// 连接被对端终止
case EINTR: // 系统调用被信号中断
case EPROTO: // 协议错误(偶尔出现)
case EPERM: // 权限不足(临时)
case EMFILE: // 进程fd耗尽(可处理)
errno = savedErrno; // 重置errno,上层可感知
break;
// 非预期错误:属于程序逻辑/系统故障,无法恢复
case EBADF: // fd无效
case EFAULT: // 地址空间无效
case EINVAL: // listen未调用/参数错误
case ENFILE: // 系统fd耗尽
case ENOBUFS: // 系统缓冲区不足
case ENOMEM: // 内存不足
case ENOTSOCK: // fd不是socket
case EOPNOTSUPP: // 不支持该操作
LOG_FATAL << "unexpected error of ::accept " << savedErrno;
break;
// 未知错误:FATAL终止,避免静默失败
default:
LOG_FATAL << "unknown error of ::accept " << savedErrno;
break;
}
}
return connfd;
}
核心设计亮点:
- 错误分类处理 :
- 预期错误:是网络编程中正常现象(如非阻塞 accept 无连接时返回 EAGAIN),仅重置 errno,上层可根据 errno 重试 / 忽略;
- 非预期错误:属于程序 bug 或系统级故障(如 fd 不是 socket),直接
LOG_FATAL终止,避免错误扩散;
- 保存 errno :先保存
errno再打日志,避免日志函数覆盖 errno,保证上层能获取准确的错误码; - accept4 优势 :直接在 accept 时设置
SOCK_NONBLOCK | SOCK_CLOEXEC,比accept + fcntl少 2 次系统调用,提升连接接收效率。
6. 基础 IO / 管理操作:简单封装 + 错误日志
cpp
// connect:直接转发,返回错误码(客户端连接失败非致命)
int sockets::connect(int sockfd, const struct sockaddr* addr)
{
return ::connect(sockfd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
}
// read/readv/write:直接转发,返回值由上层处理(如EAGAIN)
ssize_t sockets::read(int sockfd, void *buf, size_t count) { return ::read(sockfd, buf, count); }
ssize_t sockets::readv(int sockfd, const struct iovec *iov, int iovcnt) { return ::readv(sockfd, iov, iovcnt); }
ssize_t sockets::write(int sockfd, const void *buf, size_t count) { return ::write(sockfd, buf, count); }
// close:失败打日志(不终止,fd关闭失败通常不影响核心逻辑)
void sockets::close(int sockfd)
{
if (::close(sockfd) < 0) { LOG_SYSERR << "sockets::close"; }
}
// shutdownWrite:关闭写端,失败打日志
void sockets::shutdownWrite(int sockfd)
{
if (::shutdown(sockfd, SHUT_WR) < 0) { LOG_SYSERR << "sockets::shutdownWrite"; }
}
核心逻辑:
- 无额外处理:read/write/connect 直接转发,因为这些操作的错误(如 EAGAIN、ECONNREFUSED)需要上层根据业务逻辑处理;
- close/shutdownWrite 失败仅打日志:fd 关闭失败通常是 "已关闭" 或 "无效 fd",属于轻微错误,无需终止进程。
7. 地址转换:协议无关 + 可读格式化
cpp
// 转换为 "ip:port" 字符串(IPv6 格式为 [ip]:port)
void sockets::toIpPort(char* buf, size_t size, const struct sockaddr* addr)
{
if (addr->sa_family == AF_INET6)
{
buf[0] = '['; // IPv6地址加[],符合RFC规范
toIp(buf+1, size-1, addr); // 先转换IP
size_t end = ::strlen(buf);
const struct sockaddr_in6* addr6 = sockaddr_in6_cast(addr);
uint16_t port = sockets::networkToHost16(addr6->sin6_port); // 网络序→主机序
snprintf(buf+end, size-end, "]:%u", port); // 拼接端口
return;
}
// IPv4:直接 ip:port
toIp(buf, size, addr);
size_t end = ::strlen(buf);
const struct sockaddr_in* addr4 = sockaddr_in_cast(addr);
uint16_t port = sockets::networkToHost16(addr4->sin_port);
snprintf(buf+end, size-end, ":%u", port);
}
// 仅转换为IP字符串
void sockets::toIp(char* buf, size_t size, const struct sockaddr* addr)
{
if (addr->sa_family == AF_INET) // IPv4
{
assert(size >= INET_ADDRSTRLEN); // 确保缓冲区足够
const struct sockaddr_in* addr4 = sockaddr_in_cast(addr);
::inet_ntop(AF_INET, &addr4->sin_addr, buf, static_cast<socklen_t>(size));
}
else if (addr->sa_family == AF_INET6) // IPv6
{
assert(size >= INET6_ADDRSTRLEN);
const struct sockaddr_in6* addr6 = sockaddr_in6_cast(addr);
::inet_ntop(AF_INET6, &addr6->sin6_addr, buf, static_cast<socklen_t>(size));
}
}
// 从 "ip+port" 构建 sockaddr_in(IPv4)
void sockets::fromIpPort(const char* ip, uint16_t port, struct sockaddr_in* addr)
{
addr->sin_family = AF_INET;
addr->sin_port = hostToNetwork16(port); // 主机序→网络序
if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0) // 字符串→网络序IP
{
LOG_SYSERR << "sockets::fromIpPort";
}
}
// 从 "ip+port" 构建 sockaddr_in6(IPv6)
void sockets::fromIpPort(const char* ip, uint16_t port, struct sockaddr_in6* addr)
{
addr->sin6_family = AF_INET6;
addr->sin6_port = hostToNetwork16(port);
if (::inet_pton(AF_INET6, ip, &addr->sin6_addr) <= 0)
{
LOG_SYSERR << "sockets::fromIpPort";
}
}
关键细节:
- 字节序转换:port 必须在主机序和网络序之间转换(
networkToHost16/hostToNetwork16),IP 地址由inet_ntop/inet_pton自动处理; - IPv6 格式:严格遵循
[ip]:port规范,避免地址解析错误; - 缓冲区校验:
assert确保缓冲区足够容纳 IP 字符串(INET_ADDRSTRLEN=16,INET6_ADDRSTRLEN=46)。
8. fd 信息查询:错误状态 + 地址获取
cpp
// 获取fd的错误状态(非阻塞connect的核心判断依据)
int sockets::getSocketError(int sockfd)
{
int optval;
socklen_t optlen = static_cast<socklen_t>(sizeof optval);
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0)
{
return errno; // 获取失败,返回当前errno
}
else
{
return optval; // 获取成功,返回SO_ERROR的值(0表示无错误)
}
}
// 获取本地地址(返回sockaddr_in6,兼容IPv4)
struct sockaddr_in6 sockets::getLocalAddr(int sockfd)
{
struct sockaddr_in6 localaddr;
memZero(&localaddr, sizeof localaddr); // 初始化内存
socklen_t addrlen = static_cast<socklen_t>(sizeof localaddr);
if (::getsockname(sockfd, sockaddr_cast(&localaddr), &addrlen) < 0)
{
LOG_SYSERR << "sockets::getLocalAddr";
}
return localaddr;
}
// 获取对端地址(返回sockaddr_in6,兼容IPv4)
struct sockaddr_in6 sockets::getPeerAddr(int sockfd)
{
struct sockaddr_in6 peeraddr;
memZero(&peeraddr, sizeof peeraddr);
socklen_t addrlen = static_cast<socklen_t>(sizeof peeraddr);
if (::getpeername(sockfd, sockaddr_cast(&peeraddr), &addrlen) < 0)
{
LOG_SYSERR << "sockets::getPeerAddr";
}
return peeraddr;
}
核心应用:
getSocketError:非阻塞 connect 后,通过该函数判断连接是否成功(返回 0 表示成功,否则为错误码如 ECONNREFUSED);getLocalAddr/getPeerAddr:返回 sockaddr_in6,上层无需区分 IPv4/IPv6,直接通过sa_family判断即可。
9. 自连接检测:避免无效连接
cpp
bool sockets::isSelfConnect(int sockfd)
{
struct sockaddr_in6 localaddr = getLocalAddr(sockfd);
struct sockaddr_in6 peeraddr = getPeerAddr(sockfd);
if (localaddr.sin6_family == AF_INET) // IPv4
{
// 转换为sockaddr_in(IPv4映射到IPv6的地址)
const struct sockaddr_in* laddr4 = reinterpret_cast<struct sockaddr_in*>(&localaddr);
const struct sockaddr_in* raddr4 = reinterpret_cast<struct sockaddr_in*>(&peeraddr);
// 对比端口和IP(网络序直接对比)
return laddr4->sin_port == raddr4->sin_port
&& laddr4->sin_addr.s_addr == raddr4->sin_addr.s_addr;
}
else if (localaddr.sin6_family == AF_INET6) // IPv6
{
// 对比端口和IP(IPv6地址用memcmp)
return localaddr.sin6_port == peeraddr.sin6_port
&& memcmp(&localaddr.sin6_addr, &peeraddr.sin6_addr, sizeof localaddr.sin6_addr) == 0;
}
else
{
return false;
}
}
核心场景:
- 自连接:客户端误连接到自身监听的端口(如配置错误),导致无效连接;
- 检测逻辑:对比本地和对端的 port + IP,完全一致则判定为自连接,上层可关闭该连接。
3. 核心设计亮点总结
| 设计点 | 解决的问题 | 价值 |
|---|---|---|
| 跨平台编译适配(VALGRIND/NO_ACCEPT4) | 不同环境下系统调用支持差异导致编译 / 运行错误 | 保证模块在老旧内核 / 调试工具环境下正常工作 |
| accept 错误分类处理 | 所有错误都终止 / 都忽略,导致程序崩溃 / 静默失败 | 区分预期 / 非预期错误,兼顾稳定性和灵活性 |
| 优先用系统调用 flags 配置 fd 属性 | fcntl 多次调用导致性能损耗 | 减少系统调用次数,提升 fd 配置效率 |
| 协议无关的地址操作 | 上层需区分 IPv4/IPv6,代码冗余 | 一套代码支持双协议,降低维护成本 |
| 可读的地址格式化(IPv6 [ip]:port) | IPv6 地址格式不规范导致解析错误 | 符合网络协议规范,方便日志 / 调试 |
| 自连接检测 | 无效自连接占用 fd 资源 | 提前关闭无效连接,提升服务稳定性 |
总结
- 跨平台兼容:通过编译条件适配不同环境(VALGRIND / 无 accept4),优先用高性能系统调用,降级场景用 fcntl 补充;
- 错误精细化处理:致命错误(socket/create bind/listen 失败)直接终止,预期错误(accept EAGAIN)仅重置 errno,轻微错误(close 失败)仅打日志;
- 高性能设计:优先用系统调用 flags 配置非阻塞 /close-on-exec,减少 fcntl 调用;
- 协议无关:所有地址操作基于 sockaddr_in6,兼容 IPv4,避免区分协议版本的冗余代码;
- 调试友好:地址格式化、错误日志、自连接检测,大幅降低网络问题定位成本。