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保证网络延迟的报文全部抵达