Post

TCP发送接收过程(二) -- 实验观察TCP性能和窗口、Buffer的关系

通过实验观察TCP性能和窗口、Buffer的关系,并用Wireshark跟踪TCP Stream Graphs。

TCP发送接收过程(二) -- 实验观察TCP性能和窗口、Buffer的关系

1. 引言

上一篇博客中介绍了Wireshark里的TCP Stream Graghs可视化功能并查看了几种典型的图形,本篇进行实验观察TCP性能和窗口、Buffer的关系,并分析一些参考文章中的案例。

一些相关文章:

202505更新:之前一直占坑没进行实验,最近定位问题碰到UDP接收缓冲区满导致丢包的情况,补充本实验增强体感。

另外近段时间重新过了下之前看过的几篇网络相关文章,对知识树查漏补缺挺有帮助:

说明:本博客作为个人学习实践笔记,可供参考但非系统教程,可能存在错误或遗漏,欢迎指正。若需系统学习,建议参考原链接。

2. 理论和技巧说明

2.1. 计算在途字节数

在途字节数(Bytes in flight:发送方已发送出去,但是尚未接收到ACK确认的字节数(正在链路上传输的数据)。在途字节数如果超出网络的承载能力,会出现丢包重传。

如何计算在途字节数?

  • TCP传输过程中,由于客户端和服务端的网络包可能是不同的顺序,所以最好两端都进行抓包,并从 数据发送方 抓到的包分析在途字节数。
  • 计算方式:在途字节数 = Seq + Len - Ack(Wireshark中提供的SEQ/ACK analysis功能会自动计算)
  • TCP协议中,由发送窗口动态控制,跟cwnd拥塞窗口和rwnd接收窗口也有关系。

在Wireshark中可以直接查看其提供的SeqAck分析结果,方式可见 Wireshark中新增实用列中的示例:

tcp-useful-column

2.2. 估算网络拥塞点

当发送方发送大量数据,在途字节数超出网络承载能力时,就会导致拥塞,这个数据量就是 拥塞点

大致可以认为:发生拥塞时的在途字节数就是该时刻的网络拥塞点。

如何在Wireshark中找到拥塞点?

  • 从Wireshark中找到一连串重传包中的第一个,再根据重传包的Seq找到 原始包,最后查看原始包发送时刻的在途字节数
  • 具体步骤:
    • Export Info中查看重传统计表
    • 找到第一个重传包,根据其Seq找原始包,可以直接在SEQ/ACK analysis中跳转到原始包。
    • 如下图所示,对应的原始包是No为46的包,其在途字节数为908,即此时的网络拥塞点。
  • 该方法不一定很精确,但很有参考意义。

拥塞点估算示例:
wireshark-find-retran-seq

2.3. 带宽时延积(BDP)

带宽时延积BDP(Bandwidth-Delay Product)带宽 (bps)往返时延 (RTT, 秒)的乘积,衡量在特定带宽和往返时延(RTT)下,网络链路上可同时传输的最大数据量。

  • 公式:BDP (bits)=带宽 (bps) * 往返时延 (RTT, 秒)
    • 如:带宽1Gbps(即万兆)、RTT为50ms,则 BDP = 10^9 * 0.05 = 5*10^7 = 6.25MB
    • 试下LaTeX语法:\text{BDP} = (10^9 \times 0.05 = 5 \times 10^7) bits = \text{6.25MB}
  • 在途字节数的关系:BDP是链路的理论容量,在途字节数是实时传输中的数据量。
    • 在途字节数 == BDP,说明链路被完全填满,可达到最大吞吐量
    • 在途字节数 < BDP,链路未充分利用
    • 在途字节数 > BDP,可能引发拥塞或丢包

3. 实验说明

3.1. 环境和步骤

自己本地只有一个Mac笔记本和Rocky Linux系统的PC,对MacOS的内核网络控制不熟悉,还是尽量模拟生产环境中的Linux间通信,起ECS进行实验。

起2个阿里云ECS(2核2G):

  • 系统:Rocky Linux release 9.5 (Blue Onyx)
  • 内核:5.14.0-503.35.1.el9_5.x86_64

网速基准:TCP 2.1Gbps左右

1
2
3
4
5
6
7
8
9
# TCP
[root@iZbp169scc1yz2vwe6mp31Z ~]# iperf3 -c 172.16.58.147
Connecting to host 172.16.58.147, port 5201
[  5] local 172.16.58.146 port 56178 connected to 172.16.58.147 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
...
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  2.56 GBytes  2.20 Gbits/sec  87452             sender
[  5]   0.00-10.22  sec  2.56 GBytes  2.15 Gbits/sec                  receiver

步骤说明:

  • 服务端:python -m http.server起http服务,监听8000端口
    • dd一个2GB文件用于测试:dd if=/dev/zero of=test.dat bs=1G count=2
  • 客户端:curl请求下载文件
    • curl "http://xxx:8000/test.dat" --output test.dat
  • 抓包:
    • tcpdump -i any port 8000 -s 100 -w normal_client.cap -v
    • 先两端都抓包对比下,服务端也抓包:tcpdump -i any port 8000 -s 100 -w normal_server.cap -v
    • 下面只看客户端(网络路径比较简单,抓包差别不大,只看一边即可)

自己PC的Rocky Linux发送接收窗口相关参数默认值(可通过man tcp查看各选项含义和功能):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# ECS里则把下面两项:TCP和UDP的内存配置降低了,其他一样
# net.ipv4.tcp_mem = 18951        25271   37902
# net.ipv4.udp_mem = 37905        50542   75810

# 本地PC
[root@xdlinux ➜ tmpdir ]$ sysctl -a|grep mem
# 每个socket接缓冲区的默认值,字节
net.core.rmem_default = 212992
# 每个socket接收缓冲区的最大允许值,字节
net.core.rmem_max = 212992
# 每个socket发送缓冲区的默认值,字节
net.core.wmem_default = 212992
# 每个socket发送缓冲区的最大允许值,字节
net.core.wmem_max = 212992

# TCP协议整体使用的内存阈值,注意:单位是page(page size一般4KB,可`getconf PAGESIZE`查看)
# [low, pressure, high]数组。
# low:TCP内存充足,无需限制;pressure:内核开始限制TCP内存分配;high:内核拒绝新连接
    # (低水位 压力模式 上限)
    # (1.43GB 1.92GB 2.88GB)
net.ipv4.tcp_mem = 371808	495745	743616
# TCP单个连接的接收缓冲区窗口,单位:字节。在TCP中会覆盖上面的全局socket缓冲区配置
# TCP根据网络状况动态调整缓冲区大小,范围在[min, max]之间
    # [min, default, max]
    # 4KB 128KB 6MB
net.ipv4.tcp_rmem = 4096	131072	6291456
# TCP单个连接的发送缓冲区大小
    # 4KB 16KB 4MB
net.ipv4.tcp_wmem = 4096	16384	4194304

# UDP整体使用的内存阈值,单位:page
    # (低水位 压力模式 上限)
    # (2.88GB 3.84GB 5.76GB)
net.ipv4.udp_mem = 743616	991490	1487232
# UDP单个连接接收缓冲区的最小值,4KB
net.ipv4.udp_rmem_min = 4096
# UDP单个连接发送缓冲区的最小值,4KB
net.ipv4.udp_wmem_min = 4096

3.2. 实验用例

  • 1、保持默认参数正常curl下载文件,抓包用作基准对比
  • 2、修改服务端的发送窗口为最小值:4096,即一个page size;客户端不变
    • 服务端:sysctl -w net.ipv4.tcp_wmem="4096 4096 4096"
  • 3、服务端不变;修改客户端的接收窗口为4096
    • 客户端:sysctl -w net.ipv4.tcp_rmem="4096 4096 4096"

3.3. 结果及分析

用例1:默认参数

bdp-case-normal-param

用例2:服务端的发送窗口4096字节

bdp-case-normal-param

用例3:客户端的接收窗口4096字节。很慢且抓包太大,手动打断了下载

bdp-case-normal-param

4. 小结

5. 参考

This post is licensed under CC BY 4.0 by the author.