剖析三次握手与TCP的可靠性

TCP通过校验和、序列号、确认应答、超时重发、流量控制、拥塞控制等保证可靠性
其中TCP连接的建立与断开过程值得深究,有助于深刻理解套接字编程

TCP连接的建立与断开的过程

TCP通过「三次握手」保证连接的建立,「四次挥手」保证链接的断开
建立、断开时,TCP通过状态机的思想管理整个过程

对于三次握手

  • 第一次:客户端发送SYN包(syn=j)到服务器,客户端进入SYN_SENT状态
  • 第二次:服务器收到SYN包,发送确认包(ack=j+1)和SYN包(syn=k),进入SYN_RECV状态
  • 第三次:客户端收到SYN+ACK包,向服务端发送ACK包(ack=k+1),进入ESTABLISHED状态
    • (服务端收到最后一个ACK包,也进入ESTABLISHED状态)

对于四次挥手

  • 第一次:客户端发送FIN包(fin=j)到服务器,客户端进入FIN_WAIT_1状态
  • 第二次:服务器收到FIN包,发送确认包(ack=j+1),进入CLOSE_WAIT状态
    • (客户端收到FIN包, 进入FIN_WAIT_2状态)
  • 第三次:服务器确定收到所有数据,并释放资源,发送FIN包(fin=k),进入LAST_ACK状态
  • 第四次:客户端收到FIN包,向服务端发送ACK包(ack=k+1),进入TIME_WAIT状态
    • (服务端收到最后一个ACK包,进入CLOSED状态)

图示为

为什么必须经过三次握手

问题「为什么需要三次握手?」 可转化为 「为什么两次握手不可靠」
首先明确:没有返回确认的报文(即最后一次的报文)本身是不可靠的,二次握手是,三次握手也是
具体二次握手哪里不合适,接下来从两方面解释

  • 出现异常造成的故障的影响
  • 故障是否可以修复

假设网络只需二次握手

在最后一个ACK没有收到时,服务端已经认为连接已经建立
服务端开始维护这个链接

  • 此时对于服务端
    • 服务端认为连接建立好了,等待客户端发送消息
  • 此时对于客户端
    • 客户端一直等待ACK包,不会发消息

此时故障会对服务端造成影响:
客户端如果需要传输数据,只能发起一个新的连接请求
但此时这个故障对服务器的性能产生了恶劣影响:旧的连接会持续占用服务端的资源
如果客户端是恶意的,还可以通过大量SYN包攻击服务器(SYN洪水攻击)

这个故障是不可修复的,尽管客户端可以通过某种手段(RST标志)通知服务端需要重新建立连接
但是服务端并不知道客户端需要重置哪个连接(服务端使用一个端口维护该客户端的多个连接)

结论:在二次握手下,故障会对服务器造成大的影响,且无法解决

三次握手可以解决问题

在最后一个ACK没有收到时,服务端不认为连接已经建立,不会维护这个链接
此时故障不会对服务端造成影响:
至此问题基本解决了一半
(三次握手图参考图一)

  • 此时对于服务端
    • 服务端认为连接没有建立
  • 此时对于客户端
    • 认为连接建立了,开始发送数据报

服务端在没有收到ACK的情况下先收到了数据,可以知道ACK出现了问题,可以发送带有RST标志的报文
服务端收到后即可重新建立连接

结论:在三次握手下,故障不会对服务器造成影响,且故障可以解决

为什么需要经过四次挥手

四次挥手对比三次握手,主要区别在于服务端返回FIN与ACK包是分开进行的

服务器需要时间关闭socket,不能立即发送表示我已经关闭了的FIN包
但为了避免客户端超时重发,先返回ACK包
所以共计四次挥手

为什么需要TIME_WAIT状态的设计

客户端收到FIN后会进入TIME_WAIT,等待2MSL(Max Segment Life, 报文最大生存时间)的时间
这样做的原因还是因为:没有返回确认的报文是不可靠的

客户端不能保证最后一次ACK成功被接收,因为最后一次报文没有返回确认

但通过设计TIME_WAIT,客户端发送ACK后等待一些时间(2MSL)
如果ACK丢失,服务端会重发FIN,保证连接顺利关闭。
(如果TIME_WAIT没有收到FIN,则认为最后一次ACK抵达)

另外,TIME_WAIT保证网络延迟的报文全部抵达