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

【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_wmemtcp_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;
        }

这段代码会把连接分散到 portport + MAX_PORT - 1。服务端如果要配合多端口测试,需要同时监听多个端口。当前 reactor.c 中:

#define MAX_PORTS 1

int server_fds[MAX_PORTS];

如果要做多端口压测,可以把 MAX_PORTS 调大,并确保每个端口都 init_server(port + i)

6. 服务端如何观察连接建立速度

reactor.caccept_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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值