TCP 三次握手的问题,应该每只猿都被问到过,网络上关于解释 TCP 三次握手的文章也非常非常多,但是读大部分文章,我自己读来没有办法一目了然。因此就想要尝试一下,希望可以做到让人比较容易理解。
TCP三次握手流程
话不多说,先看一下熟悉的三次握手流程图:
第一次握手:客户端向服务端发送连接请求;
位码 SYN=1 为固定值,seq 为客户端生成的随机序列号,客户端这侧生成,假定该值为 8888;
第二次握手:服务端接收到客户端连接请求后,对该连接请求作出响应,告知客户端我存在,并且可以接收到数据;
位码 SYN=1 为固定值,seq 为服务端生成的随机序列号,与客户端生成的 seq 没有半毛钱关系,假定该值为 7777,ACK=1 表示对请求的应答,ack=client_isn+1,此处值一定为 8889,表示对前一请求的应答;随后服务端进入 SYN_RCVD 状态,等待建立连接;
第三次握手:客户端收到来自服务端的应答响应后,再向服务器发送数据的确认包,告诉服务器我已经知道你在线了,并且准备发送数据了;
ACK=1 同样表示作为应答,ack=server_isn+1 表示对上一服务器过来的数据的应答,此处值一定为 7778,seq 客户端发送给服务器的第二个数据包,所以这个时候 seq = 8889,之后客户端直接进入连接状态,服务器收到数据时,会检查 ack 数据值是否与预期值匹配,如果数据匹配则状态变更到连接状态;
TCP两次握手有什么毛病
从上述步骤,看出来三次握手完全能够满足建立连接的需求,那么问题来了,为什么要三次,两次不可以吗?比如下图的 TCP 两次握手:
服务端S接收到客户端C连接请求后,发送响应给C,告诉C,S是存在的,可以传输数据,随后就到连接成功的状态,等待C发数据过来了。初看也没什么毛病,两次握手就能建立起连接为什么要三次?那么问题来了,我们看一下下面的场景:
从这个场景来看,两次握手的问题就很清晰了,S收到C的连接请求后,向C发了响应确认,随后进入已连接状态,等待C发送数据,但是实质上C并没有收到来自S的消息,也就是C并没有接收能力,或者由于网络原因消息包丢失,S连接被占据,白白浪费了资源;由此可见,两次握手没有办法保证交互双方同时具备发送与接收能力。
连接建立的双方是可以互相发送数据的,这个时候C认为S不在线,于是就不会继续发送数据,那么这个时候 S 向 C 发数据会是什么情况呢?
如上场景所示,C 此前并不认为跟 S 已经建立了连接,不知道 S 的初始序列号,因此 S 发来的任何数据分组都将被忽略。
TCP四次挥手流程
接下来看一下四次挥手的流程图:
第一次挥手:客户端向服务器发送关闭连接请求,随后进入 FIN-WAIT-1 状态;
位码 FIN=1 为固定值,seq=u 为数据传输过程后累计到的序列号,假定为 10086
第二次挥手:服务端接收到关闭请求,向客户端响应关闭请求,随后进入等待关闭状态 CLOSEWAIT;
ACK=1 代表应答,ack=u+1 代表对关闭请求的应答,此处值一定为 10087,seq=v 为服务端传输过程后累计的序列号,假定为 10010,此过程主要快速响应客户端的断开请求,告知客户端已经接收到断开请求了,但是我还有数据没有发完毕。所以客户端收到报文后进入 FIN_WAIT-2 状态,知道服务端还不能关闭
第三次挥手:服务端发送完了所有的数据后,再次响应一次客户端的关闭请求,告知客户端自己也可以关闭了;
位码 FIN=1 为固定值,ACK=1 代表应答,ack=u+1 代表对关闭请求的应答,此处值仍然为 10087,随后服务端进入 LAST-ACK 状态,等待来自客户端的应答
第四次挥手:客户端收到来自服务端的可关闭报文后,发送响应告知服务端我已经完成工作了,准备关闭;
ACK=1 代表应答,seq=u+1,此处一定为 10087,ack=w+1 代表对来自服务端上一个报文的应答,服务端接收到应答后,进入 CLOSED 状态。
从客户端主动发送关闭状态到服务端第二次响应才携带 FIN=1 的状态码来看,我们可以发现,如果报文中包含 FIN=1,那么就代表发送报文的这一方可以进入关闭状态了,至于什么时候才进入到 CLOSED 状态,需要根据报文响应才进行切换。
TCP三次挥手有什么毛病
从四次挥手流程来看,整个流程也很完美,那么为什么一定要四次?三次不可以吗?比如下图的 TCP 三次挥手:
同样的来模拟一种场景:
从上面场景可以看到,C 随时都可能发送关闭连接的报文,而 S 也一直有数据发送到 C,这个时候 S 要么等待最后的数据发送完毕后再响应 FIN 报文,要么先告诉 C,我收到你的断开请求了,但是你还得等一等,显然对网络包的报文都是迅速响应的,这个时候如果携带 FIN 位码,C 认为 S 已经可以关闭了,则不再接收其他报文,响应关闭报文。这个时候服务端未发送完毕的数据即会丢失。
那么想象一下,服务端如响应 FIN 之后,即便是收到了来自客户端的 ACK 请求,仍然把未发送完毕的数据继续发送给客户端直到发送完毕再进入 CLOSED 状态会怎样呢?分析一下可以知道,TCP 是可靠的数据传输协议,这个时候发送的数据没有办法保证被接收成功,或者直接被客户端拒绝了。
四次挥手过程中的状态
最后,我们来看看四次挥手过程中,客户端与服务端在整个过程中的状态变化,以及各个状态的设计的意义:
客户端:FIN-WAIT-1
客户端在发起断开连接请求后进入该状态,这个状态下,客户端处于等待服务端响应自己发送的断开请求的过程,如果服务端一直没有响应断开请求,那么客户端会重发;
服务端:CLOSE-WAIT
服务端接收到客户端断开请求后,响应断开请求后进入该状态,由于在接收到客户端断开请求时,服务器可能仍然有数据在发送过程,顾不会立即进入关闭,需要等待最后的数据都发送完毕
客户端:FIN-WAIT-2
客户端收到来自服务端对自己的断开请求响应后,进入该状态,表示等待服务端发送最后的数据
服务端:LAST-ACK
服务端发送完所有的报文数据后,并对客户端发送带有 FIN 的断开确认请求,进入到该状态,等待客户端响应自己的断开请求
客户端:TIME-WAIT
客户端收到来自服务器的断开确认后,然后做最后的响应,进入到该状态,该状态值主要用于确保服务端接收到了自己响应,如果服务端未接收到,会重复发携带 FIN 的报文过来,在这个阶段可以重复进行 ACK
客户端&服务端:CLOSED
服务端收到断开响应后直接进入CLOSED状态,客户端等待 2MSL 时间后进入该状态,表示该次连接结束。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。