解析muduo源码之 SocketsOps.h & SocketsOps.cc

目录

[一、 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 被正确关闭(即使多次调用也不会出错),避免原生 ::closeEINTR 导致关闭失败;
  • 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.cppSocketsOps.h 接口的具体实现,核心围绕 **"跨平台兼容 + 安全错误处理 + 高性能默认配置"** 展开:

  • 编译条件适配:针对 VALGRIND(内存检测工具)或无 accept4 系统调用的环境,降级使用 accept + fcntl 替代 accept4socket + 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"; // 监听失败,致命错误
  }
}

关键细节

  • bindaddrlensockaddr_in6 大小:无论传入的是 IPv4 还是 IPv6 地址,都能兼容,避免区分协议版本;
  • listenbacklog 设为 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 资源 提前关闭无效连接,提升服务稳定性

总结

  1. 跨平台兼容:通过编译条件适配不同环境(VALGRIND / 无 accept4),优先用高性能系统调用,降级场景用 fcntl 补充;
  2. 错误精细化处理:致命错误(socket/create bind/listen 失败)直接终止,预期错误(accept EAGAIN)仅重置 errno,轻微错误(close 失败)仅打日志;
  3. 高性能设计:优先用系统调用 flags 配置非阻塞 /close-on-exec,减少 fcntl 调用;
  4. 协议无关:所有地址操作基于 sockaddr_in6,兼容 IPv4,避免区分协议版本的冗余代码;
  5. 调试友好:地址格式化、错误日志、自连接检测,大幅降低网络问题定位成本。
相关推荐
ctyshr2 小时前
C++编译期数学计算
开发语言·c++·算法
努力写代码的熊大2 小时前
c++异常和智能指针
java·开发语言·c++
John_ToDebug2 小时前
WebContent 与 WebView:深入解析浏览器渲染架构的双层设计
c++·chrome·ui
千秋乐。2 小时前
C++-string
开发语言·c++
孞㐑¥2 小时前
算法—队列+宽搜(bfs)+堆
开发语言·c++·经验分享·笔记·算法
yufuu983 小时前
并行算法在STL中的应用
开发语言·c++·算法
charlie1145141913 小时前
嵌入式C++教程——ETL(Embedded Template Library)
开发语言·c++·笔记·学习·嵌入式·etl
陳10303 小时前
C++:AVL树的模拟实现
开发语言·c++
CSDN_RTKLIB3 小时前
错进错出得到正确的字节序列
c++