TCP详解之三次握手和四次挥手
1. TCP基本认识
1.1 什么是 TCP
TCP是面向连接的、可靠的、基于字节流的传输层通信协议。
1.2 TCP协议段格式
我们先来看看TCP首部协议的格式
我们先来介绍一些与本文关联比较大的字段,其他字段不做详细阐述。
序列号:在建立连接时,由计算机随机生成的数作为初始值,通过SYN报文传给接收端。每发送一次,就累加一次该数。用来解决网络包乱序问题。
确认应答号:指下次期望收到的数据的序列号,发送端收到这个确认应答后可以认为这个序号以前的数据都被接收端正常接收了。用来解决丢包问题。
标志位:
- ACK:该位为1时,确认应答字段是否有效
- RST:该位为1时,表示TCP连接中出现异常,必须强制断开连接。
- SYN:该位为1时,表示希望建立连接,并在序列号字段进行序列号初始值的设定。
- FIN:该位为1时,通知对端以后不会有数据发送,希望断开连接。当通信结束希望断开连接。
1.3 为什么需要 TCP 协议
IP
层是不可靠的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中数据的完整性。
如果需要保障网络数据包的可靠性,就需要上层 传输层 的TCP
协议来负责。
因为TCP是一个工作在传输层的可靠数据传输服务,它能确保接收端接收的网络包是无损坏的、无间隔的、非冗余和按序的。
2. TCP连接建立
2.1 TCP三次握手过程是怎样的
TCP是面向连接的协议,所以使用TCP前必须先建立连接,而建立连接是通过三次握手来进行的。具体过程如下图所示:
-
一开始,客户端和服务端都处于
CLOSE
状态。先是服务端主动监听某个端口,处于LISTEN
状态。 -
第一次握手,客户端会随机初始化序号(
client_isn
),将该序号设置为TCP首部序列号字段,同时把SYN
标志位置为1,表示客户端向服务端发起连接,该报文不包含数据,之后客户端处于SYN_SENT
状态。 -
第二次握手,服务端接收到客户端发来的
SYN
报文,首先也会随机初始化自己的序列号(server_isn
),将此序号设置到TCP首部序列号字段,接着把确认应答号字段设置为client_isn + 1
,把SYN
和ACK
标志位设置为1。最后把报文发送给客户端,该报文也不包含数据,服务端进入SYN_RCVD
状态。 -
第三次握手,客户端收到
SYN+ACK
报文后,向服务端发送一个应答报文,将确认应答号设置为server_isn + 1
,ACK
标志位也设置为1,最后把报文发送给客户端,此次报文可以包含数据,发送后客户端处于ESTABLISHED
状态。 -
服务端收到报文后,也进入
ESTABLISHED
状态。
一旦完成三次握手,双方都处于ESTABLISHED
状态,此时连接就已经建立完成,客户端和服务端就可以互相发送数据了。
2.2 为什么是三次握手,不是两次、四次
在前面,我们知道了什么是TCP连接:用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合称为连接。
接下来,以三个方面分析三次握手的原因:
- 三次握手才可以阻止重复历史连接的初始化(主要原因)
- 三次握手才可以同步双方的初始序列号
- 三次握手才可以避免资源浪费
2.2.1 避免历史连接
简单来说,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。
假设,客户端先发送了SYN(seq = 90)报文,然后客户端宕机了,而且这个SYN报文还被网络阻塞了,服务器并没有收到,接着客户端重启,又像服务端建立连接,发送SYN(seq = 100)报文(这里不是重传,重传的SYN序号是一样的)。
-
一个
旧SYN报文
比新SYN报文
早到达了服务端,这时服务端会回一个SYN+ACK
报文给客户端,此报文中的确认应答号是91(90 + 1)。 -
客户端收到后,发现自己期望收到的确认应答号应该是101(100 + 1),而不是91,于是就会回
RST
报文。 -
服务端收到
RST
报文后,就会释放连接。 -
后续新的
SYN
报文抵达服务器后,客户端与服务端就可以正常的完成三次握手了。
上述的旧SYN报文
成为历史连接,TCP使用三次握手建立连接的最主要原因就是防止历史连接初始化了连接,造成资源浪费。
TIPS:有人可能会问了,如果服务端在收到RST报文之前,就先收到了新SYN报文,这时会发生什么?
当服务端第一次收到SYN报文,也就是收到旧SYN报文时,就会回复SYN+ACK
报文给客户端,此时报文中的确认应答号是91(90 + 1)。
然后这时再收到新SYN报文时,就会回Challenge ACK
报文给客户端,这个ACK
报文并不是确认收到新SYN报文的,而是上一次的ACK
确认应答号91(90 + 1)。所以客户端收到此ACK报文时,发现与期望收到的确认应答号101(100 + 1)不符,于是会回RST
报文。
如果时两次握手连接,就无法阻止历史连接,在两次握手的情况下,服务端没有中间状态给客户端来阻滞历史连接,导致服务器可能建立一个历史连接,造成资源浪费。
在两次握手的情况下,服务器收到SYN报文后,就进入ESTABLISHED状态,意味着这时可以给对方发送数据,但是客户端此时还没有进入ESTABLISHED状态,假设这次是历史连接,客户端判断到此次连接为历史连接,就会回RST报文来断开连接,而服务端在第一次握手时就进入了ESTABLISHED状态,所以它认为可以发送数据,但是它并不知道这是个历史连接,只有它收到RST报文后,才会断开连接。
可以看到,如果两次握手建立TCP连接的场景下,服务端在向客户端发送数据前,并没有阻止掉历史连接,导致服务端建立了一个历史连接,不仅白白发送了数据,而且浪费了服务器的资源。
所以,TCP三次握手建立连接的主要原因就是防止历史连接初始化了连接。
2.2.2 同步双方初始序列号
TCP协议的通信双方,都必须维护一个序列号,序列号是TCP可靠传输的一个关键因素。
- 接收方可以去除重复的数据
- 接收方可以根据数据包的序列号按顺序接收
- 可以标识发出去的数据包中,哪些是已经被对方收到的(通过ACK报文中的应答序列号知道)
可见,序列号在TCP连接中占据着非常重要的作用,所以当客户端发送携带初始序列号的SYN报文时,需要服务端回一个ACK应答报文,表示客户端的SYN报文已经被服务端成功接收,当服务端发送的初始序列号给客户端时,依然也要得到客户端的回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
四次握手也能够可靠的同步双方的初始化序列号,由于第二步和第三步可以优化成一步,所以就成了三次握手
而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
2.2.3 避免资源浪费
如果只有两次握手,当客户端发送的SYN报文在网络中阻塞,客户端没有接收到ACK报文,就会重新发送SYN,由于没有第三次握手,服务器不清楚客户端是否收到了字节回复的ACK报文,所以服务端每收到一个SYN就只能先主动建立一个连接,如果客户端大宋的SYN报文在网络中阻塞了,重复发送多次SYN报文,那么服务端在收到请求后就会建立多个冗余的无效连接,造成不必要的资源浪费。
总结:
TCP建立连接时,通过三次握手就能防止历史连接的建立,帮助双方同步初始序列号,减少双方不必要的资源开销。保证TCP连接的可靠性。
不使用两次握手和四次握手的原因:
- 两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方初始序列号
- 四次握手:三次握手就已经理论上最少可靠连接建立,不需要更多的通信次数。
3. TCP连接断开
3.1 四次挥手的过程
天下没有不散的宴席,对于TCP连接也是这样。TCP连接断开是通过四次挥手方式。
双方都可以主动断开连接,断开连接后主机的资源将被释放,四次挥手过程如下图所示:
- 客户端打算关闭连接,此时会发送一个TCP首部
FIN
标志位被置为1的报文,之后客户端进入FIN_WAIT_1
状态。 - 服务端收到
FIN
报文后,就向客户端发送ACK
应答报文,接着服务器进入CLOSE_WAIT
状态。 - 客户端收到
ACK
应答报文后,进入FIN_WAIT_2
状态。 - 等待服务端处理完数据后,也想客户端发送
FIN
报文,之后服务端进入LAST_ACK
状态。 - 客户端收到
FIN
报文后,回复一个ACK
应答报文,之后进入TIME_WAIT
状态。 - 服务端收到
ACK
应答报文后,就进入CLOSE
状态,至此服务端已经完成连接的关闭。 - 客户端在经过
2MSL
一段时间后,自动进入CLSOE
状态,至此客户端也完成连接的关闭。
每个方向都需要一个FIN
和一个ACK
,因此通常被称为四次挥手。
这里需要注意的是:主动关闭连接的,才有TIME_WAIT
状态。
3.2 为什么挥手需要四次
再来回顾一下四次挥手双方发FIN包的过程,就能理解为什么需要四次了。
- 关闭连接时,客户端向服务端发送
FIN
报文时,仅仅表示客户端不再发送数据了,但是还能接收数据。 - 服务端收到客户端的
FIN
报文时,先回一个ACK
应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送FIN
报文给客户端表示同意关闭连接。
从上面过程可知,服务端通畅需要等待完成数据的发送和处理,所以服务端的ACK
和FIN
一般会分开发送,因此是需要四次挥手。
但是在特定情况下,四次挥手是可以变成三次挥手的。
3.3 什么情况会出现三次挥手
当被动关闭方(这里是服务端)在TCP挥手过程中,没有数据要发送并且开启了TCP延迟确认机制,那么第二次和第三次挥手就会合并发送,这样就出现了三次挥手。
什么是延迟确认机制?
当发送没有携带数据的ACK
报文时,它的网络效率是很低的,因为它也有40个字节的IP首部和TCP首部,但却没有携带数据报文。为了解决ACK
传输效率低的问题,就衍生出了TCP延迟确认。TCP延迟确认的具体策略如下:
- 当有响应数据要发送时,
ACK
会随着响应数据一起立刻发送给对端。 - 当没有响应数据要发送时,
ACK
将会延迟一段时间,等待是否有响应数据可以一起发送。 - 如果在延迟等待发送
ACK
期间,对方的第二个数据报文又到达了,这时会立刻发送ACK
。