1.套接字选项概览
Linux 提供三级设置接口:
c
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
-
level(级别)SOL_SOCKET:通用套接字层IPPROTO_TCP:TCP层IPPROTO_IP:IP层IPPROTO_IPV6、SOL_IP
-
级别
0(SOL_SOCKET)之外,还有 文件级别 的socket选项可用fcntl / ioctl控制(如O_NONBLOCK、TCP_CORK之外的O_DIRECT等)。
2.核心套接字选项讲解
2.1.SOL_SOCKET 级常用选项
| 选项 | 类型 | 含义 |
|---|---|---|
SO_REUSEADDR |
int | 允许绑定 TIME_WAIT 状态的地址,避免"Address already in use"。服务器必设 |
SO_REUSEPORT |
int | 允许多个进程/线程绑定同一端口,内核做负载均衡(内核 3.9+),性能利器 |
SO_KEEPALIVE |
int | 开启 TCP 保活探测 |
SO_RCVBUF / SO_SNDBUF |
int | 接收/发送缓冲区大小(内核实际设为值 ×2,见下文) |
SO_RCVLOWAT / SO_SNDLOWAT |
int | 触发 select/poll 可读/可写的低水位 |
SO_RCVTIMEO / SO_SNDTIMEO |
struct timeval | 收发超时 |
SO_LINGER |
struct linger | 关闭行为:是否等待数据发送完 |
SO_BROADCAST |
int | 允许发送广播报文(UDP) |
SO_OOBINLINE |
int | 带外数据改为普通数据内联读取 |
SO_ERROR |
int (只读) | 获取并清除套接字上的 pending error(非阻塞 connect 必备) |
2.2.IPPROTO_TCP 级选项
| 选项 | 含义 |
|---|---|
TCP_NODELAY |
关闭 Nagle 算法,小包立即发出(延迟敏感服务必设) |
TCP_MAXSEG (MSS) |
控制最大报文段 |
TCP_KEEPIDLE / TCP_KEEPINTVL / TCP_KEEPCNT |
保活参数:空闲时间、探测间隔、探测次数 |
TCP_CORK |
强制内核聚合小数据,像"写合并"(与 TCP_NODELAY 相反,吞吐场景使用) |
TCP_DEFER_ACCEPT |
accept 前等待数据到达,减少空连接(防护 DoS) |
TCP_FASTOPEN |
TFO,握手时携带数据,省 1 个 RTT |
TCP_QUICKACK |
打开/关闭 quickack 模式(防止延迟 ACK 与对方糊涂窗口) |
TCP_INFO |
获取连接详细统计(RTT、重传等) |
2.3.IPPROTO_IP 级选项
IP_TOS:服务类型(如设置IPTOS_LOWDELAY)IP_MTU_DISCOVER:路径MTU发现IP_MULTICAST_*:组播相关IP_PKTINFO:获取/设置数据包的目的地址与接口
2.4.UDP 常用选项
SO_REUSEPORT:多线程UDP收包SO_RCVTIMEO:避免recvfrom永久阻塞IP_MTU_DISCOVER:UDP大包不分片控制
3.典型代码模板
c
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
int rcvbuf = 4 * 1024 * 1024;
setsockopt(listenfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
setsockopt(listenfd, SOL_SOCKET, SO_SNDBUF, &rcvbuf, sizeof(rcvbuf));
int nodelay = 1;
setsockopt(listenfd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
/* 保活参数 */
int idle = 60, intvl = 10, cnt = 3;
setsockopt(listenfd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
setsockopt(listenfd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl));
setsockopt(listenfd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));
/* 非阻塞 */
int flags = fcntl(listenfd, F_GETFL, 0);
fcntl(listenfd, F_SETFL, flags | O_NONBLOCK);
bind(listenfd, ...);
listen(listenfd, 511);
注意:listen(listenfd, 511) 的 backlog 在内核 ≥2.2 中实际生效上限受 somaxconn(默认 511/4096)约束。
4.缓冲区设置的"坑"
- 内核会加倍:调用
setsockopt(SO_SNDBUF, 1MB)后,getsockopt返回约2MB。因为内核为校验(clamping)和封装开销额外预留。若net.ipv4.tcp_wmem中设置了max,实际值还会被限制。 - 最大传输窗口
BDP:缓冲区至少应 ≥ 带宽时延积(BDP):- 千兆网 +
RTT 30ms:BDP ≈ 125 MB/s × 0.03s ≈ 3.75MB - 万兆 +
RTT 10ms:BDP ≈ 12.5MB
- 千兆网 +
- 单连接场景:仅设置
SO_SNDBUF/RCVBUF即可;但内核还会用tcp_wmem/tcp_rmem的autotuning动态调整窗口,通常在关闭autotuning或设置static时才完全受控。 - 读写独立:缓冲区大小是每个方向的。
c
/* 查看实际生效值 */
int actual;
socklen_t len = sizeof(actual);
getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &actual, &len); // ≈ 2 × 设置值
5.性能优化核心策略
5.1.I/O 模型选择
| 模型 | 适用场景 |
|---|---|
| 阻塞 + 线程池 | 连接数少、逻辑复杂 |
非阻塞 + select/poll |
少量连接 |
非阻塞 + epoll |
高并发主流(水平触发/边缘触发) |
io_uring |
极高并发、批量 I/O(内核 5.1+,零系统调用批量提交) |
epoll 关键点:
ET(边缘触发)模式必须循环读到EAGAIN,配合非阻塞 I/O- 使用
EPOLLONESHOT避免多线程惊群竞争(或依赖 EPOLLEXCLUSIVE) SO_REUSEPORT多线程accept实现负载均衡(内核 3.9+)
5.2.系统级调优(/etc/sysctl.conf)
c
# 文件描述符与端口范围
fs.file-max = 2097152
net.ipv4.ip_local_port_range = 1024 65535
# TCP 连接队列
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_abort_on_overflow = 0
# 缓冲区(BDP 对齐)
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
# 连接复用与回收
net.ipv4.tcp_tw_reuse = 1 # TIME_WAIT 快速复用(客户端)
net.ipv4.tcp_fin_timeout = 15
# 注意:tcp_tw_recycle 在 4.10+ 已废弃(NAT 场景有害)
# 拥塞控制与重传
net.ipv4.tcp_congestion_control = bbr # 高带宽/高丢包优于 cubic
net.ipv4.tcp_no_metrics_save = 0
net.ipv4.tcp_slow_start_after_idle = 0 # 防止空闲后慢启动重来
# 与 SO_REUSEPORT 相关
net.ipv4.tcp_early_demux = 1
启用 BBR:
c
modprobe tcp_bbr
echo "tcp_bbr" >> /etc/modules-load.d/bbr.conf
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.core.default_qdisc=fq
5.3.应用层优化技巧
- 减少系统调用:
writev/sendmsg合并写;开启TCP_CORK(Linux 特有)把多个小write聚合为一个MSS分段发送,避免Nagle+延迟ACK的40ms等待。 - 批量收发:
recvmsg/sendmsg + mmsg(recvmmsg/sendmmsg,一次调用处理多个报文)。 - 零拷贝:
sendfile():文件 →socket直传(零拷贝)splice():pipe间零拷贝mmap + write- 注意:
sendfile不支持TLS;splice对socket支持有限
- 内存池:避免
malloc/free抖动,自定义固定大小buffer池。 - 减少上下文切换:
- 每连接一个协程(如
Go、C++协程库),用同步编程模型获得异步性能 CPU亲和性SO_INCOMING_CPU / pthread_setaffinity / irqbalance- 关闭
IRQ绑定到单核
- 每连接一个协程(如
- 定时器设计:用 时间轮(
timing wheel) 代替每连接一个定时器(如timerfd+epoll或epoll_wait的超时参数)。 - 应用层协议:消息头固定长度 + 长度字段,粘包/拆包自己处理;考虑使用
protobuf/FlatBuffers减少序列化CPU。 Keep-alivevs 长连接:HTTP场景用Connection:keep-alive+TCP_KEEPALIVE;TCP_DEFER_ACCEPT减少空握手。
5.4.UDP 高并发优化
SO_REUSEPORT+ 多线程- 开启
net.core.netdev_max_backlog(默认1000→ 调大) - 增大
net.core.rmem_max防丢包 - 应用层实现可靠传输(确认、重传、拥塞控制)------可考虑
QUIC(用户态协议,用UDP + TLS 1.3)
5.5.故障排查工具
| 工具 | 用途 |
|---|---|
ss -s / ss -ti |
连接状态、TCP 详情(RTT、重传、拥塞窗口) |
netstat -s |
协议统计(丢包、重传) |
sar -n TCP,ETCP 1 |
重传率、主动/被动连接速率 |
lsof -p |
文件描述符占用 |
strace -e trace=network |
系统调用跟踪 |
tcpdump + Wireshark |
包级分析 |
perf / ebpf |
CPU/内核态分析 |
查看丢包关键指标:
c
# 内核接收丢包(应用读得太慢导致 socket buffer 满)
nstat -az | grep -E "TcpExtListenDrops|ListenOverflows"
# 网卡 ring buffer 丢包
ethtool -S eth0 | grep -i drop
6.常见性能瓶颈自查清单
| 现象 | 可能原因 | 对策 |
|---|---|---|
accept 延迟/失败 |
backlog 过小、somaxconn 限制 |
调整 backlog + tcp_max_syn_backlog |
小包延迟 40ms |
Nagle 算法 + 延迟 ACK |
TCP_NODELAY |
| 大文件传输慢 | socket buffer < BDP |
增大 tcp_rmem/wmem,开启 BBR |
CPU 高(软中断) |
单核处理网络栈 | RSS 多队列、SO_REUSEPORT、RPS/RFS |
| 连接数上不去 | 端口耗尽 | ip_local_port_range、短连接改长连接 |
大量 TIME_WAIT |
主动关闭方 | tcp_tw_reuse(客户端)、连接池 |
CLOSE_WAIT 堆积 |
应用未 close |
代码检查资源泄漏 |
UDP 丢包 |
buffer 满、netdev_max_backlog 小 |
调大 buffer、应用层重传 |
7.实践建议(演进路线)
- 入门:阻塞
IO+ 线程池 +SO_REUSEADDR+TCP_NODELAY - 进阶:
epoll ET+ 非阻塞 + 时间轮 + 零拷贝(sendfile) - 高性能:
io_uring + BBR + SO_REUSEPORT多核扩展 + 内核旁路(可选DPDK/QUIC) - 云原生:考虑
eBPF做内核层负载均衡(如Cilium的SO_REUSEPORT eBPF版本)
核心公式:吞吐量 = min(带宽, 窗口大小 / RTT)。性能优化的本质是在不丢包的前提下把有效窗口推向 BDP,并减少每字节 CPU 消耗(批量、零拷贝、协程)。