目录
[一、 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. 设计亮点总结)
[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.cpp 是 Socket 类的具体实现,核心是 **"轻量级封装 + 底层调用转发"**:
- 所有 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而非原生::close:SocketsOps::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 包,表明本端不再发送数据;
- 区别于
close:shutdownWrite仅关闭写端(读端仍可接收数据),且不受 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,彻底杜绝文件描述符泄漏; |
总结
- RAII 落地 :析构调用
sockets::close,确保 socket fd 不泄漏,是资源管理的核心保障; - 底层解耦 :所有系统调用委托给
SocketsOps,Socket 仅做轻量级接口封装,降低维护成本; - 协议 / 平台兼容 :accept 用
sockaddr_in6适配 IPv6,SO_REUSEPORT 做条件编译,兼容不同系统; - 高性能配置:封装 TCP_NODELAY/REUSEADDR 等核心选项,一键配置高性能 socket 参数;
- 调试友好:TCP 状态查询与字符串转换,快速定位网络问题(如丢包、重传、RTT 异常)。