图解 TCP 三次握手与四次分手

引言

TCP三次握手和四次挥手不论是在开发仍是面试中都是一个很是重要的知识点,它是咱们优化web程序性能的基础。可是大部分教材都对这部分解释的比较抽象,本文咱们就利用wireshark来抓包以真正体会整个流程的细节。html

三次握手

根据下面这幅图咱们来看一下TCP三次握手。p.s: 每一个箭头表明一次握手。web

tcp三次握手

第一次握手

client发送一个SYN(J)包给server,而后等待server的ACK回复,进入SYN-SENT状态。p.s: SYN为synchronize的缩写,ACK为acknowledgment的缩写。面试

第二次握手

server接收到SYN(seq=J)包后就返回一个ACK(J+1)包以及一个本身的**SYN(K)**包,而后等待client的ACK回复,server进入SYN-RECIVED状态。算法

第三次握手

client接收到server发回的ACK(J+1)包后,进入ESTABLISHED状态。而后根据server发来的SYN(K)包,返回给等待中的server一个ACK(K+1)包。等待中的server收到ACK回复,也把本身的状态设置为ESTABLISHED。到此TCP三次握手完成,client与server能够正常进行通讯了。shell

为何要进行三次握手

咱们来看一下为何须要进行三次握手,两次握手难道不行么?这里咱们用一个生活中的具体例子来解释就很好理解了。咱们能够将三次握手中的客户端和服务器之间的握手过程比喻成A和B通讯的过程:缓存

  • 在第一次通讯过程当中,A向B发送信息以后,B收到信息后能够确认本身的收信能力和A的发信能力没有问题。
  • 在第二次通讯中,B向A发送信息以后,A能够确认本身的发信能力和B的收信能力没有问题,可是B不知道本身的发信能力到底如何,因此就须要第三次通讯。
  • 在第三次通讯中,A向B发送信息以后,B就能够确认本身的发信能力没有问题。

wireshark

上面分析还不够形象,很容易忘记,下面咱们利用wireshark来证实一下上面的分析过程。从下面的的输出就能够很容易看出来,必需要通过前面的三次tcp请求才会有起一次http请求。服务器

第一次请求客户端发送一个SYN包,序列号是0。tcp

wireshark-tcp-01

第二次请求服务器会发送一个SYN和一个ACK包,序列号是0,ack号是1。ide

wireshark-tcp-02

第三次本地客户端请求会发送一个ACK包,序列号是1,ack号是1来回复服务器。性能

wireshark-tcp-03

四次挥手

如下面这张图为例,咱们来分析一下TCP四次挥手的过程。

tcp四次挥手

第一次挥手

client发送一个FIN(M)包,此时client进入FIN-WAIT-1状态,这代表client已经没有数据要发送了。

第二次挥手

server收到了client发来的FIN(M)包后,向client发回一个ACK(M+1)包,此时server进入CLOSE-WAIT状态,client进入FIN-WAIT-2状态。

第三次挥手

server向client发送FIN(N)包,请求关闭链接,同时server进入LAST-ACK状态。

第四次挥手

client收到server发送的FIN(N)包,进入TIME-WAIT状态。向server发送**ACK(N+1)**包,server收到client的ACK(N+1)包之后,进入CLOSE状态;client等待一段时间尚未获得回复后判断server已正式关闭,进入CLOSE状态。

TCP滑动窗口

通常在提到TCP三次握手的时候,一样会涉及到TCP滑窗,下面咱们补充一下什么是TCP滑窗。若是采用PAR的形式来传递的话,每一次发送方发送完包后必须获得接收方的确认回复,改进一下这个流程,发送端一次能够发送多个包,没必要每次等到接收方的ack回复,同时接收端也要告诉发送端本身能接收多少。固然还须要保证顺序性,对于乱序的情况,咱们能够容许等待必定时间的乱序,好比先缓存提早到达的数据,而后等待须要的数据,若是必定时间没有达到就drop掉。

