TCP实战一(三握四挥、流量控制)

上一篇博文已经介绍了tcpdump的一些基本操作与命令,今天这篇博文将带你解密如何利用wireshark对tcpdump抓到的数据包进行可视化分析!

参考文献:https://zhuanlan.zhihu.com/p/142665708

目录

1.Wireshark可视化分析ping过程(分析ICMP协议)

2.Wireshark可视化分析TCP三次握手以及四次挥手过程

3.TCP三次握手异常情况实战分析

4.Wireshark可视化分析TCP流量控制

本篇博文的精华:

Q1:TCP 第一次握手 SYN 丢包,客户端的操作是什么?

通过实验一的实验结果,我们可以得知,当客户端发起的 TCP 第一次握手 SYN 包,如果在初始RTO内没收到服务端的 ACK,就会重传 SYN 数据包,通常情况下初始RTO的值都为1,随后,每次的RTO值都是前一次的2倍,直到 SYN 包的重传次数到达 tcp_syn_retries 值后,客户端不再发送 SYN 包。(但是通常情况下你也不能把tcp_syn_retries 的值设置的过大,博主尝试过当此值设置为8以上时,效果都和8一样)

Q2:TCP 第二次握手 SYN、ACK 丢包,服务端与客户端分别进行什么操作?

通过实验二的实验结果,我们可以得知,当第二次握手的 SYN、ACK 丢包时,客户端会超时重发 SYN 包,服务端也会发送两次SYN+ACK 包,第一个 SYN+ACK 数据包是响应客户端超时重传的 SYN 数据包,第二个SYN+ACK 数据包则是超时重传的。

客户端 SYN 包超时重传的最大次数,是由 tcp_syn_retries 决定的,默认值是 5 次;服务端 SYN、ACK 包时重传的最大次数,是由 tcp_synack_retries 决定的,默认值是 5 次。且每次的 RTO 都是上一次的2倍。

参考:RTO的理解以及计算方法

Q3:TCP 第三次握手 ACK 丢包,客户端和服务端分别有什么操作

在建立 TCP 连接时,如果第三次握手的 ACK,服务端无法收到,则服务端就会短暂处于 SYN_RECV 状态,而客户端会处于 ESTABLISHED 状态。

由于服务端一直收不到 TCP 第三次握手的 ACK,则会一直重传 SYN、ACK 包,直到重传次数超过 tcp_synack_retries 值(默认值 5 次)后,服务端就会断开 TCP 连接。

而客户端则会有两种情况:

  • 如果客户端没发送数据包,一直处于 ESTABLISHED 状态,然后经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接,于是客户端连接就会断开连接。
  • 如果客户端发送了数据包,一直没有收到服务端对该数据包的确认报文,则会一直重传该数据包,直到重传次数超过 tcp_retries2 值(默认值 15 次)后,客户端就会断开 TCP 连接。

1.Wireshark可视化分析ping过程(分析ICMP协议)

Wireshark 除了可以抓包外,还提供了可视化分析网络包的图形页面,同时,还内置了一系列的汇总分析工具。

首先,我们以 ping 例子来说,我们可以使用下面的命令,把抓取的数据包保存到 tcpdata.pcap 文件

tcpdump -nn -v -c 5 icmp -w /home/xx/桌面/tcpdata.pcap

接着把 ping.pcap 文件拖到电脑,再用 Wireshark 打开它。打开后,你就可以看到下面这个界面:

 是吧?在 Wireshark 的页面里,可以更加直观的分析数据包,不仅展示各个网络包的头部信息,还会用不同的颜色来区分不同的协议,由于这次抓包只有 ICMP 协议,所以只有紫色的条目。

