三次握手指的是客户端和服务端建立连接的过程。 三次握手这个过程需要了解TCP报文的一些关键字段如下:
-
序列号:本次发送的数据的号码
-
确认应答号:期望下一次收到的报文开始的序列号(接收方发送给发送方的,表示在这个号码之前的序列号确保都收到了)
-
控制位:表示报文的类型
- syn,建立连接时发送
- ack,表示报文是确认报文
- rst,表示连接出错,需要重新建立连接
- fin,表示断开连接请求
序列号用来解决包乱序问题(因为网络时延问题,包可能是不同时间到达的,根据序列号来排序)
确认应答号,用来解决可靠性问题(确认应答号表示前面的都收到了)
在linux中,是通过源地址+源端口+目标地址+目标端口来确保tcp连接的唯一性。
三次握手过程
-
客户端发起第一次握手,随机生成一个序列号(序列号的生成有讲究)isn,发送一个syn报文给服务端。
-
服务端在收到syn报文以后,也随机生成一个序列号(服务端也要发送数据,两边各自用自己的序列号,记录最新的就行),然后发送一个syn+ack报文给客户端(这里就是四次握手合并成三次握手,本来应该是ack+syn)。其中确认应答号是isn+1,序列号是isn1
-
客户端在收到服务端的ack+syn报文以后,发送一个ack报文给服务端,其中确认应答号是isn1+1。
自此,连接建立。
经典问题是为什么不是两次握手或者是四次握手。
四次握手上面已经解答了。
不能两次握手的原因如下:
-
假如只有两次握手,那么服务端在收到第一次客户端发送的syn报文以后就是连接建立状态,客户端发送的syn报文可能重复多次,也就会导致连接多次建立,浪费资源。(创建销毁)
-
现在三次握手,有了syn_received状态,就可以避免直接establish状态。因为客户端发现报文不对的时候,可以及时阻止连接建立。
三次握手的过程不能忽略两端的状态。两端的状态是由socket提供的。
第一次握手时:
客户端:syn_sent_start。服务端:syn_recd_start
第二次握手时:
客户端:syn_sent_end。服务端:syn_recd_start
第三次握手时:
客户端:es_start。服务端:es_start
四次挥手过程
四次挥手指的是安全关闭tcp连接的过程。
- 客户端和服务端都可以主动关闭连接。其中一方发起关闭请求时(成为主动关闭方),就发一个fin类型的报文给对方。
- 被动关闭一方收到fin报文以后,发送ack给主动关闭方,代表收到了信息。
但这个时候不是立马关闭的,因为被动关闭方还有消息可能还发完!
- 所以等待被动关闭方发送完消息以后,就会发送一个fin报文给主动关闭方,主动关闭方收到fin报文以后,回复ack报文给被动关闭方,被动关闭方收到以后正式关闭。
- 主动关闭方还要等待2msi时间才关闭(2msi表示往返时间内,数据报能存活的最大时间),因为ack报文可能丢失,要等待主动关闭方重发fin报文,重新发送ack报文,帮助被动关闭方顺利关闭。
在这个过程中同样也要注意双方的状态问题(见下图)。
面试常考的是介绍一下TIME_WAIT这个状态。(也就是为什么要等待一段时间再关闭)
- 显然这个机制会帮助被动关闭方正确地关闭。
- 更重要的是防止历史连接中的数据,被后面相同四元组的连接错误的接收。假如没有这个机制,主动关闭方发出ack以后直接关闭,网络上可能还游走着数据包。有可能重新打开的时候,数据包来了,刚好数据包序列号和应答号符合,客户端接收到了。就会造成数据错乱问题。
附:图都来自小林coding,更加详细的可以看4.1 TCP 三次握手与四次挥手面试题 | 小林coding (xiaolincoding.com)