【C/C++】C1000K 百万连接测试:Linux 参数、fd 限制和 epoll 压测客户端

1. 百万连接先卡在哪里
很多人一提到百万连接,第一反应就是 epoll。但真正测试时,epoll 往往不是第一个瓶颈。更常见的问题是:
- 进程 fd 数不够。
- 系统文件对象上限不够。
- TCP 读写缓冲占用太多内存。
- 客户端本地端口不够用。
- 经过 NAT、防火墙、Docker 网络时 conntrack 表不够。
README 中整理了一组面向长连接数量测试的参数。它的目标是让系统能维持大量连接,而不是追求高吞吐。
2. fd 限制:fs.file-max 和 ulimit 都要看
socket 也是 fd。百万连接意味着服务端进程至少要能打开百万级 fd。
系统级文件对象上限:
sudo sysctl -w fs.file-max=1048576
进程级 fd 上限:
ulimit -n 1048576
如果 ulimit -n 没改,即使 fs.file-max 很大,单个进程也可能在几千或几万连接时失败。
持久化时可以写入 /etc/security/limits.conf:
* soft nofile 1048576
* hard nofile 1048576
如果服务由 systemd 拉起,还要检查 service 里的:
LimitNOFILE=1200000
3. TCP 内存:连接数测试和吞吐测试不是一回事
README 中给了这组参数:
sudo sysctl -w net.ipv4.tcp_mem="262144 524288 786432"
sudo sysctl -w net.ipv4.tcp_wmem="1024 1024 2048"
sudo sysctl -w net.ipv4.tcp_rmem="1024 1024 2048"
tcp_mem 的单位是 page,常见 page size 是 4KB:
262144 pages * 4KB ≈ 1GB
524288 pages * 4KB ≈ 2GB
786432 pages * 4KB ≈ 3GB
tcp_wmem 和 tcp_rmem 是单个 TCP socket 的发送/接收缓冲区自动调节范围。这里把它们调得很小,是为了节省每个连接的内存。
但要注意:这适合“百万空闲长连接”或“少量保活数据”的测试。如果你要测真实吞吐,不应该把缓冲区调得这么小,否则窗口太小、吞吐会很差。
4. conntrack:容器、NAT、防火墙场景容易踩坑
如果连接经过 NAT、iptables/nftables 状态跟踪、Docker 网络或虚拟机网络,还要看 conntrack:
sudo sysctl -w net.netfilter.nf_conntrack_max=1048576
sudo sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=1200
nf_conntrack_max 控制连接跟踪表大小。nf_conntrack_tcp_timeout_established 控制 ESTABLISHED 状态在 conntrack 中保留多久。
如果只是本机 loopback 测试,conntrack 可能不是主要瓶颈;但跨机器、容器或 NAT 环境里,它经常会比业务进程更早报错。
5. 本地端口范围:客户端压测也有限制
单机客户端发起大量连接时,还会受本地临时端口范围影响:
sudo sysctl -w net.ipv4.ip_local_port_range="10000 65000"
一个源 IP 到同一个目标 IP:PORT 的 TCP 四元组必须唯一。单机单 IP 单目标端口测试时,可用端口数量有限,所以项目的压测客户端支持多端口轮询:
#define MAX_PORT 20
while (1) {
if (++index >= MAX_PORT) index = 0;
if (connections < 340000 && !isContinue) {
sockfd = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_port = htons(port + index);
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
perror("connect");
goto err;
}
这段代码会把连接分散到 port 到 port + MAX_PORT - 1。服务端如果要配合多端口测试,需要同时监听多个端口。当前 reactor.c 中:
#define MAX_PORTS 1
int server_fds[MAX_PORTS];
如果要做多端口压测,可以把 MAX_PORTS 调大,并确保每个端口都 init_server(port + i)。
6. 服务端如何观察连接建立速度
reactor.c 的 accept_cb() 中每接收 1000 个连接打印一次耗时:
if ((client_fd % 1000) == 0)
{
struct timeval tv_cur;
gettimeofday(&tv_cur, NULL);
int time_used = (tv_cur.tv_sec - tv_begin.tv_sec) * 1000
+ (tv_cur.tv_usec - tv_begin.tv_usec) / 1000;
printf("Accepted connection fd: %d, time used: %d ms\n", client_fd, time_used);
memcpy(&tv_begin, &tv_cur, sizeof(struct timeval));
}
这里用 client_fd % 1000 粗略观察 fd 增长和连接接入速度。因为 fd 通常递增,所以在连接数测试里很直观。
更严谨的做法是单独维护 accepted_count,不要用 fd 取模代替连接数统计。
7. epoll 压测客户端
项目里的 mul_port_client_epoll.c 也使用 epoll 管理大量客户端 socket:
struct epoll_event events[MAX_EPOLLSIZE];
int epoll_fd = epoll_create(MAX_EPOLLSIZE);
ev.data.fd = sockfd;
ev.events = EPOLLIN | EPOLLOUT;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);
每隔一段时间打印当前连接数和耗时:
if (connections % 1000 == 999 || connections >= 340000) {
struct timeval tv_cur;
memcpy(&tv_cur, &tv_begin, sizeof(struct timeval));
gettimeofday(&tv_begin, NULL);
int time_used = TIME_SUB_MS(tv_begin, tv_cur);
printf("connections: %d, sockfd:%d, time_used:%d\n",
connections, sockfd, time_used);
int nfds = epoll_wait(epoll_fd, events, connections, 100);
}
编译:
gcc mul_port_client_epoll.c -o client
运行示例:
./client 127.0.0.1 8080
如果目标是多端口压测,要保证服务端监听了对应端口范围。
8. 推荐的临时调参清单
README 中的临时配置可以整理成一组命令:
sudo modprobe ip_conntrack
sudo sysctl -w net.ipv4.tcp_mem="262144 524288 786432"
sudo sysctl -w net.ipv4.tcp_wmem="1024 1024 2048"
sudo sysctl -w net.ipv4.tcp_rmem="1024 1024 2048"
sudo sysctl -w fs.file-max=1048576
sudo sysctl -w net.ipv4.tcp_max_orphans=16384
sudo sysctl -w net.netfilter.nf_conntrack_max=1048576
sudo sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=1200
sudo sysctl -w net.ipv4.ip_local_port_range="10000 65000"
ulimit -n 1048576
持久化可以写到:
sudo vim /etc/sysctl.d/99-epoll-test.conf
写入内容:
net.ipv4.tcp_mem = 262144 524288 786432
net.ipv4.tcp_wmem = 1024 1024 2048
net.ipv4.tcp_rmem = 1024 1024 2048
fs.file-max = 1048576
net.ipv4.tcp_max_orphans = 16384
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_tcp_timeout_established = 1200
net.ipv4.ip_local_port_range = 10000 65000
加载:
sudo sysctl --system
9. 小结
C1000K 测试不是只写一个 epoll server 就结束了。它至少涉及四层资源:
- 应用层:事件循环、连接表、缓冲区、状态管理。
- 进程层:
ulimit -n。 - 内核层:
fs.file-max、TCP 内存、orphan socket。 - 网络路径:本地端口、conntrack、NAT、防火墙。
如果只是验证“能维持多少空闲长连接”,可以适当调小 TCP 缓冲区;如果要验证真实业务吞吐,就要重新设计缓冲区、压测模型和指标。连接数和吞吐量是两个不同问题,不建议混在一起下结论。
学习链接: https://github.com/0voice
313

被折叠的 条评论
为什么被折叠?



