Linux C/C++ 学习日记(19):实现一个简易的百万并发服务器(二):配置参数,解决fd数量和内存的限制
注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。
在上一期博文中Linux C/C++ 学习日记(16):实现一个简易的百万并发服务器(一):Linux中万物皆文件-CSDN博客
我们了解到了linux万物皆文件,知道了每创建一个连接都需要一个分配一个文件描述符fd。
而想要建立百万个连接,就需要一百万个fd。我们都知道linux的文件描述符肯定是有限的(受内存等影响),因此我们必须了解一下系统允许分配的fd数量,并进行相关的配置。同时每一个连接都会占用系统内存,我们必须解决百万个连接内存必须足够的问题。在这一期博文中我会跟大家一一讲解的!
一、解决fd的数量限制
1.nofile:进程级文件描述符(fd)限制配置
作用:限制单个进程可打开的 fd 数量(默认 1024)。
1.1可以用ulimit -n的命令查看当前进程的fd限制

1.2 修改noflie
1.2.1 临时修改
使用ulimit -n 1048576 (需要root权限)

重启后回复默认值
1.2.2 永久修改
sudo vi /etc/security/limits.conf
在末尾添加
* soft nofile 1048576 # 软限制(可临时超过,有警告)
* hard nofile 1048576 # 硬限制(不可超过,由root设置)


配置完后,再次查看

配置成功
2.file-max:系统级 fd 总数限制
作用:限制整个系统所有进程的 fd 总和。
2.1 查看
cat /proc/sys/fs/file-max

2.2 修改
2.2.1 临时
sudo sysctl -w fs.file-max=1048576
2.2.2 永久
sudo vi /etc/sysctl.conf

3. nf_conntrack_max :网络连接的fd数量限制
作用:限制内核连接跟踪表(用于防火墙 / NAT)的最大条目数。(如果你关闭了防火墙,该参数就不用配置了)
简单点来讲。限制受防火墙监控的网络连接相关的文件描述符(TCP、UDP连接的socket的数目),非网络连接的 fd(如普通文件、管道、设备等)不受它的限制。
3.1 查看
3.1.1 查看nf_conntrack_max 的值
cat /proc/sys/net/netfilter/nf_conntrack_max
若模块未加载,先执行 sudo modprobe nf_conntrack(部分系统为 ip_conntrack)

可知我的是262144
3.1.2 查看当前监管的TCP使用数
cat /proc/sys/net/netfilter/nf_conntrack_count
若模块未加载,先执行 sudo modprobe nf_conntrack(部分系统为 ip_conntrack),每次重启系统都要执行

我的当前使用数为2,说明有两条受防火墙监管的连接
查看所有的tcp连接

有4条连接,说明有2条不受防火墙监管
关闭防火墙后再次检查(必须重启系统后再检查,否则追踪表里面会遗留原来的连接数据):
关闭防火墙

重新启动后检查发现为0

但是此时tcp连接为7。说明了nf_conntrack_max 限制的是受防火墙监管的tcp连接,如果关闭防火墙,该参数就不起作用了,也就是说不用配置了。

3.2 修改参数
3.2.1 临时
sudo sysctl -w net.netfilter.nf_conntrack_max = 1000000

3.2.2 永久
sudo vi /etc/sysctl.conf

sudo sysctl -p // 让配置生效
如果出现 No such file 的报错,需要执行sudo modprobe nf_conntrack(部分系统为 ip_conntrack)。如果有这种情况,配置不能永久生效,每次重启都需要执行sudo modprobe nf_conntrack和sudo sysctl -p (当然,仅限于nf_conntrack_max的配置不能永久生效,file_max还是永久生效的)

