TCP 的三次握手和四次挥手
TCP 的三次握手和四次挥手,可以说是老生常谈的经典问题。字面上,三次握手是指发送了三个报文段,四次挥手是指发送了四个报文段。但知其然,更要知其所以然。
本文整理了从相关文章的笔记,以备查用。
三次握手
TCP 三次握手,其实就是建立一个 TCP 连接,客户端与服务器交互需要 3 个数据包。
握手的主要作用就是为了确认双方的接收和发送能力是否正常,初始序列号,交换窗口大小以及 MSS 等信息。
MSS(Maximum Segment Size,最大报文长度),是TCP协议定义的一个选项,MSS选项用于在TCP连接建立时,
收发双方协商通信时每一个报文段所能承载的最大数据长度。
- 第一次握手:客户端发送
SYN
报文,并进入SYN_SENT
状态,等待服务器的确认; - 第二次握手:服务器收到
SYN
报文,需要给客户端发送ACK
确认报文,同时服务器也要向客户端发送一个SYN
报文- 也就是向客户端发送
SYN + ACK
报文,此时服务器进入SYN_RCVD
状态;
- 也就是向客户端发送
- 第三次握手:客户端收到
SYN + ACK
报文,向服务器发送确认包,客户端进入ESTABLISHED
状态。- 待服务器收到客户端发送的
ACK
包也会进入ESTABLISHED
状态,完成三次握手。
- 待服务器收到客户端发送的
TCP 连接并非是在通信设备两端之间建立信号隧道,而本质上就是双方各自维护所需的状态状态,以达到 TCP 连接的效果。所以 TCP 状态机是 TCP 的核心内容,学习 TCP 一定要搞懂这些状态机之间的转换。
为什么要三次握手
确认双方的收发能力
目的是建立可靠的通信信道,双方确认自己与对方的发送与接收是正常的。
- 第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
- 第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
- 第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
序列号可靠同步
如果是两次握手,服务端无法确定客户端是否已经接收到了自己发送的初始序列号,如果第二次握手报文丢失,那么客户端就无法知道服务端的初始序列号,那 TCP 的可靠性就无从谈起。
阻止重复历史连接的初始化
客户端由于某种原因发送了两个不同序号的 SYN
包,网络环境是复杂的,旧的数据包有可能先到达服务器。
如果是两次握手,服务器收到旧的 SYN
就会立刻建立连接,那么会造成网络异常。
如果是三次握手,服务器需要回复 SYN+ACK
包,客户端会对比应答的序号,如果发现是旧的报文,就会给服务器发 RST
报文,直到正常的 SYN
到达服务器后才正常建立连接。
三次握手才有足够的上下文信息来判断当前连接是否是历史连接。
安全问题
TCP 新建连接时,内核会为连接分配一系列的内存资源,如果采用两次握手,就建立连接,占用全连接队列,导致正常的连接无法建立,那会放大 DDOS 攻击。
SYN 攻击(SYN Flood)
SYN 攻击指的是,攻击客户端在短时间内伪造大量不存在的 IP 地址,向服务器不断地发送 SYN 包,服务器回复确认包,并等待客户的确认。
由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的 SYN 包将长时间占用未连接队列,正常的 SYN 请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。
三次握手可以携带数据吗?
第一次、第二次握手不可以携带数据,而第三次握手是可以携带数据的。
假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,疯狂着重复发 SYN 报文,这会让服务器花费大量的内存空间来缓存这些报文,这样服务器就更容易被攻击了。
对于第三次握手,此时客户端已经处于连接状态,他已经知道服务器的接收、发送能力是正常的了。
四次挥手
当我们的应用程序不需要数据通信了,就会发起断开 TCP 连接。建立一个连接需要三次握手,而终止一个连接需要经过四次挥手。
断开一个 TCP 连接则需要“四次挥手”:
- 当客户端没有待发送的数据时,它会向服务端发送
FIN
消息,发送消息后会进入FIN_WAIT_1
状态; ^0a078c - 服务端接收到客户端的
FIN
消息后,会进入CLOSE_WAIT
状态并向客户端发送ACK
消息,客户端接收到ACK
消息时会进入FIN_WAIT_2
状态; - 当服务端没有待发送的数据时,服务端会向客户端发送
FIN
消息; - 客户端接收到
FIN
消息后,会进入TIME_WAIT
状态并向服务端发送ACK
消息,服务端收到后会进入CLOSED
状态; - 客户端等待两个最大数据段生命周期(Maximum segment lifetime,MSL) 2 的时间后也会进入
CLOSED
状态;
任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态,第一个 FIN + ACK 后,主动关闭方半关闭。
另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。
为什么需要四次?
建立连接时,当服务端收到客户端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。
关闭连接时,当服务端收到 FIN 报文时,可能服务器还有未发送完的报文,并不会立即关闭 SOCKET。
- 先回复一个 ACK 报文,告诉客户端,”你发的 FIN 报文我收到了”
- 只有等到服务端所有的报文都发送完了,才能发送 FIN 报文
TIME_WAIT 状态为什么要等 2MSL
MSL
指的是报文在网络中最大生存时间。主要原因是:
- 防止延迟的数据段被其他使用相同源地址、源端口、目的地址以及目的端口的 TCP 连接收到
- 因为数据段的网络传输时间不确定,所以可能会收到上一次 TCP 连接中未被收到的数据段
- 在经过
2MSL
时间,就可以使本连接持续的时间内所产生的所有报文都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文。
- 保证 TCP 连接的远程被正确关闭
- 等待被动关闭连接的一方收到 FIN 对应的 ACK 消息
- 客户端发出的 ACK 可能还没有被服务端接收,服务端可能还处于 LAST_ACK 状态
- 服务端因为没有收到 ACK 消息,所以仍然认为当前连接是合法的,新连接的建立时会回复 RST 消息终止连接
- 等待足够长的时间以确定远程的 TCP 连接接收到了其发出的终止连接消息 FIN 对应的 ACK
- 服务端正常收到了 ACK 消息并关闭当前 TCP 连接
- 服务端没有收到 ACK 消息,重新发送 FIN 关闭连接并等待新的 ACK 消息
- 2MSL = ACK 到达服务器 + 服务器发送 FIN 重传包,一来一回
- 等待被动关闭连接的一方收到 FIN 对应的 ACK 消息