接着,在网络包列表中选择某一个网络包后,在其下面的网络包详情中,可以更清楚的看到,这个网络包在协议栈各层的详细信息。比如,以编号 1 的网络包为例子:

 

  • 可以在数据链路层,看到 MAC 包头信息,如源 MAC 地址和目标 MAC 地址等字段;
  • 可以在 IP 层,看到 IP 包头信息,如源 IP 地址和目标 IP 地址、TTL、IP 包长度、协议等 IP 协议各个字段的数值和含义;
  • 可以在 ICMP 层,看到 ICMP 包头信息,比如 Type、Code 等 ICMP 协议各个字段的数值和含义;

以下这张图是IP头部信息与ICMP头部信息,可以与上图对照进行分析

 Wireshark 用了分层的方式,展示了各个层的包头信息,把“不可见”的数据包,清清楚楚的展示了给我们,从 ping 的例子中,我们可以看到网络分层就像有序的分工,每一层都有自己的责任范围和信息,上层协议完成工作后就交给下一层,最终形成一个完整的网络包。

2.Wireshark可视化分析TCP三次握手以及四次挥手过程

既然学会了 tcpdump 和 Wireshark 两大网络分析利器,那我们快马加鞭,接下用它俩抓取和分析 HTTP 协议网络包,并理解 TCP 三次握手和四次挥手的工作原理。

本次例子,我们将要访问的依旧是 www.baidu.com 。在终端一用 tcpdump 命令抓取数据包,并利用curl命令访问www.baidu.com网址。

在tcpdump抓取数据包的终端中按下 Ctrl+C 停止 tcpdump,并把得到的 http_info.pcap 取出到电脑。

使用 Wireshark 打开 http_info.pcap 后,你就可以在 Wireshark 中,看到如下的界面:

 

我们都知道 HTTP 是基于 TCP 协议进行传输的,那么:

  • 最开始的 3 个包就是 TCP 三次握手建立连接的包
  • 中间是 HTTP 请求和响应的包
  • 而最后的 4 个包则是 TCP 断开连接的挥手包

Wireshark 可以用时序图的方式显示数据包交互的过程,从菜单栏中,点击 统计 (Statistics) -> 流量图 (Flow Graph),然后,在弹出的界面中的「流量类型」选择 「TCP Flows」,你可以更清晰的看到,整个过程中 TCP 流的执行过程:

 

 为什么三次握手连接过程的 Seq 是 0 ?

其实是因为Wireshark 工具帮我们做了优化,它默认显示的是序列号 seq 是相对值,而不是真实值。

如果你想看到实际的序列号的值,可以右键菜单, 然后找到「协议首选项」,接着找到「Relative Seq」后,把它给取消。

取消后的流量图如下(也就是真实的seq值):

可见,客户端和服务端的序列号实际上是不同的,并且序列号初始是一个随机值。这其实跟我们书上看到的 TCP 三次握手和四次挥手基本上是一致的。

有关tcp三次握手、四次挥手可以查看我的博客:备战研发岗—计算机网络

在此以图的形式简单回顾一下整个过程:

为什么有些情况下抓到的 TCP 挥手是三次,而不是书上说的四次?例如:

因为服务器端收到客户端的 FIN 后,服务器端同时也要关闭连接,这样就可以把 ACK 和 FIN 合并到一起发送,节省了一个包,变成了“三次挥手”。

而通常情况下,服务器端收到客户端的 FIN 后,很可能还没发送完数据,所以就会先回复客户端一个 ACK 包,稍等一会儿,完成所有数据包的发送后,才会发送 FIN 包,这也就是四次挥手了。

3.TCP三次握手异常情况实战分析

 TCP三次握手的过程也许大伙都背的滚瓜烂熟了,打你有没有想过在三次握手的过程中可能会发生一下几个问题:

  • TCP 第一次握手的 SYN 丢包了,会发生了什么?
  • TCP 第二次握手的 SYN、ACK 丢包了,会发生什么?
  • TCP 第三次握手的 ACK 包丢了,会发生什么?

不就是丢包嘛,重传数据包不就好了嘛,真的是这么简单吗?那你能回答出以下的问题吗

  • 那会重传几次?
  • 超时重传的时间 RTO 会如何变化?
  • 在 Linux 下如何设置重传次数?
  • ....

