TCP 中的三次握手和四次挥手

Table of Contents

  1. 前言
  2. 数据报头部
  3. 三次握手
    1. SYN 攻击
  4. 四次挥手
    1. 半连接
    2. TIME_WAIT
  5. 结语
  6. 参考链接

前言

TCP 中的三次握手和四次挥手应该是非常著名的两个问题了,一方面这两个过程基本上属于面试必考题目,另一方面,这两个过程在实际的使用中也非常重要。

这里就来简单的看一下这两个过程是怎么一回事吧。

数据报头部

在学习三次握手和四次挥手的具体过程之前,我觉得有必要先对 TCP/IP 的数据报头部进行一定的了解,当然,不需要了解所有信息。

TCP 中的三次握手和四次挥手

上面的图片是 IP 数据报的头部结构,在这里,我们只需要明白:IP 数据报的头部会携带 源地址目的地址 的信息就足够了。

然后就是 TCP 数据报的头部结构:

TCP 中的三次握手和四次挥手

TCP 头部的结构和 IP 头部一样,都比较复杂,但是在这里,我们也只需要关注其中的部分信息:

TCP 头部 作用或含义
源端口目的端口 和 IP 头部的 源地址目的地址 一起唯一地标识了每个连接
序列号 用来标识发送的字节流,即:发送的每个字节都是进行了编号的
确认号 确认发送方希望接下来接收到的数据报的序列号,即:确认收到了对方发送的前一个数据报
SYN 标志 建立新连接时该字段启用,表明本次发送的 序列号 为自身的 初始序列号
ACK 标志 表明 确认号 字段有效,连接建立以后一般都处于启用状态
FIN 标志 表明该报文的发送方已经结束向对方发送数据

三次握手

在对 TCP/IP 数据报的头部结构有了一定的了解后,就可以进入正题了,首先是三次握手的过程:

TCP 中的三次握手和四次挥手

三次握手过程的文字描述:

  1. 第一次握手,客户端向服务端发送数据报,该数据报的 SYN 标志为 1,而序列号的值为 x
  2. 第二次握手,服务端向客户端发送数据报,该数据报的 SYN 标志和 ACK 标志为 1,而序列号的值为 y,确认号的值为 x + 1
  3. 第三次握手,客户端向服务端发送数据报,该数据报的 ACK 表示为 1,而序列号的值为 x + 1,确认号的值为 y + 1

这个过程其实不难,但是,更重要的是对这个过程的理解,或者说,就是需要明白:为什么要进行三次握手?

这个问题存在很多种解释,个人感觉最好的一个解释应该是 知乎 上的一个回答,这个回答从三次握手的目的出发对为什么需要三次握手进行了解释:

  1. 三次握手的目的除了让通信双方了解一个连接正在建立以外,还在于利用数据报的头部交换彼此的 初始序列号
  2. 当 SYN 标志位 1 时,会表明当前数据报头部的序列号就是 初始序列号
  3. 第一次握手时,客户端将自身的 初始序列号 发送给了服务端
  4. 第二次握手时,服务端通过确认号确认了客户端的 初始序列号
  5. 第二次握手时,服务端将自身的 初始序列号 发送给了客户端
  6. 第三次握手时,客户端通过确认号确认了服务端的 初始序列号

也就是说,三次握手的过程可以简化为客户端和服务端交换彼此的 初始序列号 的过程,每次交换需要:

  1. 发送 初始序列号 给另一方
  2. 接受到另一方的 确认号 表明 初始序列号 已经成功交给另一方

在这个过程中,由于单个数据报可以同时携带确认号、初始序列号,因此,将下面四个过程压缩成了三次握手:

  1. 客户端 -> 服务端:我的初始序列号为 X
  2. 服务端 -> 客户端:确认你的初始序列号为 X
  3. 服务端 -> 客户端:我的初始序列号为 Y
  4. 客户端 -> 服务端:确认你的初始序列号为 Y

可以看到,这个过程必然是需要三次握手的,少一次显得不够,多一次显得多余。

当然了,还有其他的一些解释,有兴趣的可以看一下参考链接中的文章。

SYN 攻击

SYN 攻击是针对三次握手过程的一种攻击方式,通过观察三次握手的过程可以发现,当服务端向客户端发送 SYN-ACK 后还未进入完整的连接状态,而是处于 半连接 状态。只有在接收到客户端的 ACK 后才会转入完整的连接状态。