检查配置成功了。
二、解决内存配置的问题
1. TCP连接的内存组成
| 构成部分 | 作用 / 说明 | 相关限制参数 |
|---|---|---|
| 发送缓冲区(Send Buffer) | 存储应用程序待发送但尚未被网络层发送的数据(需等待对端确认后释放) | net.ipv4.tcp_wmem(单连接最大容量) |
| 接收缓冲区(Receive Buffer) | 存储从网络接收但尚未被应用程序读取的数据(应用读取慢时可能膨胀) | net.ipv4.tcp_rmem(单连接最大容量) |
| TCP 控制块与连接元数据 | 内核中struct sock结构体,存储连接状态(如ESTABLISHED)、窗口大小、序列号等元数据;高并发时(如 10 万连接)总消耗不可忽视 | 无特定参数,由连接数决定 |
| 连接跟踪表项(nf_conntrack) | 启用防火墙(如ufw/iptables时,记录连接状态(如NEW/ESTABLISHED)的跟踪条目 | net.nf_conntrack_max(总条目数上限) |
| 内核全局 TCP 内存统计与限制 | 监控所有 TCP 连接的总内存消耗,达到阈值时主动回收闲置连接的缓冲区内存,防止系统内存耗尽 | net.ipv4.tcp_mem(全局总内存阈值) |
由此由三个关键参数需要我们配置:
net.ipv4.tcp_wmem、net.ipv4.tcp_rmem、net.ipv4.tcp_mem
2. TCP内存重要参数的配置
2.1 配置
sudo vi /etc/sysctl.conf

sudo sysctl -p
2.2 参数的解释
| 参数名称 | 单位 | 数值及含义 | 作用 |
|---|---|---|---|
| net.ipv4.tcp_mem | 页(1 页≈4KB) | 1. 2. 524288页(2GB):压力阈值,达到此值内核开始 “温和” 回收内存 3. 786432页(3GB):最大内存阈值,接近 / 达到此值内核 “强制积极” 回收内存 | 系统级 TCP 总内存限制,监控所有 TCP 连接的总内存消耗,防止系统内存耗尽 |
| net.ipv4.tcp_wmem | 字节 | 1. 1024:单个连接发送缓冲区最小容量 2. 1024:新连接发送缓冲区默认容量 3. 2048:单个连接发送缓冲区最大容量(应用可请求扩容,不超过此值) | 限制单个 TCP 连接的发送缓冲区,暂存应用待发送、尚未被内核完全发送到网络的数据(应对网络延迟、丢包重传等) |
| net.ipv4.tcp_rmem | 字节 | 1. 1024:单个连接接收缓冲区最小容量 2. 1024:新连接接收缓冲区默认容量 3. 2048:单个连接接收缓冲区最大容量 | 限制单个 TCP 连接的接收缓冲区,暂存从网络收到、尚未被应用程序读取的数据(避免因应用处理慢导致数据丢失) |
附:单位转换表
| 单位层级 | 二进制换算(2^10 = 1024 倍,计算机内存 / 系统显示用) | 十进制换算(10^3 = 1000 倍,硬件厂商标注用) | 对应英文全称 |
|---|---|---|---|
| 1 KB(千字节) | 1 KB = 1024 B(字节) | 1 KB = 1000 B(字节) | Kilobyte |
| 1 MB(兆字节) | 1 MB = 1024 KB = 1024×1024 B ≈ 1048576 B | 1 MB = 1000 KB = 1000×1000 B = 1000000 B | Megabyte |
| 1 GB(吉字节) | 1 GB = 1024 MB = 1024×1024×1024 B ≈ 1073741824 B | 1 GB = 1000 MB = 1000×1000×1000 B = 1000000000 B | Gigabyte |
2.3 在TCP连接逐渐增多的过程中,各参数对应的值是如何变化的
| 阶段划分 | tcp_wmem/tcp_rmem 变化 | tcp_mem 变化 |
|---|---|---|
| 连接建立初期(总内存未超阈值) | 新连接分配默认值;数据需求大时,从默认值向最大值扩容(不超单连接最大值,且总内存未触阈值) | 总内存从 “最低阈值” 逐渐向 “压力阈值” 累积 |
| 总内存接近压力阈值(预警阶段) | 新连接缓冲区扩容幅度节流;已有连接扩容速度放缓 | 总内存进入 “预警区间”,内核开始 “温和回收” 闲置连接内存以缓解压力 |
| 总内存超过 high 阈值(强制限流阶段) | 已建连接发送缓冲区被强行收缩(远低于最大值);新连接仅分配最小值,且可能被拒绝 | 触发 “紧急模式”,通过拒绝新连接、丢弃数据包、强制收缩缓冲区,将总内存压降至 high 阈值以下 |
2.4 当tcp内存超过了tcp_mem的最大值时
| 分类 | 具体措施 / 影响 |
|---|---|
| 内核强制限流 | 1. 拒绝新 TCP 连接,内核日志报 2. 丢弃未确认数据包: -发送端丢弃 “已发未确认” 的数据报文 → 发送缓冲区中对应的内存被释放 -接收端丢弃 “已收未读取” 的数据包 → 接收缓冲区中对应的内存被释放。 3. 强制收缩已建连接发送缓冲区,限制发送速率 4.发送缓冲区:内核会强制收缩其大小,直接关联 5.接收缓冲区:内核也会通过 “延迟确认”“限制接收窗口” 等方式间接压缩内存,但相比发送缓冲区,主动收缩的优先级稍低(整体为了回收总内存)。 |
| 对系统 / 应用的影响 | 1. 新连接失败(客户端超时 / 无法连接),影响服务可用性 2. 现有连接卡顿(响应慢、传输速率下降) 3. 排查:可通过 |
2.5 需要建立百万连接,内存参数配置的选值参考
| 分类 | 具体内容 |
|---|---|
| 核心约束 | 1. 2. 单连接最小内存( |
| 额外内存预留 | 百万连接的struct sock(TCB 控制块)需预留 300-500MB |
| 优化建议 | 内存不足时,关闭nf_conntrack(net.netfilter.nf_conntrack_max=0),节省约 300MB 表项内存 |
| 总结 | 1. 用 3. 预留 TCB 控制块(储存元数据信息)、 |
(不清楚tcb、元数据是什么的,可以去看我之前的博文
Linux C/C++ 学习日记(16):TCP网络编程API的介绍(二):tcb的建立过程、元数据的介绍-CSDN博客
Linux C/C++ 学习日记(17):TCP网络编程API的介绍(三):tcb功能、send、recv、close-CSDN博客)
)
我的配置里设置连接单元最小值为2B、计算需要设置的tcp_mem:
| 内存类型 | 单连接占用 / 百万连接占用 | 100 万连接总占用(二进制换算) | 备注 |
|---|---|---|---|
| 基础缓冲区内存(tcp_wmem_min + tcp_rmem_min) | 2KB / 单连接 | 2KB × 1000000 = 2000000KB→ 2000000 ÷ 1024 ≈ 1953.125MB→ 1953.125 ÷ 1024 ≈ 1.907GB | 仅缓冲区内存,无突发流量预留 |
| TCB 控制块内存(struct sock) | 300-500MB / 百万连接 | 300MB ≈ 0.293GB500MB ≈ 0.488GB | 固定开销,需额外预留 |
| 防火墙追踪表(nf_conntrack) | 约 300MB / 百万连接 | 300MB ÷ 1024 ≈ 0.293GB | 仅启用防火墙连接跟踪时产生,禁用则无 |
| 总内存需求(启用 nf_conntrack) | - | 1.907GB + 0.293GB(TCB 下限) + 0.293GB(追踪表)≈ 2.493GB1.907GB + 0.488GB(TCB 上限) + 0.293GB(追踪表)≈ 2.688GB | 含缓冲区、TCB、防火墙追踪表 |
| 总内存需求(禁用 nf_conntrack) | - | 1.907GB + 0.293GB ≈ 2.2GB1.907GB + 0.488GB ≈ 2.4GB | 含缓冲区、TCB,无防火墙追踪表 |
得出大致配置(tcp_mem设置默认为2GB,最大值为3GB)

因此如果你是虚拟机运行的话,最好选择8GB的内存!!!!
三、总结
需要配置两个文件:
1.sudo vi /etc/security/limits.conf

2.sudo vi /etc/sysctl.conf

执行sudo sysctl -p(报错执行:sudo modprobe nf_conntrack)
3. 参数配置的协同关系
| 协同关系类别 | 核心配置逻辑(精简版) |
|---|---|
| 连接数与内存匹配 | nf_conntrack_max(如 100 万)需对应:tcp_mem最大值 ≥(单连接缓冲区总和 × 连接数 + TCB 内存),避免内存不足。 |
| fd 与连接数关联 | fs.file-max > nf_conntrack_max,预留其他文件的 fd 空间(每个 TCP 连接占用 1 个 fd)。 |
| 场景化配置原则 | 高并发小连接(微服务):保证 大流量传输(CDN):调大 |
| 通用原则 | 结合业务(并发量、传输量)和系统资源(内存、CPU)配置,避免盲目调大导致浪费或不稳定。 |
有人说明明就两张配置表的事情,为什么还需要了解那么多?
我的答案是希望大家能够明白参数为什么这么配置,只有了解底层原理了才能够学会自己去选择配置参数,以tcp内存其受读写缓存配置影响(在已知连接数为多少时),二读写缓存设置又跟大家实际需求有关。如果你明白各个参数的底层逻辑,你配起来就很有条理了!
4. 我测试代码遇到的:
(代码测试在下一篇博文)
4.1 运行到85万连接时,系统内存还剩下1G,但是系统却崩溃重启了。


原因:Remote side unexpectedly closed network connection:表示虚拟机系统崩溃直接重启。分析一下,是因为还剩下的1GB内存不足以支撑系统正常运作了。(我当时选择的虚拟机是4GB的内存)
解决方案:将虚拟机内存改为8GB
4.2 更正之后再次运行
系统接受到1020103时,不再接受了,系统也没有崩溃。这是因为文件描述符数量限制。

(下一篇博文我将分享测试代码给大家!)









