深夜的机房,两台服务器之间闪烁的网卡指示灯仿佛在无声地诉说着什么。运维老张盯着监控大屏上波动的带宽曲线眉头紧锁——新部署的分布式存储系统在跨机房同步时总是达不到预期的传输速率。他摸出手机给同事发了条消息:"用iperf3测下AB两区的带宽质量,我怀疑中间链路过载了。"
这就是网络性能测试的日常。无论是IDC机房间的专线质量验证,还是家庭宽带升级后的真实速率检验,亦或是企业VPN隧道的稳定性评估,iperf3这个看似简单的命令行工具,总能在关键时刻成为工程师手中的"听诊器"。不同于图形化工具的繁复,它用最朴素的TCP/UDP流量冲击,直击网络传输的本质矛盾:在复杂的网络环境中,理论带宽与真实性能之间,往往隔着协议栈优化、硬件瓶颈、路由策略等重重迷雾。本文将带你穿透这些迷雾,从基础安装到高阶调优,掌握这个网络诊断利器的十八般武艺。
Linux各大发行版的软件源基本都会自带iperf3,直接从软件源安装即可,如果要安装特定版本的iperf3,则考虑编译安装。
发行版 | 安装命令 |
---|---|
Centos/Redhat | yum install iperf3 -y |
Archlinux | pacman -Sy iperf3 |
Debian/Ubuntu/Kali | apt install iperf3 -y |
Gentoo | emerge --ask net-misc/iperf |
iperf3的项目地址:https://github.com/esnet/iperf/releases
截止到今天,最新版本为3.18,以3.18的安装为例。
cd /opt/
wget https://github.com/esnet/iperf/releases/download/3.18/iperf-3.18.tar.gz
tar xf iperf-3.18.tar.gz
cd iperf-3.18
./configure && make && make install
安装完成后,可通过iperf3 -v查看输出信息:
如果安装失败,尝试先执行./bootstrap.sh脚本,再进行安装。
可以到此页面或者github发布页面下载对应版本的iperf3二进制文件,下载时选择与系统架构匹配的版本(32/64位)。下载后解压到某个路径,之后再将路径写入到环境变量PATH即可。
如果不想写环境变量,只是临时使用,使用cmd打开对应路径也可以直接执行。
最简单的压测法,不加任何其他参数,默认是单线程。
IP | 操作系统 | 网卡规格 | |
---|---|---|---|
客户端 A | 192.168.1.14 | Kali Linux | 2.5Gbps |
服务端 B | 192.168.1.8 | Gentoo Linux | 2.5Gbps |
服务端监听,不指定端口的情况下默认监听5201端口:
iperf3 -s
客户端压测:
iperf3 -c <server_ip>
字段说明:
字段 | 说明 |
---|---|
Interval | 测试时间。 |
Transfer | 数据传输量,分为sender发送量与receiver 接收量。 |
Bitrate | 带宽(单位为bit),分为sender发送带宽与receiver接收带宽。 |
Retr | 重传次数。 |
Cwnd | 拥塞窗口,cwnd限制发送方在未收到接收方确认(ACK)前,最多能发送的数据量。 |
通过抓包可以看到,iperf3发起了两个TCP连接,第一个TCP连接主要通过json格式传输一些参数控制,因为在正式传输压测数据前,客户端与服务器通要过此连接交换测试配置信息(如测试时长、数据包大小、并行流数量等):
比如上面红圈这几个参数,含义如下:
字段 | 含义 |
---|---|
"len":131072 | 指定数据包长度为 131072 字节。 |
"parallel":1 | 线程数为1,使用单条数据流(非并行)。 |
"pacing_time":"1000" | 数据发送间隔为 1000 毫秒。 |
"client_version":"3.18" | 客户端版本为 3.18。 |
其中还包括客户端/服务器的资源使用情况(如CPU利用率):
字段 | 含义 |
---|---|
"cpu_util_total":10.89 | 总CPU利用率为10.89%。 |
"cpu_util_user":0.156 | 用户态CPU利用率为0.156%。 |
"cpu_util_system":10.733 | 内核态CPU利用率为10.733%。 |
到第二个TCP连接才开始传输压测数据,可以看到TCP会话界面,第一个TCP会话是一些元数据的交换因此没什么太多数据需要传输,第二个TCP会话传输了3GB数据,客户端(A)往服务端(B)压测的带宽速率为2332Mbps,这个方向对于客户端A来说,主要占用的是出带宽,对于服务端B来说,主要占用的是入带宽:
为什么上面2.5G网卡规格,实际压测只有2.36G?因为协议也有开销,不考虑物理层开销的情况下,TCP+IP头开销是40字节。
基础开销
TCP 头部开销(默认):
IPv4 头部开销(默认):
总网络层开销:
MTU为1500字节的情况下,TCP/IP协议开销为:
以上还没纳入以太网(14字节以太网帧+FCS 4字节=18字节)开销,实际开销可能会大于2.67%。
因此理论最大带宽:
巨型帧优化(MTU = 9000 Bytes)
如果使用巨帧则可以一定程度上减少开销:
iperf3默认为单线程压测,通过-P选项可指定多线程(iperf2版本不支持多线程参数),用于突破单线程瓶颈,模拟高并发场景。
IP | 操作系统 | 网卡规格 | |
---|---|---|---|
客户端 A | 192.168.1.14 | Kali Linux | 2.5Gbps |
服务端 B | 192.168.1.8 | Gentoo Linux | 2.5Gbps |
服务端:
iperf3 -s
客户端压测:
iperf3 -c <server_ip> -P 8
-P 8 指定8个线程
iperf3起了8个线程,SUM字段会统计总带宽值。
Wireshark抓包的第一条TCP流中,可以看到客户端使用的参数:
一共包含9个TCP会话,其中第一个会话是前面讲过的iperf3进行的元数据交换阶段,不纳入8个线程当中:
其他多线程示例
使用10个线程压测,压测60秒:
iperf3 -c <server_ip> -P 10 -t 60
启动10个并行线程,跳过前2秒的TCP慢启动阶段,每秒输出一次状态:
iperf3 -c <server_ip> -P 10 -O 2 -i 1
为什么需要UDP压测?因为TCP会通过滑动窗口、拥塞控制算法(比如慢启动、拥塞避免、快速重传等)动态调整发送速率,当网络出现丢包或延迟时,TCP会主动降低速率以避免拥塞。这种“智能”特性会导致TCP压测结果往往低于实际带宽上限,无法完全压榨网络资源。
UDP没有这种流量控制或拥塞控制机制,发送端可以持续以最大速率发送数据包,直至填满带宽。这种“暴力”特性让UDP能更直接地探测到网络的物理带宽极限。
IP | 操作系统 | 网卡规格 | |
---|---|---|---|
客户端 A | 192.168.1.14 | Kali Linux | 2.5Gbps |
服务端 B | 192.168.1.12 | ArchLinux | 2.5Gbps |
服务端:
iperf3 -s
客户端压测:
注意要加上 -b 参数,如果不指定压测带宽,iperf3出于保护机制默认只会压测1Mbps左右的带宽,-b 0 表示不限制压测带宽。
iperf3 -c <server_ip> -u -b 0
从客户端的抓包看,即使是UDP压测,元数据的交换也是用的TCP:
之后的UDP压测阶段,都是单方向传输,不需要服务端回包:
不限制带宽的压测会导致带宽被打满,如果也业务在跑则可能会影响业务的通信,如果要压测具体的带宽,后面接带宽值即可,比如 -b 200M、-b 2.5G、-b 10G。对于UDP压测,iperf3还提供了--dont-fragment参数用于设置UDP包不分片,TCP不支持此参数。
当A作为iperf3客户端,B作为服务端,要压测B的出带宽A的如带宽时,此时就可以利用反向压测,而无需A和B互换角色。
这类场景适用于A不太方便作为服务端去开启监听端口让B来压测A:
1)比如A本身没有公网IP,需要走公网链路去压测B时,A如需进行外网端口监听则需要做DNAT映射或网络穿透,麻烦且没必要;
2)A的网络环境有安全策略等限制,并不方便放开任何对外监听端口,只允许主动出去,而不能被主动访问。
诸如上面种种例子,A作为客户端,B作为服务端,想压测B的出带宽、A的入带宽,使用反向压测参数 -R 即可:
服务端B将发送数据给客户端A。
IP | 操作系统 | 网卡规格 | |
---|---|---|---|
客户端 A | 192.168.1.14 | Kali Linux | 2.5Gbps |
服务端 B | 192.168.1.8 | Gentoo Linux | 2.5Gbps |
服务端:
iperf3 -s
客户端:
iperf3 -c <server_ip> -P 10 -R # -P 10 使用十个线程压测
Wireshark conversation可以清晰看到,是B往A的方向传输数据:
当需要同时压测A、B端的出入带宽的时候,则可以使用双向压测参数 --bidir。
IP | 操作系统 | 网卡规格 | |
---|---|---|---|
客户端 A | 192.168.1.14 | Kali Linux | 2.5Gbps |
服务端 B | 192.168.1.8 | Gentoo Linux | 2.5Gbps |
服务端:
iperf3 -s
客户端:
iperf3 -c <server_ip> -P 5 --bidir
方便直观展示输出这里只用了5个线程压测,根据业务情况按需选择。可以看到客户端和服务端都有两个方向的带宽速率统计:TX-C即从客户端发送出去的带宽(Transmit-Client),RX-C从客户端接收到的带宽(Receive-Client),RX-S 服务端接收带宽(Receive-Server),TX-S服务端发送出去的带宽(Transmit-Server)。
Wireshark的会话页面可以看到,A到B和B到A基本都被打满了:
再仔细观察可以发现,虽然两个方向都进行压测,但无一例外都是客户端主动去连接服务端,即使指定的是5个线程,此时双向压测,iperf3则开了10个线程,其中5个连接正向传输,另外5个连接反向传输:
但为什么过滤SYN的时候会有11个连接?
因为第一个TCP连接是iperf3客户端和服务端交互的一些元数据,还没真正开始压测数据,这一步为压测做准备,可以参考上面的 3.1.2 章节。
TCP的传输效率依赖于滑动窗口(Window Size) 机制。窗口大小决定了发送方在等待接收方确认(ACK)前可以发送的最大数据量。时延越高,数据包到达接收方并返回 ACK 的时间越长。如果窗口大小不足以覆盖带宽时延积(BDP, Bandwidth-Delay Product),发送方会因等待 ACK 而暂停发送,导致带宽无法被充分利用。
BDP的公式为:
表示网络中“正在传输中”的数据总量。例如带宽 1Gbps(1,000,000,000 bps)、时延 50ms(0.05秒)时:
若 TCP 窗口大小 < BDP,实际带宽会受限。iperf3 默认的窗口大小可能不足以匹配高时延链路的需求。
模拟之前,确保已经清除已有的tc规则:
sudo tc qdisc del dev eth0 root 2>/dev/null # eth0 替换为你的网卡名
紧接着用iperf3单线程的情况下测试无限制的情况的带宽:
IP | 操作系统 | 网卡规格 | A到B的RTT | 协议 | |
---|---|---|---|---|---|
客户端 A | 192.168.1.14 | Kali Linux | 2.5Gbps | 1ms左右 | TCP |
服务端 B | 192.168.1.8 | Gentoo Linux | 2.5Gbps | 1ms左右 | TCP |
服务端:
iperf3 -s
客户端:
iperf3 -c <server_ip>
可以看到,默认情况下压测带宽在2.36G左右。
之后控制变量法,将A到B的延时增加到100ms左右(0.1秒):
sudo tc qdisc add dev eth0 root netem delay 99ms # eth0 替换为你的网卡名,因为B到A之间设已经有1ms RTT在链路上产生,在此基础上增加99ms即可。
IP | 操作系统 | 网卡规格 | A到B的RTT | 协议 | |
---|---|---|---|---|---|
客户端 A | 192.168.1.14 | Kali Linux | 2.5Gbps | 100ms左右 | TCP |
服务端 B | 192.168.1.8 | Gentoo Linux | 2.5Gbps | 100ms左右 | TCP |
再来用同样的压测命令,iperf3单线程压测:
带宽锐减到了230Mbps左右。
延续上面的问题,为什么是230Mbps左右,先看看客户端和服务端的窗口大小情况:
查看客户端的发送窗口大小(CWND):
sysctl net.ipv4.tcp_wmem
查看服务端的接收窗口大小(RWND):
sysctl net.ipv4.tcp_rmem
不管是tcp_rmen还是tcp_wmen。输出的三个字段表示的含义分别是:min、default、max,即最小值、默认值、最大值。实际有效窗口:
同时已知客户端到服务端的RTT延时为100ms。
基于以上信息,计算理论带宽大小:
按照上述公式,可以推导单线程情况下,TCP连接理论能达到的带宽大小,实际带宽还涉及到其他可能因素,包括但不限于:
调整优化上述这些非公式内的其他因素,可以让压测带宽无限逼近理论带宽。多线程压测、增大窗口大小、减少延时,也能直接提升带宽利用率。
比如增大窗口大小,增大客户端发送窗口大小:
sysctl -w net.ipv4.tcp_wmem="4096 16384 33554432" # 发送窗口最大值设为32MB
增大服务器端接收窗口大小:
sysctl -w net.ipv4.tcp_rmem="4096 131072 33554432" # 接收窗口最大值设为32MB
增大接收缓冲区和发送缓冲区大小:
sysctl -w net.core.rmem_max=33554432 # 32MB
sysctl -w net.core.wmem_max=33554432 # 32MB
以上只是调整临时增大,如需永久生效需编辑/etc/sysctl.conf文件,修改相关内核调优参数,执行sysctl -p永久生效。
如果通过iperf3指定的窗口大小 < 缓冲区大小,则会报下面的错误:
缓冲区大小也不要盲目调大,过高的max会导致内存耗尽(OOM)。
设置完后,客户端通过增大窗口进行压测:
iperf3 -c <server_ip> -w 32M #窗口大小指定为32MB
可以看到,压测带宽有了明显的提升。
使用UDP压测可以绕过 TCP 的流量控制、拥塞控制和窗口大小限制一系列影响因素,压榨出最真实的网络带宽瓶颈,如果不限制压测带宽大小,容易把机器性能打爆,如果是ssh上这台机器,ssh可能会被断连,因此这里指定2.25G带宽压测。
IP | 操作系统 | 网卡规格 | A到B的RTT | 协议 | |
---|---|---|---|---|---|
客户端 A | 192.168.1.14 | Kali Linux | 2.5Gbps | 100ms左右 | UDP |
服务端 B | 192.168.1.12 | ArchLinux | 2.5Gbps | 100ms左右 | UDP |
客户端:
iperf3 -c <server_ip> -u -b 2.25G
可以看到出带宽2.25G全部被打满,即使到服务端的延时在100ms左右。
在做模拟之前,确保已经清空了tc的相关策略:
sudo tc qdisc del dev eth0 root 2>/dev/null # eth0 替换为你的网卡名
并且在客户端设置20%的丢包率:
sudo tc qdisc add dev eth0 root netem loss 20% # eth0 替换为你的网卡名
在测试之前,我们可以通过公式来估算一下,20%丢包率理论上能压测多少带宽,已知如下条件:
MSS大小 | RTT | RTO | 丢包率(p) | 拥塞算法 |
---|---|---|---|---|
1460 | 1ms | 1ms * 2 | 20% | CUBIC |
TCP吞吐量受RTT和丢包率(p)的影响,同时系统默认的拥塞控制算法为CUBIC,常用Padhye公式估算:
代入计算:
以上10.48Mbps只是根据公式理论估算出来的值。接下来进行实际压测:
IP | 操作系统 | 网卡规格 | A到B的RTT | 协议 | 丢包率 | |
---|---|---|---|---|---|---|
客户端 A | 192.168.1.14 | Kali Linux | 2.5Gbps | 1ms左右 | TCP | 20% |
服务端 B | 192.168.1.8 | Gentoo Linux | 2.5Gbps | 1ms左右 | TCP | 0% |
服务端:
iperf3 -s
客户端:
iperf3 -c <server_ip> -P 10 # 使用10个线程并行
可以看到,和估计值基本吻合。在丢包率很高的场景下,因为会频繁触发超时重传和拥塞窗口(CWND)重置,导致一直在TCP慢启动阶段,进而导致有效吞吐量进一步下降。
这种情况下我们可以考虑开启BBR拥塞控制算法。查看当前使用的默认算法:
cat /proc/sys/net/ipv4/tcp_congestion_control
临时启用BBR算法:
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
之后我们再重新压测,速率相对有了显著的提升:
Cubic/Reno拥塞控制算法在20%丢包率下,CWND可能无法增长,吞吐量接近零。因此选用BBR,抗丢包能力相对较强。除了拥塞算法外,还可以考虑增大接收缓冲区和发送缓冲区大小、多线程压测等。
同样是基于20%的丢包率进行模拟:
IP | 操作系统 | 网卡规格 | A到B的RTT | 协议 | 丢包率 | |
---|---|---|---|---|---|---|
客户端 A | 192.168.1.12 | ArchLinux | 2.5Gbps | 1ms左右 | UDP | 20% |
服务端 B | 192.168.1.8 | Gentoo Linux | 2.5Gbps | 1ms左右 | UDP | 0% |
防止把网卡打爆导致断连,客户端压测指定带宽为2.3G,使用UDP压测:
iperf3 -c <server_ip> -u -b 2.3G
A压测B,对于客户端A来说,压测的是出带宽,即使丢包率20%也不影响出带宽压测,因为UDP是无状态的,没有拥塞控制算法,不关心发出去没发出去,因此依然可以压测出2.3G带宽,对于服务端B来说,客户端A发给它的压测数据丢了20%,因此入口只能收到80%的包:2.3G * 0.8 = 1.84Gbps,和上图吻合。
所以,如果链路上有丢包,不影响发送端压测出去的带宽,会影响接收端的入带宽,粗略估算可以是:入带宽 = 压测带宽 * 丢包率。
使用频率相对较高的通用类参数:
参数 | 说明 |
---|---|
-p,--port | 指定服务端监听端口 |
-i,--interval | 指定压测间隔 |
-t,--time | 设置压测时间 |
--cport | 设置客户端端口 |
-O,--omit | 跳过TCP慢启动 |
服务端监听如不指定端口,默认端口为5201/tcp,可以通过-p指定端口:
iperf3 -s -p 55000 # 指定端口为55000
当服务端通过指定自定义端口后,客户端也需通过-p指定对端端口:
iperf3 -c <server_ip> -p 55000
适用于默认端口被占用,或者安全策略只放通了固定的端口或端口范围的情况。
这两个参数是客户端专用的,服务端无法使用。
客户端的默认压测时间间隔为1秒,默认压测10秒停止,可以通过-i指定时间间隔:
iperf3 -c <server_ip> -i 0.1 # 间隔0.1秒压测一次
指定为0.1秒每次后,10s则会压测100次。
可以通过-t来设置默认压测时间,比如只压测1秒,压测间隔为0.1秒每次:
iperf3 -c <server_ip> -i 0.1 -t 1
适用于需要观察极短时间内(如毫秒级)带宽的突发变化或抖动,以及不想长时间占用带宽的场景。
客户端的源端口默认用的是随机高端口,可以通过--cport指定自定义端口,比如指定1024作为源端口:
iperf3 -c <server_ip> --cport 1024
适用于那些对出网的源端口范围有严格的策略限制的网络环境。
客户端压测,跳过前3秒慢启动,每秒输出一次状态:
iperf3 -c <server_ip> -O 3 -i 1 -t 15 -P 4
红圈部分的测试为前3秒的压测数据,可以看到红圈标记为omitted,即忽略,忽略的部分不会统计到最终结果里。
iPerf3 的公共测试服务器通常由网络服务商、教育机构、开源社区维护,可用于临时测速。适用于只想知道单台主机的带宽且没有多余的机器陪跑测试的情况。
iperf3官方推荐的公共测试服务器,可以参考此列表,截至目前,节点主要分布在欧洲、亚洲、大洋洲、美洲。
可以看到没有大陆的节点,如果机器在大陆去用上述这些公共测试服务器会受跨境质量拥塞影响,延时和丢包率都很高,带宽也会不符合预期。
比如选用其中的Uzbekistan Tashkent的节点进行压测:
iperf3 -c speedtest.uztelecom.uz
Cwnd拥塞窗口起不来,影响最终的吞吐量测试,Retr字段可以看到重传数量比较多。
第三方公共测试服务器节点分布较多,参考此项目地址。
此项目还提供了一个测试脚本,执行脚本将会根据你的出口IP位置选就近的服务器节点进行测试:
curl -s https://raw.githubusercontent.com/R0GGER/public-iperf3-servers/refs/heads/main/findtest.sh | bash
选出最近节点后,可以自定义iperf3命令去压测目标节点。比如上述截图中脚本已经帮你选好了节点,可以进行反向多线程压测:
iperf3 -c speedtest.hkg12.hk.leaseweb.net -R -P 8
想知道客户端出带宽正向压测即可,想知道客户端入带宽,-R反向压测即可,也可以加上-P多线程参数。
当最后一次压测的带宽曲线在屏幕上定格,老张终于露出了笑容——通过iperf3的UDP双向测试,他们精准定位到了跨国专线在晚高峰时段的突发丢包问题。这种从数据洪流中捕捉真相的能力,正是这个36年历史的老牌工具(iperf始于1986年)至今仍被广泛使用的魅力所在。
从单线程TCP的基础测试到多线程UDP的暴力压测,从局域网微秒级延时的精准测量到跨国高延迟链路的窗口调优,我们见证了iperf3在不同场景下的独特价值。值得特别注意的是,当遇到"测速结果远低于预期"的经典难题时,不妨尝试以下实战口诀:
下次面对复杂的网络性能问题时,不妨像老练的渔夫那样,把iperf3这张大网撒向数据海洋。无论是千兆光纤的极限吞吐,还是物联网设备的微弱信号,那些隐藏在协议背后的性能真相,终将在精准的流量冲击下无所遁形。
附带PDF版本:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。