Socket 选项¶
是什么 / 解决什么问题¶
通过 setsockopt/getsockopt 控制 socket 的行为参数。覆盖最常用的选项及其实际影响。
使用模式¶
#include <sys/socket.h>
#include <netinet/tcp.h>
// 设置选项
int val = 1;
setsockopt(fd, level, optname, &val, sizeof(val));
// 获取选项
int val;
socklen_t len = sizeof(val);
getsockopt(fd, level, optname, &val, &len);
SOL_SOCKET 层(通用选项)¶
SO_REUSEADDR¶
作用: - 允许 bind 到处于 TIME_WAIT 的地址 - 允许 bind 到 0.0.0.0 即使已有 socket bind 到具体 IP 的同一端口
几乎所有服务器都应该设置。不设置的话,重启服务器可能报 EADDRINUSE。
SO_REUSEPORT¶
作用:允许多个 socket(多个进程/线程)bind 到完全相同的 address:port。内核对新连接做负载均衡。
线程1: socket() → bind(:8080) → listen() → accept()
线程2: socket() → bind(:8080) → listen() → accept()
线程3: socket() → bind(:8080) → listen() → accept()
↑ 内核自动分配新连接到不同线程
优势:消除共享 listen socket 的 accept 锁竞争。Nginx 1.9.1+ 支持。
SO_KEEPALIVE¶
开启 TCP keep-alive 探测。搭配 TCP_KEEPIDLE/INTVL/CNT 使用(见 TCP 行为篇)。
SO_LINGER¶
struct linger lg = {
.l_onoff = 1,
.l_linger = 0, // 秒数
};
setsockopt(fd, SOL_SOCKET, SO_LINGER, &lg, sizeof(lg));
控制 close() 的行为:
| l_onoff | l_linger | close() 行为 |
|---|---|---|
| 0 | (忽略) | 默认:后台发送缓冲区数据,立即返回 |
| 1 | 0 | 发送 RST,丢弃缓冲区,立即返回(硬关闭) |
| 1 | >0 | 阻塞等待数据发完或超时,超时则 RST |
l_linger=0 的用途:避免 TIME_WAIT(代价是对端可能丢数据)。
SO_SNDBUF / SO_RCVBUF¶
int size = 262144; // 256KB
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
// 注意:内核会将设置值翻倍(用于内部簿记开销)
// 设 256KB,实际分配 512KB
// 受 /proc/sys/net/core/rmem_max 和 wmem_max 限制
何时调整: - 高带宽长延迟链路(BDP = bandwidth * RTT) - 缓冲区 < BDP 时无法跑满带宽
SO_RCVTIMEO / SO_SNDTIMEO¶
struct timeval tv = { .tv_sec = 5, .tv_usec = 0 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
// 超时后 read/write 返回 -1, errno = EAGAIN/EWOULDBLOCK
注意:这不是 connect 超时。connect 超时需要非阻塞+epoll 或 TCP_USER_TIMEOUT。
IPPROTO_TCP 层(TCP 特定选项)¶
TCP_NODELAY¶
关闭 Nagle 算法。小数据立即发送,不等待凑够 MSS。
应该设置的场景: - 请求-响应协议(HTTP、RPC) - 实时应用(游戏、交互式终端) - 已经在应用层做了写合并
不该设置的场景: - 大量微小写入且不做合并(会产生大量小包)
TCP_CORK¶
int on = 1;
setsockopt(fd, IPPROTO_TCP, TCP_CORK, &on, sizeof(on));
// 此后的 write 全部缓冲,不发送
write(fd, header, header_len);
write(fd, body, body_len);
on = 0;
setsockopt(fd, IPPROTO_TCP, TCP_CORK, &on, sizeof(on));
// 取消 cork,一次性发出所有缓冲数据
类似 Nagle 但是手动控制。适合发送 HTTP 响应(header + body 合并为少量 TCP 段)。
TCP_QUICKACK¶
禁用 Delayed ACK,收到数据后立即回 ACK。注意:这不是持久设置,内核可能在后续操作中重新启用 Delayed ACK。需要在每次读取后重新设置。
TCP_USER_TIMEOUT¶
unsigned int timeout = 5000; // 5秒
setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout));
如果已发送数据在指定时间内未被确认,则放弃连接。比 keep-alive 更精确的死连接检测。
TCP_FASTOPEN¶
// 服务端:允许 TFO
int qlen = 128;
setsockopt(listen_fd, IPPROTO_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen));
// 客户端:connect 时发送数据(省一个 RTT)
sendto(fd, data, data_len, MSG_FASTOPEN,
(struct sockaddr *)&server, sizeof(server));
首次连接还是三次握手,后续连接可以在 SYN 中携带数据,减少一个 RTT。
TCP_CONGESTION¶
Per-socket 设置拥塞控制算法。
TCP_INFO¶
struct tcp_info info;
socklen_t len = sizeof(info);
getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &len);
printf("rtt: %u us\n", info.tcpi_rtt);
printf("cwnd: %u\n", info.tcpi_snd_cwnd);
printf("retrans: %u\n", info.tcpi_total_retrans);
printf("unacked: %u\n", info.tcpi_unacked);
获取连接的详细 TCP 统计信息。对性能诊断极有价值。
性能考量¶
服务器推荐配置¶
// 典型高性能 TCP 服务器的 socket 选项
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
// 对 accept 得到的连接 fd
setsockopt(conn_fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
// 可选:快速检测死连接
setsockopt(conn_fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));
int idle = 60, intvl = 10, cnt = 3;
setsockopt(conn_fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
setsockopt(conn_fd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl));
setsockopt(conn_fd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));
需要根据场景决定的选项¶
| 场景 | 推荐 |
|---|---|
| 低延迟 RPC | TCP_NODELAY + TCP_QUICKACK |
| 高吞吐文件传输 | 大 SO_SNDBUF/RCVBUF + TCP_CORK |
| 短连接 Web 服务 | SO_REUSEPORT + TCP_FASTOPEN |
| 跨 IDC 长肥管道 | 大 buffer + BBR |
常见陷阱与 FAQ¶
1. SO_SNDBUF 设置后实际是双倍¶
int size = 65536;
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, &len);
// size = 131072 (翻倍了)
// 这是正常的,内核保留一半用于内部管理
2. TCP_NODELAY 和 TCP_CORK 互斥¶
同时设置两个的行为未定义。通常选一个: - TCP_NODELAY:所有写立即发 - TCP_CORK:手动批量控制
3. SO_RCVTIMEO 对 epoll 无效¶
超时选项只对阻塞式 read/write 生效。用 epoll 时靠 epoll_wait 的 timeout 参数控制。
观测与调试¶
# 查看 socket 选项的当前值
python3 -c "
import socket
s = socket.socket()
s.connect(('localhost', 8080))
print('TCP_NODELAY:', s.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY))
print('SO_SNDBUF:', s.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF))
print('SO_RCVBUF:', s.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF))
"
# ss 显示连接参数
ss -tnpi dst :8080
# strace 看 setsockopt 调用
strace -e trace=setsockopt -f ./server
延伸阅读¶
man 7 socket— SOL_SOCKET 层选项man 7 tcp— IPPROTO_TCP 层选项man 7 ip— IPPROTO_IP 层选项/proc/sys/net/ipv4/tcp_*— 系统级 TCP 参数