TCP三次握手
传输控制协议TCP,Transmission Control Protocol
是一种面向连接的、可靠的、基于字节流的传输层通信协议,其是运行在OSI
七层模型中的运输层,为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。
三次握手
过程
代码语言:javascript复制client server
主动打开 → SYN=1,seq=x → 被动打开,接收
(同步已发送) (同步收到)
接收 ← SYN=1,ACK=1,seq=y,ack=x 1 ← 发送
(已建立链接) (同步收到)
发送 → ACK=1,seq=x 1,ack=y 1 → 接收
(已建立链接) (已建立链接)
- 第一次握手:客户端主动链接服务器,发送初始序列号
seq=x
与SYN=1
同步请求标志,并进入同步已发送SYN_SENT
状态,等待服务器确认。 - 第二次握手:服务端收到消息后发送确认标志
ACK=1
与同步请求标志SYN=1
,发送自己的序列号seq=y
以及客户端确认序号ack=x 1
,此时服务器进入同步收到SYN_RECV
状态。 - 第三次握手:客户端收到消息后发送确认标志
ACK=1
,发送自己的序列号seq=x 1
与服务器确认号ack=y 1
,发送过后即确认链接已建立状态ESTABLISHED
,服务端接收确认信息后进入链接已建立状态ESTABLISHED
小解释
- 第一次握手:客户端:“兄弟,待会咱们出去玩吧,能看到我的消息吗,能就吱一声,让我知道我有发消息的能力”
- 第二次握手:服务端:“吱,走走走咱们去哪玩?我收到你的消息了,你有发消息的能力,要不你再给我回个消息,让我也确定我有发消息的能力”
- 第三次握手:客户端:“咱们先去河里摸鱼玩,然后上山摘点果子。我也收到你的消息了,你这发消息的能力也没问题,咱俩的发消息的能力都没问题,可以愉快的玩耍了”
双方要连接,要等待对端同意并返回确认,一端请求后收到确认包就意味着,网络可达并且对端同意建立连接。最后的模型则是
A --请求--> B
A <--确认-- B
A <--请求-- B
A --确认--> B
中间两次可以一起返回,所以是三次握手
引自知乎@Manistein
四次挥手
过程
代码语言:javascript复制client server
主动关闭 → FIN=1,seq=u → 被动关闭,接收
(终止等待1) (关闭等待)
接收 ← ACK=1,seq=v,ack=u 1 ← 发送
(终止等待2) (关闭等待)
接收 ← FIN=1,ACK=1,seq=w,ack=u 1 ← 发送
(时间等待) (最后确认)
发送 → ACK=1,seq=u 1,ack=w 1 → 接收
(时间等待 2MSL 关闭) (关闭)
- 第一次挥手:客户端发出释放标识
FIN=1
,自己的序列号seq=u
,进入终止等待FIN-WAIT-1
状态 - 第二次挥手:服务端收到消息后发出
ACK=1
确认标志和客户端的确认号ack=u 1
,自己的序列号seq=v
,进入关闭等待CLOSE-WAIT
状态,客户端收到消息后进入终止等待FIN-WAIT-2
状态 - 第三次挥手:服务器发送释放标识
FIN=1
信号,确认标志ACK=1
,确认序号ack=u 1
,自己的序列号seq=w
,服务器进入最后确认LAST-ACK
状态 - 第四次挥手:客户端收到回复后,发送确认标志
ACK=1
,确认序号ack=w 1
,自己的序列号seq=u 1
,客户端进入时间等待TIME-WAIT
状态,经过2
个最长报文段寿命后,客户端CLOSE
。服务器收到确认后,立刻进入CLOSE
状态。
常见问题
为什不能两次握手
TCP
链接握手是通过序列号进行的,也就是seq
,TCP
需要seq
序列号来做可靠重传或接收,而避免连接复用时无法分辨出seq
是延迟或者是旧链接的seq
,因此需要三次握手来约定确定双方的初始seq
序列号,也就是保证序号完成同步确认。
假如进行两次握手,流程如下,客户端发送起始序列号seq=x
到服务端,服务端收到消息后发送确认标识ACK=1
与确认号ack=x 1
以及自己的序列号req=y
,此时服务端认为链接已建立,客户端收到消息后也认为请求已建立。
考虑到网路中数据延时的情况,假设客户端首先发起了一次链接,此报文在网路中堵塞,此时客户端又发起了一次请求,此时报文更换了路由线路,到达了服务端,服务端此时认为建立链接,并传报文到客户端,数据通信完成后关闭了链接,此时,客户端第一次发送的报文终于到达了服务端,由于两次握手,服务端此时认为建立链接,发送确认标识到客户端,但客户端认定次序列号无效,不建立链接,这就造成了服务端资源的浪费,本不该建立的链接而建立了链接。
为什么不要四次握手
当初上课时老师给出的红蓝军问题的例子,也就是两军问题,从通信的可靠性出发,不存在绝对可靠的协议
代码语言:javascript复制在山的两头是红军1和红军2,山上是蓝军,红军1和2都不是蓝军的对手,想打败蓝军只有一起动手。那么红军1和2需要相互通信才能确定双方在同一个时间动手
红军1的传令军成功偷偷越过大山告诉红军2,明天早上中午十二点一起动手
红军2表示赞同,但是红军1并不确定红军2是否收到了消息,贸然动手必定失败,所以需要红军2的传令军越过大山告诉红军1收到了消息
红军1收到了红军2的确认消息,但此时红军2并不知道红军1是否收到自己的确认消息,所以需要红军1的传令兵越过大山告诉红军2收到了确认消息
红军2收到了红军1的确认消息,但此时红军1并不知道红军2是否收到自己的确认消息,于是需要红军2的传令兵越过大山告诉红军1收到了确认消息
......
两军问题最后陷入了一个死循环,这说明永远都不能完美的达成协议,不存在完全可靠的通信协议。而TCP
作为一种可靠传输控制协议,既要保证数据可靠传输,又要提高传输的效率,而三次握手的时候已经可以达到一个非常可观的可信率与传输效率,再继续握手虽然能继续提高连接的可信率,但是就像对数log
函数曲线,随着握手次数的增加,可靠率增加的并不明显,甚至相对于效率上的消耗,多次握手反而不利于数据的传输。
建立连接是三次握手,关闭连接却是四次挥手
建立连接的时候, 服务器在LISTEN
状态下,收到建立连接请求的SYN
报文后,把ACK
和SYN
放在一个报文里发送给客户端。而关闭连接时,服务器收到对方的FIN
报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN
报文给对方来表示同意现在关闭连接,因此,己方ACK
和FIN
一般都会分开发送,从而导致多了一次。
客户端最后还要等待2MSL
MSL
是Maximum Segment Lifetime
,TCP
允许不同的实现可以设置不同的MSL
值。
第一,保证客户端发送的最后一个ACK
报文能够到达服务器,因为这个ACK
报文可能丢失,站在服务器的角度看来,我已经发送了FIN ACK
报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL
时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL
计时器。
第二,防止类似已经失效的连接请求报文段出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL
时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
参考
代码语言:javascript复制https://www.zhihu.com/question/24853633
https://www.cnblogs.com/jainszhang/p/10641728.html
https://blog.csdn.net/jun2016425/article/details/81506353
https://blog.csdn.net/qq_38950316/article/details/81087809