接下来用三个实验案例带你解密整个过程,帮助你全面剖析TCP三次握手。

实验环境

在VMware上部署两个虚拟机,一台当作客户机,一台当作服务器。两台虚拟机关系如下图:

  • 客户端和服务端都基于 CentOs 6.5 Linux,Linux 内核版本 2.6.32
  • 服务端 192.168.127.150,apache web 服务
  • 客户端 192.168.127.151
  • curl是一个命令行工具,通过指定的URL来上传或下载数据,并将数据展示出来。curl中的c表示client,而URL,就是URL.
  • Telnet协议是TCP/IP协议家族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。Telnet是常用的远程控制Web服务器的方法。

有些人可能不懂如何在服务端部署apache web服务,详情见链接:CentOS6.5环境下搭建Apache httpd服务器

3.1 实验一:TCP 第一次握手 SYN 丢包

为了模拟 TCP 第一次握手 SYN 丢包的情况,我的步骤如下:

  • 客户端运行tcpdump命令,并利用curl指令成功连上服务端。
  • 切换到服务端,立马拔掉服务端的网线(可以采用ifdown  网卡名的命令进行操作)
  • 最后切换到客户端,再次运行curl指令

 

从上图可以看出centos6.5(内核版本 2.6.32)参数tcp_syn_retries的默认值为5,为了实验效果我们把该值设置为6。

按照上述的实验步骤,最终得到的实验结果如下图所示:

从上图可以发现, 客户端发起了 SYN 包后,一直没有收到服务端的 ACK ,基于超时重传机制所以客户端根据tcp_syn_retries的值(6)重传了 6 次syn数据包(一共包含7个Flags[S],其中第一个是正常发送SYN数据包,后面6个为超时重传发送的syn数据包),并且每次重传syn数据包的间隔时间是不同的并且是有规律的:1,2,4,8,16,32.centos6.5默认RTO(Retransmission Timeout)的初始值为1,

RTO指的是,发送数据包在一定的时间周期内没有收到相应的ACK,等待一定的时间,超时之后就认为这个数据包丢失,就会重新发送。这个等待时间被称为RTO.  

随后,每次的RTO值都是前一次的2倍。当超过最大重传次数后,客户端不再发送 SYN 包。

第六次发送syn数据包,需要等待64s才知道该连接也超时了,此时重发次数已达到tcp_syn_retries的值,所以最后在21:56:14(发送第一个syn数据包的时间) + (1+2+4+8+16+32+64) = 21: 58:21curl命令返回连接失败。

实验一的实验小结

Q:TCP 第一次握手 SYN 丢包,客户端的操作是什么?

通过实验一的实验结果,我们可以得知,当客户端发起的 TCP 第一次握手 SYN 包,如果在初始RTO内没收到服务端的 ACK,就会重传 SYN 数据包,通常情况下初始RTO的值都为1,随后,每次的RTO值都是前一次的2倍,直到 SYN 包的重传次数到达 tcp_syn_retries 值后,客户端不再发送 SYN 包。(但是通常情况下你也不能把tcp_syn_retries 的值设置的过大,博主尝试过当此值设置为8以上时,效果都和8一样)


记录一下:在没有翻墙的情况下,博主利用客户机curl命令访问外网例如谷歌、youtube等等,按理来说在tcp_syn_retries = 6的情况下,tcpdump指令应该抓到7个syn数据包,但是实验结果如下所示:

在tcp_syn_retries = 3的情况下,实验结果如下:

综上所述,当你在客户端利用curl命令链接外网,tcp_syn_retries的有效值最大为4。

  • 意思就是如果你设置的tcp_syn_retries的值大于4,tcpdump命令只能抓到客户端重复发送4次syn数据包,并且发送完4次syn数据包后,客户端还会接收到服务端发送的RST+ACK包。
  • 如果你设置的tcp_syn_retries的值小于4,则tcpdump命令就只会抓到客户端重复发送 tcp_syn_retries的值 次syn数据包,并且在距离第一次发送syn数据包21秒后,客户端接收到服务端发送的RST+ACK。包。

