48、SO_RCVBUF/SO_SNDBUF:接收/发送缓冲区大小设置、系统限制
说实话,缓冲区大小这个问题,我早年踩过不少坑。那时候刚做网络编程,总觉得缓冲区设大点肯定没错,结果被系统内核狠狠教育了一回。今天咱们就好好聊聊 SO_RCVBUF 和 SO_SNDBUF 这两个套接字选项。
缓冲区到底是个啥?
简单说,每个 TCP 套接字在内核里都有两个缓冲区:一个收,一个发。应用程序调用 send() 时,数据先拷贝到发送缓冲区,内核再慢慢发出去。接收端也一样,数据到了先放接收缓冲区,应用程序再 recv() 取走。
你想想看,如果没有缓冲区,应用程序和网络接口就得直接打交道,那效率得多低?我见过一个项目,就是因为没调好缓冲区,导致高并发下大量丢包,排查了好几天才找到根因。
怎么设置?
设置方法很简单,用 setsockopt() 就行。我贴一段代码,你一看就明白:
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
exit(1);
}
int rcvbuf = 65536; // 64KB
int sndbuf = 65536; // 64KB
if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
&rcvbuf, sizeof(rcvbuf)) < 0) {
perror("setsockopt SO_RCVBUF");
}
if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF,
&sndbuf, sizeof(sndbuf)) < 0) {
perror("setsockopt SO_SNDBUF");
}
// 验证一下实际设置的值
int actual_rcv, actual_snd;
socklen_t len = sizeof(int);
getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &actual_rcv, &len);
getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &actual_snd, &len);
printf("请求的接收缓冲区: %d\n", rcvbuf);
printf("实际的接收缓冲区: %d\n", actual_rcv);
printf("请求的发送缓冲区: %d\n", sndbuf);
printf("实际的发送缓冲区: %d\n", actual_snd);
close(sock);
return 0;
}
注意:你设的值和内核实际给的值,往往不一样。内核会把你设的值翻倍,然后再加上一些开销。比如你设 65536,内核可能给你 131072。这是历史原因造成的,很多老手第一次碰到也懵。
系统限制有哪些?
内核不会让你无限放大缓冲区。有两个关键限制:
| 参数 | 说明 | 查看方式 |
|---|---|---|
net.core.rmem_max |
接收缓冲区最大值 | sysctl net.core.rmem_max |
net.core.wmem_max |
发送缓冲区最大值 | sysctl net.core.wmem_max |
net.ipv4.tcp_rmem |
TCP 接收缓冲区自动调优范围 | sysctl net.ipv4.tcp_rmem |
net.ipv4.tcp_wmem |
TCP 发送缓冲区自动调优范围 | sysctl net.ipv4.tcp_wmem |
默认情况下,rmem_max 和 wmem_max 通常是 212992 字节(约 208KB)。如果你设的值超过这个数,setsockopt() 不会报错,但内核会默默给你截断到最大值。
我的习惯:做高吞吐服务时,我会先调大系统限制。比如 sysctl -w net.core.rmem_max=8388608(8MB),然后再在代码里设缓冲区。这样内核才不会拦着你。
缓冲区大小对性能的影响
缓冲区太小,数据容易积压,导致发送端阻塞或接收端丢包。缓冲区太大,内存占用高,而且 TCP 的流量控制可能变得迟钝。
我记得有一次做视频流传输,默认缓冲区只有 64KB,结果在高码率下频繁出现发送超时。后来我把发送缓冲区调到 1MB,问题立刻解决了。但反过来,如果你做的是实时交互应用,缓冲区太大反而会增加延迟。
核心逻辑图
下面这张图展示了缓冲区设置的核心流程,我建议你仔细看看:
实际项目中的建议
我个人习惯,做文件传输或视频流这类大吞吐场景,缓冲区设 256KB 到 1MB。做游戏或即时通讯这种低延迟场景,64KB 以内就够了。
还有一个坑:setsockopt() 必须在 connect() 或 listen() 之前调用。我曾经在连接建立后才设缓冲区,结果发现根本没生效,查了半天文档才反应过来。
核心要点:
- 用
setsockopt()设置,必须在连接前调用 - 内核会把请求值翻倍,实际值可能比你设的大
- 不能超过
rmem_max/wmem_max,超了会被截断 - 高吞吐设大缓冲区,低延迟设小缓冲区
- 用
getsockopt()验证实际值,别想当然
嗯,缓冲区设置这块,说白了就是跟内核讨价还价。你提需求,内核看心情给。理解了这层关系,后面调优就顺了。