gitbook/网络排查案例课/docs/486281.md
2022-09-03 22:05:03 +08:00

323 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 11 | 拥塞:TCP是如何探测到拥塞的?
你好,我是胜辉。
前面两节课,我们通过真实的案例,一起学习了TCP传输方面的知识,比如其中的核心概念:往返时间、接收窗口和发送窗口、在途字节数,还有推导出来的核心公式。
当然,在实际场景里,我们可以直接利用Wireshark的I/O Graph查看速度趋势图,这样最方便,并且有不同时段的速度,方便我们对整体状况做全面的评估。
不过,不知道你有没有发现,TCP传输的起始阶段,速度都是从低到高升上来的,很少有一上来就直接以最终速度运行的情形。其实,这个行为跟 **TCP拥塞控制**有着密切的关系。所以这节课,我会带你了解什么是拥塞窗口、TCP是如何检测和避开拥塞的。这样呢,以后你处理TCP拥塞相关的问题时候,就能有的放矢,做到有针对性的分析了。
好,让我们来看一个具体的案例吧。
## 案例
在公有云服务的时候,我们有个银行的客户,他们有一次需要跨机房拷贝一个大文件,也就是用SCP命令,把文件从公有云机房拷贝到他们的自建机房。但是客户发现速度比较慢,让我们看一下原因。
![](https://static001.geekbang.org/resource/image/c8/5b/c88359c7b0889fe31436bd1424b0015b.jpg?wh=1686x539)
架构上说,客户自己的机房在上海,公有云上的资源则在北京。地理位置相差很远,而且机房所属的性质也完全不同,那两者如何通信呢?走公网的话不太安全,而且速度和质量没有保障。
如果你熟悉网络的话,可能知道可以在两个机房之间搭建VPN。而从可靠性角度考虑,客户选择了使用公有云的专线产品,也就是在上海自有机房和北京公有云之间,打通了专线。这样,客户在上海的自有机房,就可以直接以10.x.x.x这种内网地址,直达他们在北京公有云的资源,就好像真的在同一个内网一样。是不是挺酷的?
> 补充:上传的抓包文件我做了脱敏修改,所以IP也是随机值而不是10.x.x.x。
可是,他们在传输文件的时候,发现速度只有200KB/s左右,达不到购买的专线带宽值。正好他们也在传输过程做了tcpdump抓包,我们来看看这个抓包文件的具体情况。
> 抓包示例文件已上传至 [Gitee](https://gitee.com/steelvictor/network-analysis/tree/master/11),你可以用Wireshark打开这个文件,跟随我的分析步骤来同步学习。
跟我们在[第9讲](https://time.geekbang.org/column/article/484923)和[第10讲](https://time.geekbang.org/column/article/485689)学过的一样,我们可以用 **I/O Graph** 这个小工具来直观地看一下传输速度:
![图片](https://static001.geekbang.org/resource/image/1c/cd/1c9eb4698d2fa4cf0fccac8ac2413ccd.jpg?wh=896x624)
从图上看,速度大体上在100~200KB/s之间浮动,有少数几个时段的速度在230KB/s左右。这是传输速度方面的整体概览。
另外就是要查看TCP传输过程中的一些行为了,这些行为没办法在I/O Graph上体现出来,但是它们很可能就是“因”,正是因为它们,才有了传输速度这个“果”。
现在课程快学到一半了,你应该对Expert Information很熟悉了吧?这次也不例外,我们来看一下Expert Information:
![图片](https://static001.geekbang.org/resource/image/12/60/125d10e8d65429639yy87875a4e91360.jpg?wh=1818x296)
可见信息量还挺大的,包括了多种行为。
* Warning级别有两种,分别是乱序(Out-of-Order)和前面报文未抓取的情形。这两者本质上都是乱序引起的现象。
* Note级别,一共有四种,分别是:
* **Spurious重传**:这是已经被确认过的数据再一次被重传。
* **快速重传**:收到3次及以上次数的重复确认后,不等超时就做出的重传。
* **重传**:超时计时器到点而触发的重传,这就是有名的超时重传。
* **重复确认**:确认号重复的多个报文,重复确认是引发快速重传的原因。
* Chat级别的 **TCP Window update**,这里主要是客户端(上海)向SSH服务端(北京)通告自己的接收窗口的变化,是比较正常的行为。
既然还是跟传输速度相关的话题,你应该还记得上节课刚深入讨论过的TCP Window Full了吧?但是这里为什么一条这样的信息都没有呢?
对于TCP传输来说,其速度大体上是窗口/往返时间。而这里的窗口,在不同情境下就有着不同的含义。我们用CW(Congestion Window)来指代自身的拥塞窗口,而用RW(Receive Window)指代对端的接收窗,那么:
* 当RW<CW时,这里的“窗口”就是RW;
* 当RW>CW时,这里的“窗口”就是CW。
对于情况1,也就是对端的接收窗口小于自身拥塞窗口的情况,一般意味着传输过程中没有或者很少有“拥塞”发生,因而拥塞窗口能增长到较高的值。所以,传输速度的上限就是对端接收窗口值决定的。这样呢,也就容易在Wireshark里观察到TCP Window Full这样的现象。当然,也不是每次都一定有TCP Window Full,像[第9讲](https://time.geekbang.org/column/article/484923)的案例就没有。
对于情况2,也就是对端的接收窗口大于自身拥塞窗口的情况,这一般意味着传输过程中遇到了“拥塞”,因而拥塞窗口进行了适配,也就是往下调整,这往往会使得拥塞窗口变得比较小。
事实上,我们在第9讲也已经初步介绍了上面这些知识。我做一下搬运工,同时也做一下美工,对第9讲的图补充了Wireshark可能解读出来的信息,供你参考。下次你在Wireshark看到TCP Window Full、Out-of-Order、或者retransmission时,就可以跟这里的图对应起来,协助你排查传输速度方面的问题了。
![](https://static001.geekbang.org/resource/image/7e/a3/7edf726e8a4a678996ce79067e9700a3.jpg?wh=2000x695)
在当前这个案例里,因为乱序、重传等都有大几千个,非常多,所以我们初步判断,应该就是这些事件导致传输遇到了拥塞,也进而限制了传输速度。那么到这里,我们也就正式进入拥塞机制的学习了。我会给你概括其关键部分,也会结合案例里的抓包文件,来带你获得一个更加感性的认识。
## TCP拥塞控制
为了应对错综复杂的互联网网络环境,TCP使用了**拥塞控制机制**来确保传输速度和稳定性。这里你也要注意,总的来说,拥塞控制主要是通信两端自己需要实现的功能,而途中的网络设备,比如交换机、路由器等等,除了可能会发出拥塞通知报文以外,其他时候它们只管转发报文,都是不会担负更多的拥塞控制的责任的。
TCP拥塞控制主要有四个重要阶段:
* 慢启动;
* 拥塞避免;
* 快速重传;
* 快速恢复。
其中还包括拥塞窗口这个概念,接下来我给你逐一介绍一下。
### 慢启动
Slow Start,是指TCP传输的开始阶段是从一个相对低的速度(“慢”一词的由来)开始的。事实上,在这个阶段,拥塞窗口会以翻倍的方式增长,所以从增长过程来看,叫“快启动”也未尝不可。
具体来说,在这个阶段,每次TCP收到一个确认了数据的ACK,拥塞窗口就增加一个MSS。比如下面这样:
![](https://static001.geekbang.org/resource/image/10/8c/107e4ef0ec736a9b71dbe5968efa768c.jpg?wh=1728x951)
不过这里的“确认了数据的ACK”怎么理解呢?
它说的是有确认数据的ACK报文,而不是重复的ACK报文。比如收到2个ACK但确认号一样,那第二个ACK就不是“确认了数据的ACK”了,拥塞窗口不会增加2个MSS,而是只增加1个MSS。
那么,这个过程什么时候终止呢?是下面两件事中有一件发生时:
* 遇到了拥塞;
* 拥塞窗口增长到慢启动阈值。
#### 慢启动阈值
慢启动阈值(也有人称之为慢启动门限),英文简称ssthresh。过了这个阈值,拥塞窗口的增长速度立刻就放缓了,变成了每过一个RTT,拥塞窗口就只增长一个MSS(此前是每个确认数据的ACK,增长一个MSS)。
![图片](https://static001.geekbang.org/resource/image/b4/c9/b4a0028f02c61b1c8c1afeyya2cf44c9.jpg?wh=715x599)
比如上图的例子中,假设ICW是4个MSS,ssthresh是32个MSS。在慢启动阶段,经过一个RTT后,CW扩大为8个MSS,然后是16个MSS,32个MSS,以指数级上升。
那么到了这个阈值后,TCP就进入了**拥塞避免阶段**,每过一个RTT,拥塞窗口只增加一个MSS,于是在图上看,就又变成了一条平直的斜率比较低的直线了。
![](https://static001.geekbang.org/resource/image/b2/f2/b2ebefaeb3070dfdffd7fe024b0dc6f2.jpg?wh=1663x963)
那么,如果拥塞窗口正好等于慢启动阈值,发送方应该选择继续慢启动过程(指数性增长),还是拥塞避免过程(线性增长)呢? [RFC5681](https://datatracker.ietf.org/doc/html/rfc5681) 的规定是“没有规定”,两种都可以。
#### 间隔确认
这里有个情况必须要提一下。很多TCP实现里(比如Windows系统),确认报文是这样工作的:如果收到连续多个报文,确认报文是一个隔一个回复。也就是:
* 收到1、2,对2进行确认;
* 收到3、4,对4进行确认。
比如就在这个案例里,我们很容易就发现有这种隔一个报文再确认的现象:
![图片](https://static001.geekbang.org/resource/image/09/4d/090550397c591a01a4afeb7b1965354d.jpg?wh=920x184)
上图中,我们选中了17号报文,Wireshark自动找到了被它确认的16号报文,也在它的左边打上了一个小小的勾。当然,我们也可以通过对比NextSeq和ACK来找到这种关系。我在图中就用红框和箭头找到了这里的3对TCP确认关系。
这样的间隔ACK,可能会使得拥塞窗口的增长速度,比每次都ACK要更低一些。
### 拥塞窗口
Congestion Window,缩写是CWND,或者CW。拥塞窗口是不是操作系统全局统一的配置呢?其实这是比较常见的误解。拥塞窗口是每个连接分开维护的,比如同一个主机有两个TCP连接在传输数据的话,那么这两个连接就各自维护自己的拥塞窗口,比如一个很大而一个很小,都没有关系。
下图中,我用CW指代拥塞窗口,图中CW1到CW8都是各自不同、独立维护的拥塞窗口:
![](https://static001.geekbang.org/resource/image/26/b1/267cfac4b58d69ef8a4e8568ca8d34b1.jpg?wh=2000x836)
这里还有一个子概念很重要,叫**初始拥塞窗口**,英文是Initial Congestion Window(或者Initial Window),缩写为ICW(或者IW)。
在Linux内核3.0以前,初始拥塞窗口的大小比较小,在2到4个MSS。2010年,谷歌[提出](https://datatracker.ietf.org/doc/html/draft-hkchu-tcpm-initcwnd-01),为了充分利用现代互联网的传输能力,Linux应该把ICW从2~4个MSS提升到10个MSS。这也被应用到了Linux内核3.0版本及以后的版本中,比如在include/net/tcp.h中,就定义了TCP\_INIT\_CWND的值为10。
```plain
/* TCP initial congestion window as per rfc6928 */
#define TCP_INIT_CWND 10
```
前面刚介绍过,在慢启动阶段,每过一个RTT,拥塞窗口就翻倍。那么不同的ICW就会造成不同的传输速度,比如:
![](https://static001.geekbang.org/resource/image/31/a2/3114816492a2e9d6da4074f9fd7541a2.jpg?wh=2000x591)
看着ICW的变迁,我真的就觉得很多知识是相通的。还记得我们在[第3讲](https://time.geekbang.org/column/article/479163)学习TCP握手的时候,介绍的Window Scale概念吗?为什么已经有Window字段,设计者们还要创造Window Scale呢?本质原因还是互联网发展很快,原先设计的Window不够用了。
那么现在ICW的增加也是如此:既然互联网条件好了那么多,咱就不要过于谨慎了吧?天地大得很,上来就迈大点的步子,后面跑起来就快上加快了!
### 拥塞避免
前面我说过,传输过了慢启动阈值(ssthresh)之后,就进入了**拥塞避免**阶段。这个阶段的特征是“**和性增长乘性降低**”,英文是Addictive increase/mutiplicative decrease,缩写为AIMD。它也翻自英文,怪不得这中文念起来略有不顺,特别是“和性增长”。其实说是“佛系增长”也许更容易理解吧,因为增长很慢,挺佛系的。
那么,因为AIMD的关系,每一个RTT里,拥塞窗口只增长一个MSS,所以这个阶段的拥塞窗口的增长是线性的。直到探测到拥塞,然后拥塞窗口就要往下降。这个下降是直接减半的,所以叫**乘性降低**。我画了一个示意图,给你做参考:
![](https://static001.geekbang.org/resource/image/1b/fc/1b8d403aace12f2742f6054078e273fc.jpg?wh=2000x1125)
当然,图中的第二个拥塞点比第一个低只是一种可能的情况,现实场景里什么情况都可能有,因为网络状况本身就是动态变化的。
### 窗口和MSS的关系
本来这也属于拥塞算法相关的知识点,但因为确实是比较常见的误区,所以我在这里单独拎出来介绍一下。首先,窗口一般比MSS大,而且大很多,可能会有个别同学以为“MSS就是窗口最大值”。其实这是反过来的:窗口值一般比MSS大很多,相当于就是MSS的某个倍数,比如2倍、10倍、50倍等等。
MSS是有确定上限的,我在前面课程里都多次提到过,MSS一般为1460,当然根据实际情况,也经常会有更低的值。比如,开启TCP timestamp等Option的话,肯定要相应地从1460字节里扣去这部分字节数,这样的话MSS就会低于1460,比如可能是1440字节。
你可以理解为:**窗口就是n个MSS**。
> 补充:确切来说,窗口的单位是字节数,所以也经常不是MSS的整数倍,这也都是正常的。
### 快速重传
TCP每发送一个报文,就启动一个超时计时器。如果在限定时间内没收到这个报文的确认,那么发送方就会认为,这个报文已经在网络上丢失了,于是需要重传这个报文,这种形式叫做**超时重传**。
一般来说,TCP的最小超时重传时间为200ms。这样的超时重传的机制虽然解决了丢包的问题,但也带来了一个新的问题:如果每次丢包都要等200ms或者更长时间,那应用不是就不能及时处理了吗?特别是对于有些时间敏感型的应用来说,影响更为严重。
所以,TCP会用另外一种方式来解决超时重传带来的时间空耗的问题,就是用**快速重传**。在这个机制里,一旦发送方收到3次重复确认(加上第一次确认就一共是4次),就不用等超时计时器了,直接重传这个报文。
### 快速恢复
这是TCP Reno算法引入的一个阶段,它是跟随快速重传一起工作的。跟之前的“慢启动->拥塞避免->慢启动->拥塞避免”这种做法不同的是,在遇到拥塞点之后,通过快速重传,就不再进入慢启动,**而是从这个减半的拥塞窗口开始**,保持跟拥塞避免一样的线性增长,直到遇到下一个拥塞点。你可以参考下面的图片来理解,橙色线就是快速恢复阶段:
![](https://static001.geekbang.org/resource/image/34/54/34cf2de4yy170ayyc8cd107efeacbb54.jpg?wh=2000x1125)
那么,是不是有了快速重传和快速恢复,传输过程中都不用反复进入慢启动了呢?其实并不是这样。如果遇到超时,也一样要回到慢启动阶段,重新开始。
## 回到案例
好了,拥塞控制相关的知识点告一段落。这里,正好我们结合实际案例,来学习一下这部分知识。
在这次从北京传输文件到上海的过程中,有没有慢启动呢?要查看这个信息,用Wireshark的TCP Stream Graphs再合适不过了。我们打开Statistics菜单下的TCP Stream Graphs -> Time Sequence (Stevens),得到下图:
![图片](https://static001.geekbang.org/resource/image/ec/9f/ece79da2cf0f158bb5d7f5a322732e9f.jpg?wh=849x767)
这里的斜率还是比较平稳的,说明虽然有很多乱序和重传,但整体速度还算稳定。当然,如果我们放大这条线,就能看到不同的景象了。比如放大到前10秒,明显曲线的波动幅度加大了不少:
![图片](https://static001.geekbang.org/resource/image/f9/be/f99c0dd7032fda26942946d028e19abe.jpg?wh=853x765)
我们可以看到,在起点的时候,这条曲线的斜率是很高的,过了0.5秒以后开始平缓,然后到了2.5秒开始又陡峭了,然后大体上在循环着这个过程。这样看来,你可能会想:“陡的部分都是在慢启动吗?”
整体来说,确实可以这么理解。陡的部分是慢启动过程,所以斜率比较高;缓的部分是拥塞避免阶段,斜率比较低一些。
所以我们也发现,**慢启动不是只在TCP连接启动时候发生,而是可能在传输过程中发生多次**。前面的示意图里就显示,一旦拥塞避免阶段探测到了拥塞,TCP还是会回到慢启动过程,只是这次的慢启动阈值跟之前的不同。然后如果多次遇到拥塞,就会重复这个过程,直到传输结束。
而要查看最初的慢启动的过程(最初的慢启动也称为冷启动,Cold Start),还得继续放大。比如下图中,我们在0~0.4秒区间内,才终于明显找到它了。
![图片](https://static001.geekbang.org/resource/image/9c/30/9cf144995d235700b3546a31f3341930.jpg?wh=791x798)
这里我用红色方框标注出了每一次往返时间(RTT),那么在每个RTT内,22端口都连续发送了很多报文给到客户端,每次报文的序列号都是图上的一个点。显然,每个RTT后,发送的报文数量,都比前一轮RTT里发送的更多。
那你可能会有疑问:为什么这里的慢启动,并没有严格按照“翻倍”这样的原则来进行,比如说第二个红框的高度应该是第一个红框高度的两倍,但为什么图上并不是这样呢?
我个人的理解是这样的:在慢启动阶段,并不一定是“每次RTT就翻倍”,也可能会比翻倍低一些。这是因为,**在慢启动阶段,TCP每收到一个ACK,拥塞窗口就增加一个MSS**。假设初始拥塞窗口是2 MSS,发送2个数据报文后:
* 收到1个ACK(也就是间隔确认),那么在这次RTT内,拥塞窗口就变成了3 MSS,这是“不翻倍”的。
* 收到2个ACK,那么在这次RTT内,拥塞窗口就变成了4 MSS,这是“翻倍”的。
然后我们再看一下拥塞。在这个Graph中,也明显有几个点出现了异常。因为Y轴代表序列号,那么这些低处的点位,**它们的序列号自然就是比前一个更低(也就是更小)的值,说明这些是老的报文再次发了出去,所以可以判断为是重传**。
![图片](https://static001.geekbang.org/resource/image/b1/ea/b1f5314f1616ffb8b04497f9e0933eea.jpg?wh=789x658)
比如我们选中最下面这个点。这里的点都比较小,定位起来稍麻烦一些。你如果没有一点外科医生的手艺,那还是多一些耐心吧,慢慢移动鼠标才能定位到它。
![图片](https://static001.geekbang.org/resource/image/2d/b2/2dbce0ed33a895dd6b327938a43d50b2.jpg?wh=1153x889)
定位到这个点之后,发现它是201号报文。在主窗口里,我们可以看到,201号报文是一个TCP Fast Retransmission,也就是快速重传报文。那么通过这个重传,TCP拥塞控制机制就感知到了拥塞,然后进入了拥塞避免阶段。
好了,拥塞的知识点介绍得差不多了,案例也快结束了。那么案例的结论又是什么呢?
其实就是**专线上的限速设置失误**造成的。这个限速应该是通过网络硬件设备完成的,它单位时间内只允许一定字节数的报文通过,如果超过限制,就会丢弃这些报文。
现在我们学习了TCP拥塞控制机制,应该已经明白了:丢包对于发送端来说就是“拥塞”,然后它就根据拥塞控制机制,主动进入拥塞避免阶段,以确保传输速度,不至于大面积丢包。事实上就自动降低了速率,达到了我们想要的“限速”的效果。
我们的同事把这个限速设置值给搞错了,并不是客户购买的那个带宽值。修正之后就解决了问题。
虽然说这个排查很简单,不过通过这些抓包文件的拆解分析,还是可以对我们理解TCP拥塞控制的机制,有很大的帮助。
## 实验一下
拥塞控制机制对TCP传输十分重要,技术细节也很复杂,所以是由内核实现的。这也是内核实现TCP栈的巨大优势:应用程序可以集中于业务逻辑,而不需要操心传输和拥塞这种底层细节了。
那么,这是不是意味着,TCP拥塞这个东西有点“高冷”,咱们也就看看,评论一下,好像啥都做不了?
并不是,接下来的实验,就是可以改变一些拥塞控制行为的。
### 实验1:修改初始拥塞窗口
有些时候,我们也有修改拥塞行为的需求。比如,修改初始拥塞窗口(ICW)。这样,当你认为某条链路网络状况比较糟糕,用更低的ICW更合理时,就可以这么做。
在Linux操作系统上,修改初始拥塞窗口的方法是这样的:
* 运行`ip route`命令,找到当前的路由条目,把整行都进行复制,记为item:
```plain
$ ip route
default via 10.0.2.2 dev enp0s3 proto dhcp src 10.0.2.15 metric 100
```
* 运行`ip route change item initcwnd n`,把路由项item的初始拥塞窗口修改为n,比如改为2:
```plain
$sudo ip route change default via 10.0.2.2 dev enp0s3 proto dhcp src 10.0.2.15 metric 100 initcwnd 2
```
### 实验2:改变TCP拥塞控制算法
有时候,我们还需要调整TCP拥塞控制算法。在Linux里,我们可以通过 **sysctl命令**,查看或者修改这个算法。比如,Linux默认是用cubic算法,那你可以运行下面这条命令,看看是不是这样?
```plain
$ sysctl net.ipv4.tcp_congestion_control
net.ipv4.tcp_congestion_control = cubic
```
那如果你想用最新的BBR算法,该怎么做呢?如果内核大于4.9,Linux里就已经默认带有BBR了。执行下面的命令即可:
```plain
$ sudo sysctl net.ipv4.tcp_congestion_control=bbr #配置拥塞算法为BBR
net.ipv4.tcp_congestion_control = bbr
$ sudo sysctl net.core.default_qdisc=fq #调整缓存队列算法
net.core.default_qdisc = fq
```
> 补充:把这些配置写入到/etc/sysctl.conf,即使机器重启,配置也会保持不变。
TCP BBR拥塞控制算法是Google于2016年提出的新的算法。它的开发背景是,当今网络设备的缓存越来越大,导致丢包这个行为不像以前缓存小的时代那么频繁,但是报文延迟的问题比以前严重了。所以,要更加准确地探测拥塞,我们应该更多地关注延迟,并基于延迟的变化作出拥塞窗口的调整。
## 小结
这节课,我们学习了TCP传输中非常核心的一块内容:拥塞控制。拥塞控制的实现,主要依靠这几个环节。
* **慢启动**:每收到一个ACK,拥塞窗口(CW)增加一个MSS。
* **拥塞避免**:策略是“和性增长乘性降低”,每一个RTT,CW增加一个MSS。
* **快速重传**:接收到3次或者以上的重复确认后,直接重传这个丢失的报文。
* **快速恢复**:结合快速重传,在遇到拥塞点后,跳过慢启动阶段,进入线性增长。
另外,我们也复习了拥塞窗口(CW)和接收窗口(RW)是如何决定了传输速度上限的,简单来说:
* 当RW<CW时,速度由RW决定;
* 当RW>CW时,速度由CW决定。
然后,我们也知道了拥塞窗口(CW)是每条连接分开各自维护的,以及初始拥塞窗口(ICW)的概念,并且知道,从Linux 3.0内核开始,ICW已经提升到10个MSS。
在Wireshark使用技巧上,你也要清楚如何用 **TCP Stream Graphs** 的Time sequence (Stevens)小工具,来观察慢启动和拥塞避免等现象,包括其中发生的快速重传等行为,都可以在图上看到,这非常有利于你的排查工作。
除此之外,我们也可以对拥塞控制做一些调整:
* 用`ip route change`命令,调整某个网卡接口的初始拥塞窗口。
* 用`sysctl net.ipv4.tcp_congestion_control`命令,查看和修改内核使用的拥塞控制算法。
## 思考题
你在工作中有没有遇到拥塞引起的问题,或者有没有在抓包分析过程中,观察到过拥塞现象呢?欢迎在留言区分享你的经验,我们一同成长、进步。
## 附件
抓包示例文件:[https://gitee.com/steelvictor/network-analysis/tree/master/11](https://gitee.com/steelvictor/network-analysis/tree/master/11)