TCP的三次握手和四次挥手
1、TCP简介
- TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。数据在传输前要建立连接,传输完毕后还要断开连接。
- 面向连接:一定是一对一才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的。
- 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端。
- 字节流:消息是没有边界的,所以无论我们消息有多大都可以进行传输。并且消息是有序的,当前一个消息没有收到的时候,即使它先收到了后面的字节,那么也不能扔给应用层去处理,同时对重复的报文会自动丢弃。
- 客户端在收发数据前要使用 connect() 函数和服务器建立连接。建立连接的目的是保证IP地址、端口、物理链路等正确无误,为数据的传输开辟通道。
- TCP建立连接时要传输三个数据包,俗称三次握手(Three-way Handshaking)。
2、TCP数据报结构
- 序列号:Seq(Sequence Number)序列号占32位,用来标识从计算机A发送到计算机B的数据包的序列号,计算机发送数据时对此进行标记。
- 确认应答号:Ack(Acknowledge Number)确认应答号占32位,客户端和服务器端都可以发送,Ack = Seq + 1。
- 标志位:每个标志位占用1Bit,共有6个,分别为 URG、ACK、PSH、RST、SYN、FIN,具体含义如下:
- URG:紧急指针(urgent pointer)有效。
- ACK:确认应答号有效。
- PSH:接收方应该尽快将这个报文交给应用层。
- RST:重置连接。
- SYN:建立一个新连接。
- FIN:断开一个连接。
3、TCP的三次握手
TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,而建立连接是通过三次握手来进行的。
- 一开始,客户端和服务端都处于
CLOSED
状态。先是服务端主动监听某个端口,处于LISTEN
状态
- 客户端会随机初始化序列号(
client_isn
),将此序号置于 TCP 首部的序列号字段中,同时把SYN
标志位置为1
,表示SYN
报文。接着把第一个SYN
报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于SYN-SENT
状态。
- 服务端收到客户端的
SYN
报文后,首先服务端也随机初始化自己的序列号(server_isn
),将此序列号填入 TCP 首部的序列号字段中,其次把 TCP 首部的确认应答号字段填入client_isn + 1
, 接着把SYN
和ACK
标志位置为 1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于SYN-RCVD
状态。
- 客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部
ACK
标志位置为1
,其次确认应答号字段填入server_isn + 1
,最后把报文发送给服务端,这次报文可以携带客户到服务器的数据,之后客户端处于ESTABLISHED
状态。 - 服务器收到客户端的应答报文后,也进入
ESTABLISHED
状态。
从上面的过程可以发现第三次握手是可以携带数据的,前两次握手是不可以携带数据的,这也是面试常问的题。
一旦完成三次握手,双方都处于 ESTABLISHED
状态,此时连接就已建立完成,客户端和服务端就可以相互发送数据了。
4、为什么是三次握手?而不是两次握手或四次握手?
- 避免资源浪费
- 同步双方的初始序列号
- 阻止重复历史连接的初始化
TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始序列号。序列号能够保证数据包不重复、不丢弃和按序传输。
不使用两次握手的原因:两次握手无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号。
不使用四次握手的原因:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。
5、TCP的四次挥手
所谓的四次挥手即TCP连接的释放(解除)。连接的释放必须是一方主动释放,另一方被动释放。以下为客户端主动发起释放连接的图解:
- 首先客户端想要释放连接,向服务器端发送一段
TCP
报文,其中:标记位为FIN
,表示“请求释放连接“。序号为Seq=u
;随后客户端进入FIN-WAIT-1
阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK
确认报文。 - 服务器端接收到从客户端发出的
TCP
报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED
阶段,进入CLOSE-WAIT
阶段(半关闭状态)并返回一段TCP
报文,其中:标记位为ACK
,表示接收到客户端发送的释放连接的请求。序号为Seq=v
;确认号为Ack=u+1
,表示是在收到客户端报文的基础上,将其序号Seq值加1
作为本段报文确认号Ack
的值。随后服务器端开始准备释放服务器端到客户端方向上的连接。客户端收到从服务器端发出的TCP
报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1
阶段,进入FIN-WAIT-2
阶段前两次挥手既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了。 - 服务器端自从发出
ACK
确认报文之后,经过CLOSED-WAIT
阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP
报文,其中:标记位为FIN
,ACK
,表示已经准备好释放连接了。注意:这里的ACK
并不是确认收到服务器端报文的确认报文。序号为Seq=w
;确认号为Ack=u+1
。表示是在收到客户端报文的基础上,将其序号Seq值加1
作为本段报文确认号Ack
的值。随后服务器端结束CLOSE-WAIT
阶段,进入LAST-ACK
阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。 - 客户端收到从服务器端发出的
TCP
报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2
阶段,进入TIME-WAIT
阶段,并向服务器端发送一段报文,其中:标记位为ACK
,表示接收到服务器准备好释放连接的信号。序号为Seq=u+1
。表示是在收到了服务器端报文的基础上,将其确认号Ack
值作为本段报文序号的值。确认号为Ack=w+1
。表示是在收到了服务器端报文的基础上,将其序号Seq
值作为本段报文确认号的值。随后客户端开始在TIME-WAIT
阶段等待2MSL
。
5、为什么连接的时候是三次握手,关闭的时候却是四次挥手?
- 因为当
Server
端收到Client
端的SYN
连接请求报文后,可以直接发送SYN+ACK
报文。其中ACK
报文是用来应答的,SYN
报文是用来同步的。 - 但是关闭连接时,当
Server
端收到FIN
报文时,很可能并不会立即关闭SOCKET
,所以只能先回复一个ACK
报文,告诉Client
端,“你发的FIN
报文我收到了”。 - 只有等到我
Server
端所有的报文都发送完了,我才能发送FIN
报文,因此不能一起发送。故需要四步握手。
6、TCP的三次握手一定能保证传输可靠吗?
不能。
- 三次握手比两次更可靠,但也不是完全可靠,而追加更多次握手也不能使连接更可靠了。因此选择了三次握手。
- 世界上不存在完全可靠的通信协议。从通信时间成本空间成本以及可靠度来讲,选择了三次握手作为点对点通信的一般规则。
7、为什么客户端在TIME-WAIT阶段要等2MSL?
为的是确认服务器端是否收到客户端发出的ACK
确认报文。
当客户端发出最后的ACK
确认报文时,并不能确定服务器端能够收到该段报文。所以客户端在发送完ACK
确认报文之后,会设置一个时长为2MSL的计时器。MSL指的是Maximum Segment Lifetime:一段TCP
报文在传输过程中的最大生命周期。2MSL即是服务器端发出为FIN报文和客户端发出的ACK
确认报文所能保持有效的最大时长。
如果服务器端在1MSL内没有收到客户端发出的ACK
确认报文,就会再次向客户端发出FIN
报文。
如果客户端在2MSL内,再次收到了来自服务器端的FIN
报文,说明服务器端由于各种原因没有接收到客户端发出的ACK
确认报文。客户端再次向服务器端发出ACK
确认报文,计时器重置,重新开始2MSL的计时。否则客户端在2MSL内没有再次收到来自服务器端的FIN
报文,说明服务器端正常接收了ACK
确认报文,客户端可以进入CLOSED
阶段,完成“四次挥手”。所以,客户端要经历时长为2SML的TIME-WAIT
阶段;这也是为什么客户端比服务器端晚进入CLOSED
阶段的原因。