而 SYN 攻击便是通过短时间内伪造大量不存在的 IP 地址,向服务器不断地发送 SYN 包实现的。这使得服务端存在大量未确认的半连接,这些半连接只有等待服务端不断的重发直至超时才会断开。

这些伪造的 SYN 包将长时间占用未连接队列,正常的 SYN 请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。

四次挥手

四次挥手似乎没有三次握手那么有名,但也还是十分重要的一个过程,其具体过程如下:

TCP 中的三次握手和四次挥手

四次挥手过程的文字描述:

  1. 第一次挥手,主动关闭者 A 向被动关闭者 B 发送 FIN 标志为 1 的数据报,并指明希望接收者看到的自己当前的序列号 u
  2. 第二次挥手,被动关闭者 B 将 u 值加一作为响应的确认号值,表明它已经成功接收到主动关闭者发送的 FIN
  3. 第三次挥手,被动关闭者 B 将身份转变为主动关闭者,并发送自己的 FIN,并指明希望接收者看到的自己当前的序列号 w
  4. 第四次挥手,A 将 w 值加一作为响应的确认号值,表明它已经成功接收到 B 发送的 FIN

和三次握手一样,我们需要的是对四次挥手过程的理解,这里就附上个人的理解好了:

  1. 四次挥手的过程其实就是关闭连接的过程
  2. 关闭连接的过程中,主动关闭者和被动关闭者需要停止各自的 发送接收 操作
  3. 任何一端只能主动关闭自身的 发送 操作
  4. 任何一端只能在确定对方已经停止 发送 操作以后才能停止相应的 接收 操作

也就是说,四次挥手的过程我们可以看成是客户端和服务端停止自身的 发送 操作并 通知 另一端的过程:

  1. 第一次挥手,主动关闭者通过发送带有 FIN 标志的数据报告诉被动关闭者:我的数据已经发送完了,你可以停止接受操作了
  2. 第二次挥手,被动关闭者通过发送带有相应确认号的数据报告诉主动关闭者:好的,你的通知我已受到,你可以停止发送操作了
  3. 第三次和第四次操作正好相反,原本的被动关闭者变为主动关闭者,关闭自身的 发送 操作并通知另一端

由于任何一端停止自身的 发送 操作并 通知 另一端都需要两次挥手的过程,因此,总的来说就需要四次挥手了。

半连接

通过对四次挥手过程的理解我们可以发现,连接的关闭过程是由两端分别停止自身的数据 发送 操作完成的,因此,假如一方停止发送操作,而另一方继续发送数据,这时便进入了半连接状态。

TIME_WAIT

TIME_WAIT 这个状态也是比较常见的一个问题了,第四次挥手后进行第四次挥手的一方会进入 TIME_WAIT 状态,要至少等待 2MSL 才关闭连接。

这是为了避免另一端没有收到自己的 ACK 又进行了 FIN 的重发,如果自己直接就把连接关了,那么就收不到这个 FIN 数据报了。这样一来,另一端就会长时间处在 LAST_ACK的状态。

虽然 TIME_WAIT 这个状态是出于好意,但有些时候还是为造成一些问题,特别是在 Web 服务器这种需要主动关闭连接的服务端。

2MSL 的时间长度默认情况下并不短,通常情况下可能有 30~300 秒,这意味着在这个时间段类相应的 端口 资源是一直被占据的,这对相当依赖有限的端口资源的服务器来说是难以接受的。

因此,可以考虑通过将 2MSL 调低来解决这样问题。

结语

说起来,学习计算机网络基础的时候,并没有怎么学习关于三次握手和四次挥手的内容,基本上都是简单的了解了一下就完事了。

直到面试遇到了这个问题 @_@

然后才发现,这里面的弯弯道道也还不少,而且,似乎离我们并不是那么远,也许,实际操作中的一些问题就是由这两个过程导致的。

所以说,这两个过程能称为面试问题中的常客也不是没有道理的,是真的很重要。

注:三次握手和四次挥手中还有一个比较重要的内容是状态的转换,这里基本上没有提及这方面的内容,有需要或有兴趣的可以查阅相关的资料。

参考链接

上一篇:SSH框架使用中存在的诡异异常


下一篇:深入char、varchar、text和nchar、nvarchar、ntext的区别详解