原因可能在于linux内核版本升级了,所以对此进行了优化(这是我的猜想)。

3.2 实验二:TCP 第二次握手 SYN、ACK 丢包

为了模拟客户端收不到服务端第二次握手 SYN、ACK 包,我的做法是在客户端加上防火墙限制,直接粗暴的把来自服务端的数据都丢弃,防火墙的配置如下:

接着在客户端运行tcpdump命令进行数据包的抓取

 最后在客户端执行 curl 指令:

由上图date命令间隔可知:运行curl命令前后花了 63s curl报错退出了。有些人可能立马就会联想到63s不正是实验一客户端5次重发syn数据包的总时间吗(1+2+4+8+16)+32 = 63s.实验二不是探讨TCP 第二次握手 SYN、ACK 丢包吗,这与实验一有关系吗?不急,接下来就带你解密。

我们把客户端抓取的数据包利用Wireshark进行分析:

  • 第1行:客户端成功向服务端发送syn数据包
  • 第2行:Service成功接收到来自Client端发送syn数据包,所以向Client端发送 syn+ack 数据包
  • 由于我们在客户端加上防火墙限制,直接粗暴的把来自服务端的数据都丢弃,所以Client端是收不到来自Service端的ACK+SYN数据包。
  • 第3行:Client端又重新向Service端发送一个seq相同的syn数据包。
  • 因为距离第一次发送syn数据包已经过去了1s,这1s正好满足RTO的初始值,此时Client端认为第一次发送syn数据包超时了,所以进行第一次超时重传。
  • 第4行:在第2行Service端向Client端发送ACK+SYN数据包后,没有收到来自Client端的ACK数据包,所以选择了超时重传ACK+SYN数据包。
  • 第5行:Client端超时重传的 SYN 包又抵达了服务端,服务端收到后,重传定时器就重新计时,然后向Client端回了 SYN、ACK 包。
  • 所以出现了2次Service端向Client端发送ACK+SYN数据包。
  • 第6-17行重复上述过程。
  • 最后,Client端重发syn数据包达到了5次 (tcp_syn_retries 默认值 5 次),就不会再发送syn数据包了。

根据以上两张图片,可以发现

  • 客户端发起 SYN 后,由于防火墙屏蔽了服务端的所有数据包,所以 curl 是无法收到服务端的 SYN、ACK 包,当发生超时后,就会重传 SYN 包
  • 服务端收到客户的 SYN 包后,就会回 SYN、ACK 包,但是客户端一直没有回 ACK,服务端在超时后,重传了 SYN、ACK 包,接着一会,客户端超时重传的 SYN 包又抵达了服务端,服务端收到后,超时定时器就重新计时,然后回了 SYN、ACK 包,所以相当于服务端的超时定时器只触发了一次,又被重置了。
  • 最后,客户端 SYN 超时重传次数达到了 5 次(tcp_syn_retries 默认值 5 次),就不再继续发送 SYN 包了。

所以,我们可以发现,当第二次握手的 SYN、ACK 丢包时,客户端会超时重发 SYN 包,服务端也会发送两次SYN+ACK 包第一个 SYN+ACK 数据包是响应客户端超时重传的 SYN 数据包,第二个SYN+ACK 数据包则是超时重传的。

Q:客户端设置了防火墙,屏蔽了服务端的网络包,为什么 tcpdump 还能抓到服务端的网络包?

添加 iptables 限制后, tcpdump 是否能抓到包 ,这要看添加的 iptables 限制条件:

  • 如果添加的是 INPUT 规则,则可以抓得到包
  • 如果添加的是 OUTPUT 规则,则抓不到包

网络包进入主机后的顺序如下:

  • 进来的顺序 Wire(网线) -> NIC(网卡) -> tcpdump -> netfilter/iptables(防火墙)
  • 出去的顺序 iptables -> tcpdump -> NIC -> Wire

