48、SO_RCVBUF/SO_SNDBUF:接收/发送缓冲区大小设置、系统限制

说实话,缓冲区大小这个问题,我早年踩过不少坑。那时候刚做网络编程,总觉得缓冲区设大点肯定没错,结果被系统内核狠狠教育了一回。今天咱们就好好聊聊 SO_RCVBUFSO_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_maxwmem_max 通常是 212992 字节(约 208KB)。如果你设的值超过这个数,setsockopt() 不会报错,但内核会默默给你截断到最大值。

我的习惯:做高吞吐服务时,我会先调大系统限制。比如 sysctl -w net.core.rmem_max=8388608(8MB),然后再在代码里设缓冲区。这样内核才不会拦着你。

缓冲区大小对性能的影响

缓冲区太小,数据容易积压,导致发送端阻塞或接收端丢包。缓冲区太大,内存占用高,而且 TCP 的流量控制可能变得迟钝。

我记得有一次做视频流传输,默认缓冲区只有 64KB,结果在高码率下频繁出现发送超时。后来我把发送缓冲区调到 1MB,问题立刻解决了。但反过来,如果你做的是实时交互应用,缓冲区太大反而会增加延迟。

核心逻辑图

下面这张图展示了缓冲区设置的核心流程,我建议你仔细看看:

SO_RCVBUF / SO_SNDBUF 缓冲区设置核心流程 应用程序 setsockopt(SO_RCVBUF/SO_SNDBUF) 内核检查:是否超过 rmem_max/wmem_max? (系统限制:net.core.rmem_max / wmem_max) 未超过限制 内核按请求值设置(翻倍) 超过系统限制 内核截断到最大值 注意:实际值 = 请求值 × 2(内核自动翻倍)

实际项目中的建议

我个人习惯,做文件传输或视频流这类大吞吐场景,缓冲区设 256KB 到 1MB。做游戏或即时通讯这种低延迟场景,64KB 以内就够了。

还有一个坑:setsockopt() 必须在 connect()listen() 之前调用。我曾经在连接建立后才设缓冲区,结果发现根本没生效,查了半天文档才反应过来。

核心要点:

  • setsockopt() 设置,必须在连接前调用
  • 内核会把请求值翻倍,实际值可能比你设的大
  • 不能超过 rmem_max / wmem_max,超了会被截断
  • 高吞吐设大缓冲区,低延迟设小缓冲区
  • getsockopt() 验证实际值,别想当然

嗯,缓冲区设置这块,说白了就是跟内核讨价还价。你提需求,内核看心情给。理解了这层关系,后面调优就顺了。

公众号:蓝海资料掘金营,微信deep3321