1.套接字选项概览
Linux 提供三级设置接口:
1int setsockopt(int sockfd, int level, int optname, 2 const void *optval, socklen_t optlen); 3int getsockopt(int sockfd, int level, int optname, 4 void *optval, socklen_t *optlen); 5
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.典型代码模板
1int listenfd = socket(AF_INET, SOCK_STREAM, 0); 2 3int on = 1; 4setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 5setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)); 6 7int rcvbuf = 4 * 1024 * 1024; 8setsockopt(listenfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); 9setsockopt(listenfd, SOL_SOCKET, SO_SNDBUF, &rcvbuf, sizeof(rcvbuf)); 10 11int nodelay = 1; 12setsockopt(listenfd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)); 13 14/* 保活参数 */ 15int idle = 60, intvl = 10, cnt = 3; 16setsockopt(listenfd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)); 17setsockopt(listenfd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl)); 18setsockopt(listenfd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt)); 19 20/* 非阻塞 */ 21int flags = fcntl(listenfd, F_GETFL, 0); 22fcntl(listenfd, F_SETFL, flags | O_NONBLOCK); 23 24bind(listenfd, ...); 25listen(listenfd, 511); 26
注意: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时才完全受控。 - 读写独立:缓冲区大小是每个方向的。
1/* 查看实际生效值 */ 2int actual; 3socklen_t len = sizeof(actual); 4getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &actual, &len); // ≈ 2 × 设置值 5
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)
1# 文件描述符与端口范围 2fs.file-max = 2097152 3net.ipv4.ip_local_port_range = 1024 65535 4 5# TCP 连接队列 6net.core.somaxconn = 65535 7net.ipv4.tcp_max_syn_backlog = 65535 8net.ipv4.tcp_abort_on_overflow = 0 9 10# 缓冲区(BDP 对齐) 11net.core.rmem_max = 134217728 12net.core.wmem_max = 134217728 13net.ipv4.tcp_rmem = 4096 87380 134217728 14net.ipv4.tcp_wmem = 4096 65536 134217728 15 16# 连接复用与回收 17net.ipv4.tcp_tw_reuse = 1 # TIME_WAIT 快速复用(客户端) 18net.ipv4.tcp_fin_timeout = 15 19# 注意:tcp_tw_recycle 在 4.10+ 已废弃(NAT 场景有害) 20 21# 拥塞控制与重传 22net.ipv4.tcp_congestion_control = bbr # 高带宽/高丢包优于 cubic 23net.ipv4.tcp_no_metrics_save = 0 24net.ipv4.tcp_slow_start_after_idle = 0 # 防止空闲后慢启动重来 25 26# 与 SO_REUSEPORT 相关 27net.ipv4.tcp_early_demux = 1 28
启用 BBR:
1modprobe tcp_bbr 2echo "tcp_bbr" >> /etc/modules-load.d/bbr.conf 3sysctl -w net.ipv4.tcp_congestion_control=bbr 4sysctl -w net.core.default_qdisc=fq 5
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/内核态分析 |
查看丢包关键指标:
1# 内核接收丢包(应用读得太慢导致 socket buffer 满) 2nstat -az | grep -E "TcpExtListenDrops|ListenOverflows" 3# 网卡 ring buffer 丢包 4ethtool -S eth0 | grep -i drop 5
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 消耗(批量、零拷贝、协程)。
《Linux 网络编程--套接字选项》 是转载文章,点击查看原文。