Q:tcp_syn_retries 是限制 SYN 重传次数,那第二次握手 SYN、ACK 限制最大重传次数是多少?

如图所示,TCP 第二次握手 SYN、ACK 包的最大重传次数默认值是 5 次。

为了验证 SYN、ACK 包最大重传次数是 5 次,我们继续做下实验,我们先把客户端的 tcp_syn_retries 设置为 1,表示客户端 SYN 最大超时次数是 1 次,目的是为了防止多次重传 SYN,把服务端 SYN、ACK 超时定时器重置。

接着,还是如上面的步骤:

  • 客户端配置防火墙屏蔽服务端的数据包
  • 客户端 tcpdump 抓取 curl 执行时的数据包

把抓取的数据包,用 Wireshark 打开分析,显示的时序图如下:

从上图,我们可以分析出:

  • 客户端的 SYN 只超时重传了 1 次,因为 tcp_syn_retries 值为 1
  • 服务端应答了客户端超时重传的 SYN 包后,由于一直收不到客户端的 ACK 包,所以服务端一直在超时重传 SYN、ACK 包,每次的 RTO 也是指数上涨的,一共超时重传了 5 次,因为 tcp_synack_retries 值为 5

接着,我把 tcp_synack_retries 设置为 2,tcp_syn_retries 依然设置为 1:

$ echo 2 > /proc/sys/net/ipv4/tcp_synack_retries
$ echo 1 > /proc/sys/net/ipv4/tcp_syn_retries

依然保持一样的实验步骤进行操作,接着把抓取的数据包,用 Wireshark 打开分析,显示的时序图如下:

可见:

  • 客户端的 SYN 包只超时重传了 1 次,符合 tcp_syn_retries 设置的值;
  • 服务端的 SYN、ACK 超时重传了 2 次,符合 tcp_synack_retries 设置的值

实验二的实验小结

Q:TCP 第二次握手 SYN、ACK 丢包,服务端与客户端分别进行什么操作?

通过实验二的实验结果,我们可以得知,当第二次握手的 SYN、ACK 丢包时,客户端会超时重发 SYN 包,服务端也会发送两次SYN+ACK 包第一个 SYN+ACK 数据包是响应客户端超时重传的 SYN 数据包,第二个SYN+ACK 数据包则是超时重传的。

客户端 SYN 包超时重传的最大次数,是由 tcp_syn_retries 决定的,默认值是 5 次;服务端 SYN、ACK 包时重传的最大次数,是由 tcp_synack_retries 决定的,默认值是 5 次。且每次的 RTO 都是上一次的2倍。

参考:RTO的理解以及计算方法

3.3 实验三:TCP 第三次握手 ACK 丢包

为了模拟 TCP 第三次握手 ACK 包丢,我的实验方法是在服务端配置防火墙,屏蔽客户端 TCP 报文中标志位是 ACK 的包,也就是当服务端收到客户端的 TCP ACK 的报文时就会丢弃,iptables 配置命令如下:

接着,在客户端执行如下 tcpdump 命令:

然后,客户端向服务端发起 telnet,因为 telnet 命令是会发起 TCP 连接,所以用此命令做测试:

此时,由于服务端收不到第三次握手的 ACK 包,所以一直处于 SYN_RECV 状态:

而客户端是已完成 TCP 连接建立,处于 ESTABLISHED 状态:

过了 一阵子,观察发现服务端的 TCP 连接不见了:

但是客户端依然还是处于 ESTABLISHED 状态:

接着,在刚才客户端建立的 telnet 会话,输入 123456 字符,进行发送:

持续「好长」一段时间,客户端的 telnet 才断开连接:

以上就是本次的实现三的现象,这里存在两个疑点:

  • 为什么服务端原本处于 SYN_RECV 状态的连接,过 一阵子后就消失了?
  • 为什么客户端 telnet 输入 123456 字符后,过了好长一段时间,telnet 才断开连接?

