数据收发操作
在介绍三次握手前,我们首先需要了解浏览器发送消息到接收到响应消息这个过程协议栈是如何进行数据收发操作的。
- 创建套接字
- 将管道连接到服务器端的套接字上
- 收发数据
- 断开数据并删除套接字
当客户端通过DNS查询到目标IP地址后,就可以委托操作系统内部的协议栈向Web服务器发送消息了。
操作系统内部的协议栈发出委托时,需要调用Socket库中的程序组件。首先,调用其中的socket程序组件创建套接字,并返回一个描述符。套接字的实体就是通信控制信息,协议栈可以根据这些信息来判下一步的行动。在创建套接字时协议栈会首先分配一个内存空间,并写入表示套接字初始状态的控制信息。
接下来就是将本地的套接字与服务器的套接字进行连接了。在这里需要调用Socket库的connect程序组件,根据描述符、服务器IP地址与端口号建立连接。连接操作实际上是通信双方交换控制信息(如TCP头部、MAC头部以及IP头部),在套接字中记录必要信息并准备数据收发的一连串操作。
当套接字连接之后,只要将数据送入套接字,数据就会被发送到对方的套接字中,这个过程调用Socket库的write程序组件完成,包括将HTTP请求消息交给协议栈、对较大数据进行拆分、使用ACK号确认以及调整ACK号等待时间等多个操作。
数据收发完毕,需要调用Socket库的close程序组件进入断开阶段。最终,连接在套接字之间的管道会被断开,套接字本身也会被删除。
三次握手
在以上数据收发的过程中,我们好像并没有提到三次握手,那它到底出现在哪里呢?答案就是连接操作中,过程如下:
第一次握手
客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。
第二次握手
服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。
第三次握手
当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。
看到这里,会有人提出疑问,那为什么不可以二次握手一定要三次握手呢?
我们可以类比打电话,如果A打给B,然后B接听电话之后说话,A突然不吭声了,那A到底是听到还是没听到呢?所以只有两次的话B不能确保A收到消息。
四次挥手
四次挥手,我们望文生义一下,也可以猜到它是在断开操作中的,详细的过程如下:
第一次挥手
若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求。
第二次挥手
B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明 A 到 B 的连接已经释放,不再接收 A 发的数据了。但是因为 TCP 连接是双向的,所以 B 仍旧可以发送数据给 A。
第三次挥手
B 如果此时还有没发完的数据会继续发送,完毕后会向 A 发送连接释放请求,然后 B 便进入 LAST-ACK 状态。
PS:通过延迟确认的技术(通常有时间限制,否则对方会误认为需要重传),可以将第二次和第三次握手合并,延迟 ACK 包的发送。
第四次挥手
A 收到释放请求后,向 B 发送确认应答,此时 A 进入 TIME-WAIT 状态。该状态会持续 2MSL(最长报文段寿命,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态。当 B 收到确认应答后,也便进入 CLOSED 状态。
泛洪攻击
泛洪攻击是一种针对TCP三次握手进行攻击的手段:
攻击者在第一次握手阶段向服务器发送大量的SYN包,在服务器回应第二次握手成功后,不向服务器端发送ACK包导致服务器在第二次握手后存在大量的半开连接,消耗服务器资源,最后使得服务器无法再响应TCP连接,达到攻击目的。
使用SYN cookie可以有效抵御泛洪攻击:
服务器在第二次握手时不会为第一次握手的SYN创建半开连接,而是生成一个cookie一起发送给客户端,只有客户端在第三次握手发送ACK报文并且验证cookie成功服务器才会创建TCP连接,分配资源。