最近因使用FTP 上传数据的时候总是不能成功,抓包后发现 TCP 报文出现 TCP dup ackTCP Retransmission 两种类型的包。收集整理下
TCP

TCP dup ack (重复应答)

[TCP dup ack XXX#X] 表示第几次重新请求某一个包,
XXX表示第几个包(不是Seq),
X表示第几次请求。
丢包或者乱序的情况下,会出现该标志。
图例中:第26548 第0 此请求 4468792 的包,重复 4申请了4次 ;

[TCP Fast Retransmission] (快速重传)

一般快速重传算法在收到三次冗余的Ack,即三次[TCP dup ack XXX#X]后,发送端进行快速重传。
为什么是三次呢?因为两次 duplicated ACK 肯定是乱序造成的,丢包肯定会造成三次 duplicated ACK
在这里插入图片描述
在这里插入图片描述

[TCP Retransmission] (超时重传)

超时重传,如果一个包的丢了,又没有后续包可以在接收方触发[Dup Ack],或者**[Dup Ack]也丢失**的情况下,TCP会触发超时重传机制。
超时重传

[TCP Out-Of-Order] (报文乱序)

[TCP Out-Of-Order]指的是TCP发送端传输过程中报文乱序了。

[TCP Previous segment not captured]

在TCP发送端传输过程中,该Seq前的报文缺失了。一般在网络拥塞的情况下,造成TCP报文乱序、丢包时,会出现该标志。
需要注意的是,[TCP Previous segment not captured]解析文字是wireshark添加的标记并非TCP报文内容

[TCP Out-Of-Order]

TCP发送端传输过程中报文乱序了。

Window

[TCP ZeroWindow]

作为接收方发出现的标志,表示接收缓冲区已经满了,此时发送方不能再发送数据,一般会做流控调整。接收窗口,也就是接收缓冲区win=xxx ,告诉对方接收窗口大小。

传输过程中,接收方TCP窗口满了,win=0,wireshark会打上[TCP ZeroWindow]标签

[TCP window update]

接收方消耗缓冲数据后,更新TCP窗口, 可以看到从win=0逐渐变大,这时**wireshark会打上[TCP window update]**标签

[TCP window Full]

作为发送方的标识,当前发送包的大小已经超过了接收端窗口大小,wireshark会打上此标识,标识不能在发送。

TCP慢启动

慢启动是TCP的一个拥塞控制机制,慢启动算法的基本思想是当TCP开始在一个网络中传输数据或发现数据丢失并开始重发时,首先慢慢的对网路实际容量进行试探,避免由于发送了过量的数据而导致阻塞。
  慢启动为发送方的TCP增加了另一个窗口:拥塞窗口(congestion window),记为cwnd。当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为 1个报文段(即另一端通告的报文段大小)。每收到一个ACK,拥塞窗口就增加一个报文段(cwnd以字节为单位,但是慢启动以报文段大小为单位进行增加)。发送方取拥塞窗口与通告窗口中的最小值作为发送上限。拥塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。发送方开始时发送一个报文段,然后等待 ACK。当收到该ACK时,拥塞窗口从1增加为2,即可以发送两个报文段。当收到这两个报文段的 A C K时,拥塞窗口就增加为4。这是一种指数增加的关系。

拥塞避免算法

拥塞避免

网络中拥塞的发生会导致数据分组丢失,需要尽量避免。在实际中,拥塞算法与慢启动通常在一起实现,其基本过程:
   1. 对一个给定的连接,初始化cwnd为1个报文段,ssthresh为65535个字节。
   2. TCP输出例程的输出不能超过cwnd和接收方通告窗口的大小。拥塞避免是发送方使用 的流量控制,而通告窗口则是接收方进行的流量控制。前者是发送方感受到的网络拥塞的估 计,而后者则与接收方在该连接上的可用缓存大小有关。
   3. 当拥塞发生时(超时或收到重复确认),ssthresh被设置为当前窗口大小的一半(cwnd 和接收方通告窗口大小的最小值,但最少为2个报文段)。此外,如果是超时引起了拥塞,则 cwnd被设置为1个报文段(这就是慢启动)。
   4. 当新的数据被对方确认时,就增加cwnd,但增加的方法依赖于是否正在进行慢启动或拥塞避免。如果cwnd小于或等于ssthresh,则正 在进行慢启动,否则正在进行拥塞避免。慢启动一直持续到回到当拥塞发生时所处位置的半时候才停止(因为记录了在步骤2 中制造麻烦的窗口大小的一半),然后转为执行拥塞避免。
   慢启动算法初始设置cwnd为1个报文段,此后每收到一个确认就加 1。那样,这会使窗口按指数方式增长:发送 1个报文段,然后是2个,接着是4个……。

TCP协议中的计时器

TCP中有四种计时器(Timer),分别为:

  1. 重传计时器:Retransmission Timer
  2. 坚持计时器:Persistent Timer
  3. 保活计时器:Keeplive Timer
  4. 时间等待计时器:Timer_Wait Timer

重传计时器

大家都知道TCP是保证数据可靠传输的。怎么保证呢?带确认的重传机制。在滑动窗口协议中,接受窗口会在连续收到的包序列中的最后一个包向接收端发送一个ACK,当网络拥堵的时候,发送端的数据包和接收端的ACK包都有可能丢失。TCP为了保证数据可靠传输,就规定在重传的“时间片”到了以后,如果还没有收到对方的ACK,就重发此包,以避免陷入无限等待中。
  当TCP发送报文段时,就创建该特定报文的重传计时器。可能发生两种情况:
  1.若在计时器截止时间到之前收到了对此特定报文段的确认,则撤销此计时器。
  2.若在收到了对此特定报文段的确认之前计时器截止时间到,则重传此报文段,并将计时器复位。

持久计时器

先来考虑一下情景:
  发送端向接收端发送数据包知道接受窗口填满了,然后接受窗口告诉发送方接受窗口填满了停止发送数据。此时的状态称为“零窗口”状态,发送端和接收端窗口大小均为0.直到接受TCP发送确认并宣布一个非零的窗口大小。但这个确认会丢失。我们知道TCP中,对确认是不需要发送确认的。 若确认丢失了,接受TCP并不知道,而是会认为他已经完成了任务,并等待着发送TCP接着会发送更多的报文段。但发送TCP由于没有收到确认,就等待对方发送确认来通知窗口大小。双方的TCP都在永远的等待着对方。
  要打开这种死锁,TCP为每一个链接使用一个持久计时器。当发送TCP收到窗口大小为0的确认时,就坚持启动计时器。当坚持计时器期限到时,发送TCP就发送一个特殊的报文段,叫做探测报文。这个报文段只有一个字节的数据。他有一个序号,但他的序号永远不需要确认;甚至在计算机对其他部分的数据的确认时该序号也被忽略。探测报文段提醒接受TCP:确认已丢失,必须重传。
  坚持计时器的值设置为重传时间的数值。但是,若没有收到从接收端来的响应,则需发送另一个探测报文段,并将坚持计时器的值加倍和复位。发送端继续发送探测报文段,将坚持计时器设定的值加倍和复位,直到这个值增大到门限值(通常是60秒)为止。在这以后,发送端每个60秒就发送一个探测报文,直到窗口重新打开。

保活计时器

保活计时器使用在某些实现中,用来防止在两个TCP之间的连接出现长时间的空闲。假定客户打开了到服务器的连接,传送了一些数据,然后就保持静默了。也许这个客户出故障了。在这种情况下,这个连接将永远的处理打开状态。
  要解决这种问题,在大多数的实现中都是使服务器设置保活计时器。每当服务器收到客户的信息,就将计时器复位。
  通常设置为两小时。若服务器过了两小时还没有收到客户的信息,他就发送探测报文段。若发送了10个探测报文段(每一个相隔75秒)还没有响应,就假定客户除了故障,因而就终止了该连接。
  这种连接的断开当然不会使用四次握手,而是直接硬性的中断和客户端的TCP连接。

时间等待计时器

时间等待计时器是在四次握手(挥手)的时候使用的。四次握手的简单过程是这样的:假设客户端准备中断连接,首先向服务器端发送一个FIN的请求关闭包(FIN=final),然后由 established 过渡到FIN-WAIT1状态。服务器收到FIN包以后会发送一个ACK,然后自己有established进入CLOSE-WAIT.此时通信进入半双工状态,即留给服务器一个机会将剩余数据传递给客户端,传递完后服务器发送一个FIN+ACK的包,表示我已经发送完数据可以断开连接了,就这便进入LAST_ACK阶段。客户端收到以后,发送一个ACK表示收到并同意请求,接着由FIN-WAIT2进入TIME-WAIT阶段。服务器收到ACK,结束连接。此时(即客户端发送完ACK包之后),客户端还要等待2MSL(MSL=maxinum segment lifetime最长报文生存时间,2MSL就是两倍的MSL)才能真正的关闭连接。
  四次挥手
  附加:
  
三次握手

$(function() { setTimeout(function () { var mathcodeList = document.querySelectorAll(‘.htmledit_views img.mathcode’); if (mathcodeList.length > 0) { var testImg = new Image(); testImg.onerror = function () { mathcodeList.forEach(function (item) { $(item).before(‘\\(’ + item.alt + ‘\\)’); $(item).remove(); }) MathJax.Hub.Queue([“Typeset”,MathJax.Hub]); } testImg.src = mathcodeList[0].src; } }, 1000) })

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