humid1ch blogs

本篇文章

手机用户建议
PC模式 或 横屏
阅读


Linux网络 2024 年 1 月 11 日

[TCP/IP] 传输层代表协议--TCP协议介绍(2): TCP协议的"三次握手"过程分析、超时重传机制介绍...

TCP协议是面向连接的, 面向字节流的, 可靠的 传输层协议...
上一篇文章中, 已经介绍了TCP协议的数据格式, 简单分析了其与UDP协议 关于可靠性方面的差异
本篇文章, 介绍分析一下 使用TCP协议通信, 非常重要的一个过程: 三次握手

TCP的”三次握手”

TCP协议是有连接的传输层协议, 即使用TCP协议通信, 是需要建立连接的
TCP协议建立连接的过程, 叫做”三次握手”, 这个过程具体是什么? 为什么是三次? 这两个问题可以分析一下
TCP“三次握手”建立连接的过程, 用图片展示是这样的:

注意, 通信中发送的 SYN SYN+ACK ACK, 并不是发送报文中携带的数据, 而是指发送的报文报头中设置的标记位

此图中, Client代表 客户端 Server代表 服务端, 不同颜色的片段表示不同的状态
那么, 这个”三次握手”的过程就是:
  1. 客户端先发送连接请求, 即 将TCP报文中SYN标记位设置为1, 然后再将整个报文发送给服务端(一般情况下不会携带数据)

    客户端发送了此报文之后, 客户端进入SYN_SENT状态, 表示已发送了建立连接的请求

  2. 如果服务端收到了 客户端发送的连接请求, 那么服务端就会应答客户端的请求, 即 将TCP报文中SYNACK标记位都设置为1, 然后再将整个应答报文发送给客户端(同样一般不会携带数据)

    服务端发送了此 应答报文之后, 服务端进入SYN_RCVD状态, 表示已经应答了客户端的连接请求

  3. 然后客户端就应该收到 来自服务端的应答报文 之后, 客户端就需要再向服务端发送一个应答, 即 客户端将TCP报文中ACK标记位设置为1, 然后将报文发送给服务端

    客户端发送了此次应答报文之后, 客户端就会进入ESTABLISHED状态, 客户端认为连接建立成功

  4. 最后, 服务端应该收到来自客户端的应答报文, 收到之后, 服务端不会再发送应答报文, 而是进入ESTABLISHED状态, 服务端认为连接建立成功

“三次握手”的实际就是 客户端和服务端在互相发送报文, 用来确认连接的过程

如何理解简单TCP的连接

连接, 在网络中是一个比较抽象的概念
TCP协议是面向连接的, 那么 如何理解TCP连接呢?
一个主机是可以同时建立大量的连接的, 那么操作系统就需要同时维护、管理大量的连接
按照以往操作系统管理大量进程、文件等的经验, 操作系统一定会针对每个连接 均维护 包含此连接所有属性的结构体. 不过, 由于TCP连接的管理较为复杂, 所以对应需要维护的结构体不止一个
那么, 也就是说, 当客户端或服务端 为了维护TCP连接 创建了对应的结构体对象 并 已经完成了结构体内数据的填充, 就表示 客户端或服务端认为此次TCP连接已完成且成功

为什么是”三次握手”

上面 介绍了”三次握手”的过程, 但是 为什么是三次?
一次不行吗?
我们已经了解到, 当服务端针对此次TCP连接 创建并维护了对应的结构体对象 并 完成了结构体数据的填充时, 服务端就认为连接建立完成
既然是操作系统创建维护一些结构体, 那么就一定有时间和资源上的消耗
如果是”一次握手”, 就表示 客户端发送SYN连接请求之后, 就直接认为自己创建好了连接, 服务端收到请求 不需要应答, 服务端就同样直接认为连接已建立
虽然, “一次握手”也同样可能成功的建立连接
但是, 如果只是”一次握手” 就会出现一些问题:
  1. 客户端和服务端无法正确协定、同步 双方的初始序号

    TCP报头存在 序号, 此字段的初始值是在建立连接时, 客户端和服务端互相协定、同步的

    如果只是”一次握手”, 那么只能同步客户端的初始序号, 因为只有客户端发送了携带初始序号的报文

  2. 由于网络延迟, 客户端可能多次发送连接请求, 服务端就有可能多次建立连接

    服务端多次建立了连接, 即 多次创建了 一些维护连接所需的结构体, 但是只有一套是有效的

    这样, 会造成对服务端资源的无效占用

两次不行吗?
如果是”两次握手”, 就表示 客户端先发送SYN连接请求, 服务端收到请求 需要SYN+ACK应答, 然后服务端认为连接建立完成, 客户端收到服务端的应答之后, 客户端认为连接建立完成
我们知道, 客户端和服务端认为连接建立完成的标志是 系统已经创建并填充完成了 一系列维护TCP连接所需的结构体
那么如果”两次握手”, 则是 服务端系统先完成了 创建并填充 一系列维护连接所需的结构体
这就可能出现一个问题: 如果客户端不断地发送请求, 但是不接收服务端的请求, 然后导致 服务端不断地 维护TCP连接, 而客户端并不维护连接.
这就实现了对服务端主机的攻击: 服务端会不断地消耗时间和空间资源, 用于维护TCP连接, 而客户端不会
并且, 由于 服务端依旧是 仅接收一次客户端的报文 就确认连接已建立, 所以还可能会出现 仅”一次握手”出现的问题:
  1. 由于网络延迟, 客户端可能多次发送连接请求, 服务端就有可能多次建立连接

    服务端多次建立了连接, 即 多次创建了 一些维护连接所需的结构体, 但是只有一套是有效的

    这样, 会造成对服务端资源的无效占用