TCP滑动窗口能够解决咱们上面提到的概念,滑动窗口中的数据主要分为下面几类:

  1. Sent and Acknowledged: 这些数据表示已经发送成功并已被确认的数据。
  2. Sent But Not Yet Acknowledged: 这部分数据称为发送但尚未被确认,数据被发送出去,没有收到接收端的ACK,认为并无彻底发送,这个属于窗口内的数据。
  3. Not Sent, Recipient Ready to Receive: 这部分是尽快发送的数据,这部分数据已经被加载到缓存中,也就是窗口中了,等待发送,接受端已经告诉发送端本身可以彻底接受这些包,因此发送方须要尽快发送这些包。
  4. Not Sent, Recipient Not Ready to Reccive: 这些数据属于未发送,由于这些数据已经超出了接收端所能接受的范围。

对于接收端也是有一个接收窗口,相似发送端,接收端的数据有三个分类(注意接收端并不要等待ACK):

  1. Received and ACK Not Send to Process: 这部分数据属于接收了数据可是尚未被上层的应用程序接收,也是被缓存在窗口中。
  2. Received Not ACK: 已经接收,可是尚未ACK。
  3. Not Received: 有空位可是尚未接收数据。

TCP重传机制

下面这部份内容参考自coolshell,我认为写的比较好,因此转过来分享一下,感兴趣的朋友能够阅读一下 :)

TCP要保证全部的数据包均可以到达,因此,必须要有重传机制。

注意,接收端给发送端的Ack确认只会确认最后一个连续的包,好比,发送端发了1,2,3,4,5一共五份数据,接收端收到了1,2,因而回ack 3,而后收到了4(注意此时3没收到),此时的TCP会怎么办?咱们要知道,由于正如前面所说的,SeqNum和Ack是以字节数为单位,因此ack的时候,不能跳着确认,只能确认最大的连续收到的包,否则,发送端就觉得以前的都收到了。

超时重传机制

一种是不回ack,死等3,当发送方发现收不到3的ack超时后,会重传3。一旦接收方收到3后,会ack 回 4——意味着3和4都收到了。

可是,这种方式会有比较严重的问题,那就是由于要死等3,因此会致使4和5即使已经收到了,而发送方也彻底不知道发生了什么事,由于没有收到Ack,因此,发送方可能会悲观地认为也丢了,因此有可能也会致使4和5的重传。

对此有两种选择:

  1. 一种是仅重传timeout的包。也就是第3份数据。
  2. 另外一种是重传timeout后全部的数据,也就是第3,4,5这三份数据。

这两种方式有好也有很差。第一种会节省带宽,可是慢,第二种会快一点,可是会浪费带宽,也可能会有无用功。但整体来讲都很差。由于都在等timeout,timeout可能会很长。

快速重传机制

因而,TCP引入了一种叫Fast Retransmit 的算法,不以时间驱动,而以数据驱动重传。也就是说,若是,包没有连续到达,就ack最后那个可能被丢了的包,若是发送方连续收到3次相同的ack,就重传。Fast Retransmit的好处是不用等timeout了再重传。

好比:若是发送方发出了1,2,3,4,5份数据,第一份先到送了,因而就ack回2,结果2由于某些缘由没收到,3到达了,因而仍是ack回2,后面的4和5都到了,可是仍是ack回2,由于2仍是没有收到,因而发送端收到了三个ack=2的确认,知道了2尚未到,因而就立刻重转2。而后,接收端收到了2,此时由于3,4,5都收到了,因而ack回6。

Fast Retransmit只解决了一个问题,就是timeout的问题,它依然面临一个艰难的选择,就是,是重传以前的一个仍是重传全部的问题。对于上面的示例来讲,是重传#2呢仍是重传#2,#3,#4,#5呢?由于发送端并不清楚这连续的3个ack(2)是谁传回来的?也许发送端发了20份数据,是#6,#10,#20传来的呢。这样,发送端颇有可能要重传从2到20的这堆数据(这就是某些TCP的实际的实现)。可见,这是一把双刃剑。

References

TCP-CONNECTION

TCP-TERMINATION