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

目录

[一、 Socket.h](#一、 Socket.h)

[1. Socket 类的整体定位与核心设计目标](#1. Socket 类的整体定位与核心设计目标)

[2. 核心成员变量解析](#2. 核心成员变量解析)

[3. 核心接口逐模块拆解](#3. 核心接口逐模块拆解)

[1. 构造 / 析构:RAII 管理 fd(核心)](#1. 构造 / 析构:RAII 管理 fd(核心))

[2. 基础接口:获取 fd / 查询 TCP 状态](#2. 基础接口:获取 fd / 查询 TCP 状态)

[3. 核心 socket 操作:bind/listen/accept](#3. 核心 socket 操作:bind/listen/accept)

[4. 连接管理:关闭写端](#4. 连接管理:关闭写端)

[5. socket 选项配置(高性能网络编程必备)](#5. socket 选项配置(高性能网络编程必备))

[4. 设计亮点总结](#4. 设计亮点总结)

[二、 Socket.cc](#二、 Socket.cc)

[1. 代码整体核心逻辑回顾](#1. 代码整体核心逻辑回顾)

[2. 逐函数拆解核心实现](#2. 逐函数拆解核心实现)

[1. 析构函数:RAII 核心落地](#1. 析构函数:RAII 核心落地)

[2. TCP 状态查询:调试 / 监控核心](#2. TCP 状态查询:调试 / 监控核心)

[3. bind/listen:监听 socket 核心操作](#3. bind/listen:监听 socket 核心操作)

[4. accept:接收连接(适配事件驱动模型)](#4. accept:接收连接(适配事件驱动模型))

[5. shutdownWrite:TCP 优雅关闭](#5. shutdownWrite:TCP 优雅关闭)

[6. socket 选项配置:高性能网络编程核心](#6. socket 选项配置:高性能网络编程核心)

[1. setTcpNoDelay:禁用 Nagle 算法](#1. setTcpNoDelay:禁用 Nagle 算法)

[2. setReuseAddr:端口复用](#2. setReuseAddr:端口复用)

[3. setReusePort:多进程 / 线程共享端口(跨平台兼容)](#3. setReusePort:多进程 / 线程共享端口(跨平台兼容))

[4. setKeepAlive:TCP 保活](#4. setKeepAlive:TCP 保活)

[3. 核心设计亮点总结](#3. 核心设计亮点总结)

总结


一、 Socket.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_SOCKET_H
#define MUDUO_NET_SOCKET_H

#include "muduo/base/noncopyable.h"  // 不可拷贝基类

// struct tcp_info 定义在 <netinet/tcp.h> 中(存储 TCP 连接的详细状态)
struct tcp_info;

namespace muduo
{
///
/// TCP 网络编程相关
///
namespace net
{

class InetAddress; // 前向声明:封装 IP 地址和端口的类

///
/// Socket 文件描述符的封装类
///
/// 核心特性:1. 析构时自动关闭持有的 socket fd;
/// 2. 线程安全(所有操作均委托给操作系统内核处理,无内部状态竞争);
/// 3. 非拷贝(禁止拷贝 fd 避免重复关闭/使用);
/// 4. 提供 TCP socket 核心操作的封装接口
class Socket : noncopyable
{
 public:
  /// 构造函数:封装一个已创建的 socket fd
  /// @param sockfd 已通过 socket() 系统调用创建的文件描述符
  explicit Socket(int sockfd)
    : sockfd_(sockfd)
  { }

  // Socket(Socket&&) // C++11 移动构造函数(注释预留,支持 fd 所有权转移)
  /// 析构函数:自动关闭持有的 socket fd(避免资源泄漏)
  ~Socket();

  /// 获取封装的 socket 文件描述符
  int fd() const { return sockfd_; }
  
  /// 获取 TCP 连接的详细状态(填充到 tcp_info 结构体)
  /// @param info 指向 tcp_info 结构体的指针,用于存储状态
  /// @return 成功返回 true,失败返回 false
  bool getTcpInfo(struct tcp_info*) const;
  
  /// 获取 TCP 连接状态的字符串形式(便于日志/调试)
  /// @param buf 存储字符串的缓冲区
  /// @param len 缓冲区长度
  /// @return 成功返回 true,失败返回 false
  bool getTcpInfoString(char* buf, int len) const;

  /// 绑定本地地址到 socket(服务端)
  /// 若地址已被占用,直接终止程序(abort)
  /// @param localaddr 要绑定的本地地址(InetAddress 封装)
  void bindAddress(const InetAddress& localaddr);
  
  /// 监听 socket(服务端,进入被动连接状态)
  /// 若监听失败(如地址已被占用),直接终止程序(abort)
  void listen();

  /// 接受一个客户端连接(服务端)
  /// 特性:
  /// 1. 成功:返回新的已连接 socket fd(已设为非阻塞 + close-on-exec),并填充 peeraddr;
  /// 2. 失败:返回 -1,peeraddr 不做任何修改;
  /// @param peeraddr 输出参数,用于存储客户端的地址信息
  /// @return 成功:新的连接 fd;失败:-1
  int accept(InetAddress* peeraddr);

  /// 关闭 socket 的写端(TCP 半关闭)
  /// 核心作用:向对等方发送 FIN 包,表明本端不再发送数据,但仍可接收数据
  void shutdownWrite();

  ///
  /// 启用/禁用 TCP_NODELAY 选项(禁用/启用 Nagle 算法)
  /// 说明:启用 TCP_NODELAY 会禁用 Nagle 算法,减少小数据包的延迟(适合低延迟场景)
  /// @param on true=启用 TCP_NODELAY;false=禁用
  ///
  void setTcpNoDelay(bool on);

  ///
  /// 启用/禁用 SO_REUSEADDR 选项
  /// 说明:允许端口释放后立即被重用(解决 TIME_WAIT 状态导致的端口占用问题)
  /// @param on true=启用;false=禁用
  ///
  void setReuseAddr(bool on);

  ///
  /// 启用/禁用 SO_REUSEPORT 选项
  /// 说明:允许多个进程/线程绑定到同一个端口(负载均衡场景常用)
  /// @param on true=启用;false=禁用
  ///
  void setReusePort(bool on);

  ///
  /// 启用/禁用 SO_KEEPALIVE 选项
  /// 说明:启用后,内核会定期检测 TCP 连接是否存活(防止无效连接长期占用资源)
  /// @param on true=启用;false=禁用
  ///
  void setKeepAlive(bool on);

 private:
  const int sockfd_;  // 封装的 socket 文件描述符(const:生命周期内不可修改)
};

}  // namespace net
}  // namespace muduo

#endif  // MUDUO_NET_SOCKET_H

1. Socket 类的整体定位与核心设计目标

Socket 是 Muduo 对原生 socket fd 的轻量级封装,核心定位是:

  • RAII 资源管理:唯一持有 socket fd,析构时自动关闭 fd,彻底避免 "忘记 close (fd)" 导致的文件描述符泄漏;
  • 操作封装 :将原生的 bind/listen/accept/shutdown 等 socket 系统调用封装为面向对象的接口,简化上层使用,统一错误处理;
  • 选项配置 :封装高性能网络编程必备的 socket 选项(如 TCP_NODELAY、SO_REUSEADDR 等),避免上层直接调用 setsockopt 出错;
  • 极简设计:仅做 "操作转发",不持有额外状态(所有 socket 状态由操作系统管理),线程安全(所有操作委托给 OS,OS 保证 socket 操作的原子性)。

在 Muduo 网络层中,Socket 是 TcpServer/TcpClient/Acceptor 的核心依赖:

  • Acceptor 持有监听 socket(listen fd),通过 Socket::listen/accept 实现连接监听 / 接收;
  • TcpConnection 持有通信 socket(conn fd),通过 Socket 配置选项、关闭写端等。

2. 核心成员变量解析

cpp 复制代码
private:
  const int sockfd_; // 封装的socket fd(const:一旦创建不可修改,一个Socket仅绑定一个fd)

关键设计点

  • sockfd_ 是 const:确保一个 Socket 对象始终绑定同一个 fd,避免中途修改导致 fd 管理混乱;
  • 无其他成员变量:所有 socket 状态(如绑定的地址、监听状态、选项配置)均由操作系统管理,Socket 仅做 "操作代理",极简设计减少维护成本。

3. 核心接口逐模块拆解

1. 构造 / 析构:RAII 管理 fd(核心)
cpp 复制代码
// 构造函数:接收已创建的socket fd(fd由SocketOps::createNonblockingOrDie创建)
explicit Socket(int sockfd) : sockfd_(sockfd) { }

// 析构函数:自动关闭fd,避免资源泄漏
~Socket();

RAII 核心价值

  • 上层只需创建 Socket 对象并传入 fd,无需手动调用 close(fd)------ 对象析构时自动关闭,彻底避免 fd 泄漏(网络编程中最常见的资源泄漏问题之一);
  • fd 由上层(如 SocketOps::createNonblockingOrDie)创建,Socket 仅负责 "管理" 而非 "创建",解耦 fd 创建与操作。
2. 基础接口:获取 fd / 查询 TCP 状态
cpp 复制代码
// 获取封装的fd(供Channel/EventLoop等上层组件使用)
int fd() const { return sockfd_; }

// 获取TCP连接的详细信息(如拥塞状态、重传次数等),用于调试/监控
bool getTcpInfo(struct tcp_info*) const;
// 将TCP信息转换为字符串,方便日志输出
bool getTcpInfoString(char* buf, int len) const;

应用场景

  • getTcpInfo 常用于网络监控(如统计连接的 RTT、重传率);
  • getTcpInfoString 用于调试日志,快速查看连接的 TCP 状态。
3. 核心 socket 操作:bind/listen/accept
cpp 复制代码
// 绑定本地地址(失败则abort,因为监听socket绑定失败属于严重错误)
void bindAddress(const InetAddress& localaddr);

// 开始监听(失败则abort)
void listen();

/// 接受连接(核心接口)
/// 成功:返回新的通信fd(已设置为非阻塞+close-on-exec),*peeraddr填充对端地址;
/// 失败:返回-1,*peeraddr不修改;
int accept(InetAddress* peeraddr);

关键设计点

  • bind/listen 失败时 abort:监听 socket 的 bind/listen 失败属于不可恢复的严重错误(如端口被占用),直接终止进程,避免静默失败;
  • accept 设计适配事件驱动模型:
    • 返回的新 fd 已设置为非阻塞(适配 epoll 的边缘触发 / 水平触发);
    • 设置为close-on-exec(避免 fork 后子进程继承 fd 导致泄漏);
    • 错误返回 - 1,不修改 peeraddr:上层可根据返回值区分成功 / 失败,无需额外判断。
4. 连接管理:关闭写端
cpp 复制代码
// 关闭socket的写端(TCP半关闭)
void shutdownWrite();

核心语义

  • 调用 shutdown(SHUT_WR),向对端发送 FIN 包,表明本端不再发送数据;
  • 区别于 close(fd)shutdownWrite 仅关闭写端,读端仍可接收数据;close 会关闭读写端,且若 fd 引用计数 > 1(如 fork 后),不会立即发送 FIN 包;
  • 应用场景:TcpConnection 断开连接时,先 shutdownWrite,再等待对端关闭读端,实现优雅关闭。
5. socket 选项配置(高性能网络编程必备)
cpp 复制代码
/// 启用/禁用TCP_NODELAY(禁用/启用Nagle算法)
void setTcpNoDelay(bool on);

/// 启用/禁用SO_REUSEADDR(允许端口复用)
void setReuseAddr(bool on);

/// 启用/禁用SO_REUSEPORT(允许多进程/线程绑定同一端口)
void setReusePort(bool on);

/// 启用/禁用SO_KEEPALIVE(TCP保活)
void setKeepAlive(bool on);

每个选项的核心作用

选项 作用 应用场景
TCP_NODELAY 禁用 Nagle 算法(立即发送小数据包,不延迟合并) 低延迟场景(如即时通信、游戏),避免小数据包等待 ACK 导致延迟;
SO_REUSEADDR 允许 TIME_WAIT 状态的端口被重新绑定 服务器重启时,快速复用之前的端口(避免 TIME_WAIT 占用端口);
SO_REUSEPORT 允许多个进程 / 线程绑定同一端口(OS 分发连接) 多核服务器,多个工作进程监听同一端口,OS 均衡分发连接,提升并发;
SO_KEEPALIVE 检测无效连接(如对端崩溃未关闭连接) 长连接场景,自动关闭长时间无数据的无效连接;

4. 设计亮点总结

设计点 核心价值 网络编程应用
RAII 管理 fd 析构自动关闭 fd,避免资源泄漏 所有 socket fd 的生命周期由 Socket 对象管理,无需手动 close;
封装系统调用 简化 bind/listen/accept 等操作,避免上层直接调用原生系统调用出错 上层只需调用 Socket 的成员函数,无需处理原生系统调用的参数 / 返回值;
accept 返回非阻塞 + close-on-exec 的 fd 适配 Muduo 的事件驱动模型(epoll + 非阻塞 IO) 新连接的 fd 可直接交给 Channel 管理,无需额外配置;
封装高性能 socket 选项 一键配置 TCP_NODELAY/REUSEADDR 等,适配高性能场景 服务器可根据需求快速配置 socket 选项,无需手动调用 setsockopt;
线程安全 所有操作委托给 OS,OS 保证 socket 操作的原子性 多线程可安全调用 Socket 的接口(如配置选项、shutdown);

二、 Socket.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/Socket.h"

#include "muduo/base/Logging.h"       // 日志库(LOG_SYSERR/LOG_ERROR 等)
#include "muduo/net/InetAddress.h"    // 地址封装类
#include "muduo/net/SocketsOps.h"     // socket 系统调用封装(close/bind/listen 等)

#include <netinet/in.h>               // sockaddr_in/sockaddr_in6 定义
#include <netinet/tcp.h>              // TCP_INFO/TCP_NODELAY 等定义
#include <stdio.h>  // snprintf(格式化字符串)

using namespace muduo;
using namespace muduo::net;

/// 析构函数:关闭封装的 socket fd(释放资源)
/// 调用 sockets::close 而非直接 close:封装了错误处理和 FD_CLOEXEC 检查
Socket::~Socket()
{
  sockets::close(sockfd_);
}

/// 获取 TCP 连接的详细状态(填充 tcp_info 结构体)
/// 底层调用 getsockopt 获取内核维护的 TCP 连接状态
/// @param tcpi 输出参数,指向 tcp_info 结构体的指针
/// @return 成功返回 true,失败返回 false
bool Socket::getTcpInfo(struct tcp_info* tcpi) const
{
  socklen_t len = sizeof(*tcpi);     // 结构体长度
  memZero(tcpi, len);                // 初始化结构体(清零)
  // 调用 getsockopt:获取 SOL_TCP 层级的 TCP_INFO 选项
  // 参数:fd | 协议层级 | 选项名 | 输出缓冲区 | 缓冲区长度
  return ::getsockopt(sockfd_, SOL_TCP, TCP_INFO, tcpi, &len) == 0;
}

/// 获取 TCP 连接状态的格式化字符串(便于日志/调试)
/// 先调用 getTcpInfo 获取状态,再将核心字段格式化为可读字符串
/// @param buf 存储字符串的缓冲区
/// @param len 缓冲区长度
/// @return 成功返回 true,失败返回 false
bool Socket::getTcpInfoString(char* buf, int len) const
{
  struct tcp_info tcpi;
  bool ok = getTcpInfo(&tcpi);       // 获取 TCP 状态
  if (ok)                            // 获取成功则格式化
  {
    snprintf(buf, len, "unrecovered=%u "             // 未恢复的超时重传次数
             "rto=%u ato=%u snd_mss=%u rcv_mss=%u "  // 重传超时/预测时钟/发送/接收MSS
             "lost=%u retrans=%u rtt=%u rttvar=%u "  // 丢失包/重传包/往返时间/偏差
             "sshthresh=%u cwnd=%u total_retrans=%u",// 慢启动阈值/拥塞窗口/总重传数
             tcpi.tcpi_retransmits,  // 未恢复的 [RTO] 超时次数
             tcpi.tcpi_rto,          // 重传超时时间(微秒)
             tcpi.tcpi_ato,          // 软时钟预测滴答数(微秒)
             tcpi.tcpi_snd_mss,      // 发送最大分段大小(MSS)
             tcpi.tcpi_rcv_mss,      // 接收最大分段大小(MSS)
             tcpi.tcpi_lost,         // 丢失的数据包数量
             tcpi.tcpi_retrans,      // 已重传的数据包数量
             tcpi.tcpi_rtt,          // 平滑往返时间(微秒)
             tcpi.tcpi_rttvar,       // 往返时间偏差
             tcpi.tcpi_snd_ssthresh, // 发送慢启动阈值
             tcpi.tcpi_snd_cwnd,     // 发送拥塞窗口大小
             tcpi.tcpi_total_retrans);  // 整个连接的总重传次数
  }
  return ok;
}

/// 绑定本地地址到 socket(服务端)
/// 底层调用 sockets::bindOrDie:绑定失败则终止程序(abort)
/// @param addr 要绑定的本地地址(InetAddress 封装,兼容 IPv4/IPv6)
void Socket::bindAddress(const InetAddress& addr)
{
  sockets::bindOrDie(sockfd_, addr.getSockAddr());
}

/// 监听 socket(服务端,进入被动连接状态)
/// 底层调用 sockets::listenOrDie:监听失败则终止程序(abort)
void Socket::listen()
{
  sockets::listenOrDie(sockfd_);
}

/// 接受一个客户端连接(服务端)
/// 底层调用 sockets::accept:封装了 accept4,返回非阻塞+close-on-exec 的 fd
/// 兼容 IPv6:使用 sockaddr_in6 存储客户端地址
/// @param peeraddr 输出参数,存储客户端的地址信息(InetAddress 封装)
/// @return 成功:新的连接 fd;失败:-1
int Socket::accept(InetAddress* peeraddr)
{
  struct sockaddr_in6 addr;         // 存储客户端地址(sockaddr_in6 兼容 IPv4/IPv6)
  memZero(&addr, sizeof addr);      // 初始化地址结构体(清零)
  // 调用 sockets::accept:接收连接,返回的 connfd 已设为非阻塞 + close-on-exec
  int connfd = sockets::accept(sockfd_, &addr);
  if (connfd >= 0)                  // 接收成功
  {
    // 将客户端地址(sockaddr_in6)设置到 peeraddr 中(自动兼容 IPv4)
    peeraddr->setSockAddrInet6(addr);
  }
  return connfd;
}

/// 关闭 socket 的写端(TCP 半关闭)
/// 底层调用 sockets::shutdownWrite:封装 shutdown(fd, SHUT_WR)
void Socket::shutdownWrite()
{
  sockets::shutdownWrite(sockfd_);
}

/// 启用/禁用 TCP_NODELAY 选项(禁用/启用 Nagle 算法)
/// @param on true=启用(禁用 Nagle);false=禁用(启用 Nagle)
void Socket::setTcpNoDelay(bool on)
{
  int optval = on ? 1 : 0;          // 选项值:1=启用,0=禁用
  // 调用 setsockopt 设置 TCP_NODELAY
  // 参数:fd | 协议层级(IPPROTO_TCP) | 选项名 | 选项值 | 值长度
  ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY,
               &optval, static_cast<socklen_t>(sizeof optval));
  // FIXME:此处未检查返回值,实际项目中可添加 CHECK 确保设置成功
}

/// 启用/禁用 SO_REUSEADDR 选项
/// @param on true=启用;false=禁用
void Socket::setReuseAddr(bool on)
{
  int optval = on ? 1 : 0;          // 选项值:1=启用,0=禁用
  // 调用 setsockopt 设置 SO_REUSEADDR
  // 参数:fd | 协议层级(SOL_SOCKET) | 选项名 | 选项值 | 值长度
  ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR,
               &optval, static_cast<socklen_t>(sizeof optval));
  // FIXME:此处未检查返回值,实际项目中可添加 CHECK 确保设置成功
}

/// 启用/禁用 SO_REUSEPORT 选项
/// 兼容处理:仅在定义 SO_REUSEPORT 的系统中生效(Linux 3.9+ 支持)
/// @param on true=启用;false=禁用
void Socket::setReusePort(bool on)
{
#ifdef SO_REUSEPORT  // 系统支持 SO_REUSEPORT
  int optval = on ? 1 : 0;          // 选项值:1=启用,0=禁用
  // 调用 setsockopt 设置 SO_REUSEPORT
  int ret = ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT,
                         &optval, static_cast<socklen_t>(sizeof optval));
  // 启用失败时输出系统错误日志
  if (ret < 0 && on)
  {
    LOG_SYSERR << "SO_REUSEPORT failed.";
  }
#else  // 系统不支持 SO_REUSEPORT
  if (on)  // 尝试启用则输出错误日志
  {
    LOG_ERROR << "SO_REUSEPORT is not supported.";
  }
#endif
}

/// 启用/禁用 SO_KEEPALIVE 选项(TCP 保活)
/// @param on true=启用;false=禁用
void Socket::setKeepAlive(bool on)
{
  int optval = on ? 1 : 0;          // 选项值:1=启用,0=禁用
  // 调用 setsockopt 设置 SO_KEEPALIVE
  ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE,
               &optval, static_cast<socklen_t>(sizeof optval));
  // FIXME:此处未检查返回值,实际项目中可添加 CHECK 确保设置成功
}

1. 代码整体核心逻辑回顾

Socket.cppSocket 类的具体实现,核心是 **"轻量级封装 + 底层调用转发"**:

  • 所有 socket 核心操作(bind/listen/accept/shutdown)均转发给 SocketsOps 模块(封装原生系统调用,处理错误重试 / 参数校验);
  • 选项配置直接调用 setsockopt,针对跨平台特性(如 SO_REUSEPORT)做条件编译;
  • 调试友好:将 TCP 内部状态(如丢包数、RTT)转换为可读字符串,方便定位网络问题;
  • 严格的 RAII:析构必关闭 fd,且所有接口适配 Muduo 的事件驱动模型(如 accept 返回非阻塞 fd)。

2. 逐函数拆解核心实现

1. 析构函数:RAII 核心落地
cpp 复制代码
Socket::~Socket()
{
  sockets::close(sockfd_); // 调用封装的close,而非原生close
}

关键细节

  • 调用 sockets::close 而非原生 ::closeSocketsOps::close 会处理系统调用的 "可重试错误"(如 EINTR),并保证 fd 被正确关闭(避免重复关闭);
  • 析构必关 fd:无论上层是否显式调用 shutdown,析构都会关闭 fd,彻底杜绝文件描述符泄漏 ------ 这是 RAII 机制在 socket 管理中的核心价值。
2. TCP 状态查询:调试 / 监控核心
cpp 复制代码
// 获取TCP连接的底层状态(如丢包、重传、RTT等)
bool Socket::getTcpInfo(struct tcp_info* tcpi) const
{
  socklen_t len = sizeof(*tcpi);
  memZero(tcpi, len); // 初始化内存,避免野值
  // 调用getsockopt获取TCP_INFO(Linux特有,封装TCP连接的详细状态)
  return ::getsockopt(sockfd_, SOL_TCP, TCP_INFO, tcpi, &len) == 0;
}

// 将TCP状态转换为可读字符串(方便日志输出)
bool Socket::getTcpInfoString(char* buf, int len) const
{
  struct tcp_info tcpi;
  bool ok = getTcpInfo(&tcpi);
  if (ok)
  {
    // 格式化输出核心TCP状态字段,覆盖重传、超时、MSS、丢包等关键指标
    snprintf(buf, len, "unrecovered=%u "
             "rto=%u ato=%u snd_mss=%u rcv_mss=%u "
             "lost=%u retrans=%u rtt=%u rttvar=%u "
             "sshthresh=%u cwnd=%u total_retrans=%u",
             tcpi.tcpi_retransmits,  // 未恢复的RTO超时次数
             tcpi.tcpi_rto,          // 重传超时时间(微秒)
             tcpi.tcpi_ato,          // 软时钟预测tick(微秒)
             tcpi.tcpi_snd_mss,      // 发送MSS(最大分段大小)
             tcpi.tcpi_rcv_mss,      // 接收MSS
             tcpi.tcpi_lost,         // 丢失的数据包数
             tcpi.tcpi_retrans,      // 重传的数据包数
             tcpi.tcpi_rtt,          // 平滑RTT(微秒)
             tcpi.tcpi_rttvar,       // RTT偏差
             tcpi.tcpi_snd_ssthresh, // 慢启动阈值
             tcpi.tcpi_snd_cwnd,     // 拥塞窗口大小
             tcpi.tcpi_total_retrans); // 连接全程重传数
  }
  return ok;
}

核心价值

  • 这些接口是网络调试 / 监控的 "利器":比如服务器出现丢包时,可通过 getTcpInfoString 快速查看丢包数、重传次数、RTT 等,定位是网络问题还是 TCP 拥塞;
  • 仅依赖 Linux 内核的 TCP_INFO 选项,是高性能网络服务必备的调试手段。
3. bind/listen:监听 socket 核心操作
cpp 复制代码
void Socket::bindAddress(const InetAddress& addr)
{
  // 调用SocketsOps::bindOrDie:封装::bind,失败则abort(监听socket绑定失败是严重错误)
  sockets::bindOrDie(sockfd_, addr.getSockAddr());
}

void Socket::listen()
{
  // 调用SocketsOps::listenOrDie:封装::listen,失败则abort
  sockets::listenOrDie(sockfd_);
}

关键细节

  • bindOrDie/listenOrDie 会处理 ::bind/::listen 的错误(如 EADDRINUSE),并调用 abort() 终止进程 ------ 因为监听 socket 的 bind/listen 失败(如端口被占用)属于不可恢复的致命错误,无需静默处理;
  • addr.getSockAddr() 会返回 sockaddr*(兼容 IPv4/IPv6),保证 bind 操作的协议无关性。
4. accept:接收连接(适配事件驱动模型)
cpp 复制代码
int Socket::accept(InetAddress* peeraddr)
{
  struct sockaddr_in6 addr; // 用sockaddr_in6兼容IPv4/IPv6(IPv4地址会自动映射)
  memZero(&addr, sizeof addr); // 初始化地址结构体
  // 调用SocketsOps::accept:封装accept4(设置SOCK_NONBLOCK | SOCK_CLOEXEC)
  int connfd = sockets::accept(sockfd_, &addr);
  if (connfd >= 0)
  {
    // 成功接收连接:填充对端地址到peeraddr
    peeraddr->setSockAddrInet6(addr);
  }
  return connfd;
}

核心设计亮点

  • IPv6 兼容 :用 sockaddr_in6 而非 sockaddr_in,保证 Socket 同时支持 IPv4/IPv6(Linux 下 IPv4 地址会自动转换为 IPv6 的兼容格式);
  • 高性能配置sockets::accept 底层调用 accept4,直接将新 fd 设置为 SOCK_NONBLOCK(非阻塞)+ SOCK_CLOEXEC(exec 时关闭),无需额外调用 fcntl,减少系统调用次数;
  • 错误处理 :失败返回 -1,不修改 peeraddr,上层可直接根据返回值判断是否成功(如 Acceptor 中若返回 -1,会忽略该次事件)。
5. shutdownWrite:TCP 优雅关闭
cpp 复制代码
void Socket::shutdownWrite()
{
  // 调用SocketsOps::shutdownWrite:封装::shutdown(sockfd_, SHUT_WR)
  sockets::shutdownWrite(sockfd_);
}

核心语义

  • 关闭 socket 的写端,向对端发送 FIN 包,表明本端不再发送数据;
  • 区别于 closeshutdownWrite 仅关闭写端(读端仍可接收数据),且不受 fd 引用计数影响(即使 fd 被多线程共享,也会立即发送 FIN);
  • 应用场景:TcpConnection 断开连接时,先调用 shutdownWrite,再等待对端关闭读端,实现 "半关闭",避免数据丢失。'
6. socket 选项配置:高性能网络编程核心
1. setTcpNoDelay:禁用 Nagle 算法
cpp 复制代码
void Socket::setTcpNoDelay(bool on)
{
  int optval = on ? 1 : 0;
  // 底层调用setsockopt:IPPROTO_TCP层 + TCP_NODELAY选项
  ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY,
               &optval, static_cast<socklen_t>(sizeof optval));
  // FIXME CHECK:注释表明可添加错误检查(如返回-1时打日志)
}

核心作用

  • 禁用 Nagle 算法:小数据包会立即发送,不等待 ACK 或合并数据包,降低延迟(代价是增加网络包数量);
  • 适用场景:即时通信、游戏、金融行情等低延迟场景(Muduo 默认启用)。
2. setReuseAddr:端口复用
cpp 复制代码
void Socket::setReuseAddr(bool on)
{
  int optval = on ? 1 : 0;
  // SOL_SOCKET层 + SO_REUSEADDR选项
  ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR,
               &optval, static_cast<socklen_t>(sizeof optval));
  // FIXME CHECK
}

核心作用

  • 允许绑定处于 TIME_WAIT 状态的端口:服务器重启时,无需等待 TIME_WAIT 超时(默认 2MSL),可快速复用端口;
  • 注意:SO_REUSEADDR 不保证多进程绑定同一端口(需 SO_REUSEPORT)。
3. setReusePort:多进程 / 线程共享端口(跨平台兼容)
cpp 复制代码
void Socket::setReusePort(bool on)
{
#ifdef SO_REUSEPORT // 条件编译:仅支持SO_REUSEPORT的系统(Linux 3.9+)
  int optval = on ? 1 : 0;
  int ret = ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT,
                         &optval, static_cast<socklen_t>(sizeof optval));
  if (ret < 0 && on)
  {
    LOG_SYSERR << "SO_REUSEPORT failed."; // 失败打系统错误日志
  }
#else
  if (on)
  {
    LOG_ERROR << "SO_REUSEPORT is not supported."; // 不支持则打错误日志
  }
#endif
}

核心设计

  • 跨平台兼容SO_REUSEPORT 是 Linux 3.9+ 新增特性,其他系统(如旧 Linux、BSD)可能不支持,通过 #ifdef 避免编译错误;
  • 核心价值:允许多个进程 / 线程绑定同一端口,OS 会将新连接均衡分发给不同进程,提升多核服务器的并发能力。
4. setKeepAlive:TCP 保活
cpp 复制代码
void Socket::setKeepAlive(bool on)
{
  int optval = on ? 1 : 0;
  // SOL_SOCKET层 + SO_KEEPALIVE选项
  ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE,
               &optval, static_cast<socklen_t>(sizeof optval));
  // FIXME CHECK
}

核心作用

  • 启用 TCP 保活机制:OS 会定期发送保活包,检测对端是否存活(如对端崩溃 / 网络中断未关闭连接);
  • 适用场景:长连接服务(如网关、IM),避免大量无效连接占用 fd。

3. 核心设计亮点总结

设计点 解决的问题 性能 / 兼容性提升
底层依赖 SocketsOps 直接调用原生系统调用易出错(如 EINTR 重试、参数错误) 所有系统调用封装在 SocketsOps,Socket 仅做接口层,解耦平台相关逻辑;
IPv6 兼容(sockaddr_in6) 单独处理 IPv4/IPv6 导致代码冗余 一套代码支持 IPv4/IPv6,适配未来网络协议升级;
accept4 直接设置非阻塞 /close-on-exec 先 accept 再 fcntl 设置选项,多一次系统调用 减少系统调用次数,提升连接接收效率;
SO_REUSEPORT 条件编译 跨平台编译错误,不支持的系统崩溃 兼容不同 Linux 内核版本,无侵入式适配;
TCP_INFO 状态查询 无法直观查看 TCP 内部状态(如丢包、RTT) 调试时快速定位网络问题,无需抓包;
RAII 严格关 fd fd 泄漏导致系统资源耗尽 析构必关 fd,彻底杜绝文件描述符泄漏;

总结

  1. RAII 落地 :析构调用 sockets::close,确保 socket fd 不泄漏,是资源管理的核心保障;
  2. 底层解耦 :所有系统调用委托给 SocketsOps,Socket 仅做轻量级接口封装,降低维护成本;
  3. 协议 / 平台兼容 :accept 用 sockaddr_in6 适配 IPv6,SO_REUSEPORT 做条件编译,兼容不同系统;
  4. 高性能配置:封装 TCP_NODELAY/REUSEADDR 等核心选项,一键配置高性能 socket 参数;
  5. 调试友好:TCP 状态查询与字符串转换,快速定位网络问题(如丢包、重传、RTT 异常)。
相关推荐
阿猿收手吧!2 小时前
【C++】模板偏特化与std::move深度解析
服务器·c++
问好眼2 小时前
【信息学奥赛一本通】1296:开餐馆
c++·算法·动态规划·信息学奥赛
Qt学视觉2 小时前
3D3-PCL全面总结
c++·opencv·3d
愚者游世3 小时前
力扣解决二进制&题型常用知识点梳理
c++·程序人生·算法·leetcode·职场和发展·改行学it
逆龙泰氽4 小时前
位运算和进制
c++
Laurence4 小时前
从零到一构建 C++ 项目(IDE / 命令行双轨实现)
前端·c++·ide
我在人间贩卖青春4 小时前
cout语句和cin语句
c++·cin·输入输出流·cout
Jiu-yuan4 小时前
C++文件操作
c++