不着急,我们把刚抓的数据包,用 Wireshark 打开分析,显示的时序图如下:

上图的流程如下:

  • 客户端发送 SYN 包给服务端,服务端收到后,回了个 SYN、ACK 包给客户端,此时服务端的 TCP 连接处于 SYN_RECV 状态;
  • 客户端收到服务端的 SYN、ACK 包后,给服务端回了个 ACK 包,此时客户端的 TCP 连接处于 ESTABLISHED 状态;
  • 由于服务端配置了防火墙,屏蔽了客户端的 ACK 包,所以服务端一直处于 SYN_RECV 状态,没有进入 ESTABLISHED 状态,tcpdump 之所以能抓到客户端的 ACK 包,是因为数据包进入系统的顺序是先进入 tcpudmp,后经过 iptables;
  • 接着,服务端超时重传了 SYN、ACK 包,重传了 5 次后,也就是超过 tcp_synack_retries 的值(默认值是 5),然后就没有继续重传了,此时服务端的 TCP 连接主动中止了,所以刚才处于 SYN_RECV 状态的 TCP 连接断开了,而客户端依然处于ESTABLISHED 状态;
  • 虽然服务端 TCP 断开了,但过了一段时间,发现客户端依然处于ESTABLISHED 状态,于是就在客户端的 telnet 会话输入了 123456 字符;
  • 此时由于服务端已经断开连接,客户端发送的数据报文,一直在超时重传,每一次重传,RTO 的值是指数增长的,所以持续了好长一段时间,客户端的 telnet 才报错退出了,此时共重传了 15 次。

通过这一波分析,刚才的两个疑点已经解除了:

  • 服务端在重传 SYN、ACK 包时,超过了最大重传次数 tcp_synack_retries,于是服务端的 TCP 连接主动断开了。
  • 客户端向服务端发送数据包时,由于服务端的 TCP 连接已经退出了,所以数据包一直在超时重传,共重传了 15 次, telnet 就 断开了连接。

Q:TCP 第一次握手的 SYN 包超时重传最大次数是由 tcp_syn_retries 指定,TCP 第二次握手的 SYN、ACK 包超时重传最大次数是由 tcp_synack_retries 指定,那 TCP 建立连接后的数据包最大超时重传次数是由什么参数指定呢?

TCP 建立连接后的数据包传输,最大超时重传次数是由 tcp_retries2 指定,默认值是 15 次,如下:

$ cat /proc/sys/net/ipv4/tcp_retries2
15

如果 15 次重传都做完了,TCP 就会告诉应用层说:“搞不定了,包怎么都传不过去!”

Q:那如果客户端不发送数据,什么时候才会断开处于 ESTABLISHED 状态的连接?

这里就需要利用到TCP的保活机制,保活机制的原理如下:

定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个「探测报文」,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。

在Centos6.5(linux内核版本2.6)可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75  
net.ipv4.tcp_keepalive_probes=9
  • tcp_keepalive_time = 7200:表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何连接相关的活动,则会启动保活机制
  • tcp_keepalive_intvl = 75:表示每次检测间隔 75 秒;
  • tcp_keepalive_probes = 9:表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接。

也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接。

这个时间是有点长的,所以如果我抓包足够久,或许能抓到探测报文。

实验三小结:

Q:TCP 第三次握手 ACK 丢包,客户端和服务端分别有什么操作

在建立 TCP 连接时,如果第三次握手的 ACK,服务端无法收到,则服务端就会短暂处于 SYN_RECV 状态,而客户端会处于 ESTABLISHED 状态。

由于服务端一直收不到 TCP 第三次握手的 ACK,则会一直重传 SYN、ACK 包,直到重传次数超过 tcp_synack_retries 值(默认值 5 次)后,服务端就会断开 TCP 连接。

而客户端则会有两种情况:

  • 如果客户端没发送数据包,一直处于 ESTABLISHED 状态,然后经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接,于是客户端连接就会断开连接。
  • 如果客户端发送了数据包,一直没有收到服务端对该数据包的确认报文,则会一直重传该数据包,直到重传次数超过 tcp_retries2 值(默认值 15 次)后,客户端就会断开 TCP 连接。