“两次握手”, 理论上来说 不会出现无法协定、同步通信双方初始序号的问题

因为, 客户端发送连接请求可以携带初始序号, 服务端进行应答也可以携带初始序号

即使存在网络延迟, 导致客户端发送了多个连接请求. 服务端也会针对多个连接请求一一进行应答

所有应答报文都会填充对应的确认序号和初始序号, 所以客户端只要收到了应答报文, 就可以确认服务端应答的目标 以及 服务端的初始序号. 然后, 连接建立成功

如果, 客户端没有收到应答报文, 那这就意味着连接还没有建立成功, 客户端可能会继续发送请求, 直到成功接收应答报文

如果是三次呢?
“三次握手”的过程已经简单的分析了一下
从图中可以看到, Client率先进入了ESTABLISHED状态, 也就是说 Client率先完成了维护TCP连接操作
然后, Server进入了ESTABLISHED状态, 这样 让客户端先完成维护连接的操作, 可以避免像”两次握手”那样 服务端被攻击, 至少客户端也要付出相同的代价
其次, 因为在ClientServer进入ESTABLISHED状态之前, 都经历了一收一发, 所以不会出现 无法正确协定和同步双方初始序号的情况
并且, “三次握手”通过三次报文传输, 顺便完成了 客户端的发送(SYN请求)和接收(SYN+ACK应答)能力的检测 以及 服务端的发送(SYN+ACK应答)和接收(ACK应答)能力的检测
“三次握手”是可以完成上面这些功能的最少的次数, 如果”四次握手”或更多次数的握手, 也只是徒增连接消耗罢了

协定、同步双方初始序号

上面展示”三次握手”过程的图, 没有展示出 通信双方同步初始序号的过程
“三次握手”过程可以这样展示:

SYN ACK 大写的, 表示设置的标记位

seq ack 小写的, 表示序号 和 确认序号

整个 协定、同步初始序号的过程是:
  1. 客户端发送连接请求, 携带随机初始序号的seq = x

  2. 服务端收到请求, 读取到客户端的初始序号, 应答报文 携带随机初始序号的seq = y, 且填充确认序号ack = x+1

  3. 客户端收到应答, 读取确认序号 确认服务端已同步客户端初始序号, 同时 读取到服务端的初始序号, 然后 应答报文 填充序号seq = x+1和确认序号ack = y+1

    客户端确认连接建立

  4. 服务端收到应答, 读取确认序号 确认客户端已同步服务端初始序号

    服务端确认连接建立

整个过程中, 客户端和服务端, 都是经过一发一收 读取收到确认序号之后, 才确认的初始序号已同步
“一次握手”和”两次握手”无法完善这个过程

了解了”三次握手”的过程, 回到上面提到的一个问题:

发送方如何在第一次发送数据之前, 就知晓接收方的窗口大小呢?

这个答案就是: 双方会 在”三次握手”阶段对窗口大小进行交换、同步

6. RST标记位

“三次握手”的过程中, 报文的发送是需要时间的
在客户端进行第三次握手之后, 客户端实际就已经认为本次TCP连接已经建立完成了
客户端完成连接建立之后, 会干什么? 会向服务端发送数据
但是, 有一个问题是, 如果服务端没有收到第三次握手的报文, 但是客户端已经向服务端开始发送数据了, 怎么办?
在服务端还在等待第三次握手的报文时, 服务端还没有进入ESTABLISHED状态, 客户端已经发送通信数据到服务端了, 此时 服务端就会意识到 TCP连接出了问题
然后 服务端就会向客户端发送 设置了RST标记位的报文, 让客户端重置TCP连接并重新进行”三次握手”
这就是RST标记位的作用, 让客户端重新建立TCP连接, 所以RST标记位 叫做复位标记位

除了上面出现的场景, RST还可以用于由于长时间不进行通信, 被服务器单方面断掉的TCP连接中

TCP的超时重传机制

TCP的超时重传机制表示, TCP通信中, 如果一端长时间没有收到来自对端的应答, 那么就会重新发送没有收到应答的报文
但是, 长时间没有收到对端应答有两种情况:
  1. 报文根本就没有发送到对端, 在传输过程中丢包了
  2. 对端接收到报文了, 并且也发送了应答报文, 但是对端的应答却在传输的过程中丢包了
这两种情况的区别是, 1. 对端没收到数据 2. 对端收到了数据
  1. 对端没有收到数据

    此时, 只需要在超时之后 将报文重新发送给对端 就可以了

  2. 对端收到了数据

    如果对端已经收到了数据, 但是对端的应答报文丢了

    那么, 当报文重新发送给对端之后, 对端会再次发送应答报文

    但是, 此时 对端就会接收到重复的数据. 但重复的报文、数据是没用的 需要丢弃, 所以 TCP协议需要有能力识别接收的报文是否重复

    这就要用到TCP报文的 序号字段. 只要两个报文的序号字段相同, 就说明收到了相同的报文

TCP协议的超时重传机制, 说明了 TCP报文在发送出去 或 接收到之后, 并不会立刻丢弃, 而是会存储一段时间

这也是 TCP超时重传机制的基础

那么, TCP如何界定 是否超时?
最理想的情况, 就是可以找到一个最短的时间, 保证此次发送之后”确认应答一定能在这个时间内返回”.
但是, 网络环境是会变化的, 所以这个最短的时间也是不可能固定下来的
所以Linux中TCP协议就需要自行的界定、计算 超时边界
不过, 重传不会一直进行, 当重传累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接

关于超时的设定:

如果超时时间设的太长, 会影响整体的重传效率

如果超时时间设的太短, 有可能会频繁发送重复的包


至此, 本篇文章主要内容结束
感谢阅读~
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)

作者: 哈米d1ch 发表日期:2024 年 1 月 11 日