4.Wireshark可视化分析TCP流量控制

TCP 为了防止发送方无脑的发送数据,导致接收方缓冲区被填满,所以就有了滑动窗口的机制,它可利用接收方的接收窗口来控制发送方一次性要发送的数据量,也就是流量控制。

接收窗口是由接收方指定的值,存储在 TCP 头部中,它可以告诉发送方自己的 TCP 缓冲空间区大小,这个缓冲区是给应用程序读取数据的空间:

  • 如果应用程序读取了缓冲区的数据,那么缓冲空间区的就会把被读取的数据移除
  • 如果应用程序没有读取数据,则数据会一直滞留在缓冲区。

假设接收方接收到数据后,应用层能很快的从缓冲区里读取数据,那么窗口大小会一直保持不变,过程如下:

但是现实中服务器会出现繁忙的情况,当应用程序读取速度慢,那么缓存空间会慢慢被占满,于是为了保证发送方发送的数据不会超过缓冲区大小,服务器则会调整窗口大小的值,接着通过 ACK 报文通知给对方,告知现在的接收窗口大小,从而控制发送方发送的数据大小。

零窗口通知与窗口探测

假设接收方处理数据的速度跟不上接收数据的速度,缓存就会被占满,从而导致接收窗口为 0,当发送方接收到零窗口通知时,就会停止发送数据。

如下图,可以接收方的窗口大小在不断的收缩至 0:

接着,发送方会定时发送窗口大小探测报文,以便及时知道接收方窗口大小的变化。

以下图 Wireshark 分析图作为例子说明:

  • 发送方发送了数据包 1 给接收方,接收方收到后,由于缓冲区被占满,回了个零窗口通知;
  • 发送方收到零窗口通知后,就不再发送数据了,直到过了 3.4 秒后,发送了一个 TCP Keep-Alive 报文,也就是窗口大小探测报文;
  • 当接收方收到窗口探测报文后,就立马回一个窗口通知,但是窗口大小还是 0;
  • 发送方发现窗口还是 0,于是继续等待了 6.8(翻倍) 秒后,又发送了窗口探测报文,接收方依然还是回了窗口为 0 的通知;
  • 发送方发现窗口还是 0,于是继续等待了 13.5(翻倍) 秒后,又发送了窗口探测报文,接收方依然还是回了窗口为 0 的通知;

可以发现,这些窗口探测报文以 3.4s、6.5s、13.5s 的间隔出现,说明超时时间会翻倍递增。

这连接暂停了 25s,想象一下你在打王者的时候,25s 的延迟你还能上王者吗?

发送窗口的分析

(1) 在 Wireshark 看到的 Windows size 也就是 " win = ",这个值表示发送窗口吗?

这不是发送窗口,而是在向对方声明自己的接收窗口。

(2) 如何在包里看出发送窗口的大小?

很遗憾,没有简单的办法,发送窗口虽然是由接收窗口决定,但是它又可以被网络因素影响,也就是拥塞窗口,实际上发送窗口是值是 min(拥塞窗口,接收窗口)。

(3)  发送窗口和 MSS 有什么关系?MSS(Maximum Segment Size)最大报文段长度:表示TCP报文段中的数据字段的最大长度

发送窗口决定了一口气能发多少字节,而 MSS 决定了这些字节要分多少包才能发完。

举个例子,如果发送窗口为 16000 字节的情况下,如果 MSS 是 1000 字节,那就需要发送 1600/1000 = 16 个包。

(4) 发送方在一个窗口发出 n 个包,是不是需要 n 个 ACK 确认报文?

不一定,因为 TCP 有累计确认机制,所以当收到多个数据包时,只需要应答最后一个数据包的 ACK 报文就可以了。

猜你喜欢

转载自www.cnblogs.com/XDU-Lakers/p/13156880.html