TCP 三次握手与四次挥手

TCP抓包

之前用 Java 实现过简单的 socket 编程,于是顺便也对三次握手的过程重新认识一下。

为了不停留在理论表面,更直观的观察连接建立的过程,采用了 Wireshark 抓包软件进行状态跟踪。

因为 socket 通信我用的是本机回路,不经过网卡,因此 Wireshark 无法获取。需要安装 npcap。安装完成后打开 Wireshark,在 device 列表中会多出一个 Npcap Loopback Adapter,即本地环回地址,选择此接口。

设置过滤条件 tcp.port == 2017(2017为我设置的socket通信的端口),启动客户端后显示如下:

三次握手

三次握手

  • 第一次握手: 客户端主动打开连接,向服务器发送 SYN = 1,表示要建立连接,同时初始化序号 Sequence Number(简称为Seq)为 0,然后进入 SYN_SEND 状态,等待服务器回应。
  • 第二次握手: 服务器收到 SYN ,向客户端发送 ACK = 1、SYN = 1、Ack Number = Seq(客户端) + 1,同时初始化序号 Seq = 0,然后服务器进入 SYN_RCVD 状态。
  • 第三次握手:客户端收到服务器发过来的确认报文,检查 Ack Number 是否正确,向服务器发送 ACK = 1、Ack Number = Seq(服务端) + 1,同时 Seq = 0 + 1。进入 ESTABLISHED 状态。此时 SYN 不再置为 1,服务器收到报文检查正确后,进入 ESTABLISHED 状态,完成三次握手,TCP连接建立。

对于建立连接的三次握手,主要目的是初始化序号 Seq,这个序号作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输问题而乱序,因为TCP会用这个序号来拼接数据。

下面是TCP首部格式(摘自计算机与网络(第七版))

TCP 三次握手与四次挥手_第1张图片
注:FIN报文与SYN报文都需要消耗一个序列号。

四次挥手

数据传输结束后,通信的双方都可释放连接。

下图是电脑(10.255.151.111)与百度(180.97.33.108)的通信过程:

TCP 三次握手与四次挥手_第2张图片

最后四条记录是四次挥手的过程。

  • 初始状态下A和B都处于 ESTABLISHED 状态。当A需要关闭TCP连接的时候,向B发送 FIN = 1。此时A进入 FIN_WAIT_1 状态
  • B 确认A的报文后,向A发送 ACK = 1,表明自己接收到了关闭连接的请求,但还没有准备好关闭连接(可能自己还有数据要发送)。发送完毕后,B进入 CLOSE_WAIT 状态,A接收到确认包后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。
  • 若 B 已经没有要向 A 发送的数据,向A发送 FIN = 1。发送完毕后,B进入 LAST_ACK 状态。
  • A 收到B的报文后,向B发送 ACK = 1。 发送完毕后,A进入 TIME_WAIT 状态。在 TIME_WAIT 状态下,A经过 2MSL(最长报文段寿命)时间后就进入关闭状态。B接收到A的确认包后,立即进入关闭状态。A和B都进入关闭状态后整个TCP连接释放。

TCP状态转换(状态机)

TCP 三次握手与四次挥手_第3张图片
  • CLOSED — 初始状态,超时或连接关闭后进入此状态
  • LISTEN — 服务器端处于监听状态,可以接受连接
  • SYN_SENT — 客户端向服务器端发送 SYN 报文,此时客户端进入 SYN_SENT 状态
  • SYN_RECV — 服务器端接收到到 SYN 报文后进入此状态
  • ESTABLISHED — 表示连接建立,可以开始传输数据
  • FIN_WAIT_1 — 请求终止连接,向对方发送 FIN 报文后进入此状态
  • FIN_WAIT_2 — 收到 对 FIN 的 ACK 后进入该状态
  • TIME_WAIT — 最常见的一种状态。主动关闭连接的一方收到对方发送的 FIN 并返回 ACK 后进入此状态,等待 2MSL 时间后进入 CLOSED 状态。
  • CLOSING:表示同时关闭。发生在发送 FIN 报文之后,本应先收到 ACK 报文,却先收到对方的 FIN 报文,从 FIN_WAIT_1 的状态进入 CLOSING 状态
  • CLOSE_WAIT — 接收到 FIN 后进入此状态,并向对方发送 ACK
  • LAST_ACK — 被动关闭一方发送 FIN 报文后所处的状态
  • CLOSED — 收到 ACK 报文后,被动关闭一方进入 CLOSED 状态

关于TIME_WAIT

通信双方建立TCP连接后,主动关闭连接的一方会进入 TIME_WAIT 状态。

TIME_WAIT 状态存在的原因:

  • 保证TCP全双工连接的可靠终止。在四次挥手过程中,最终的 ACK 是由主动关闭连接的一端发出的,如果 ACK 丢失,另一方将重发最终的 FIN,因此主动关闭一方必须维护状态信息允许它重发最终的 ACK。如果主动关闭一方不维持 TIME_WAIT 状态,而是处于 CLOSED 状态,那么将会对发来的 FIN 回应 RST ,另一方收到 RST 后认为连接出现异常。
  • 如果主动关闭的一方不进入 TIME_WAIT 直接 CLOSED,然后发起一个新连接。原有的数据包因某些原因在新连接建立后才到达目的端,造成新旧数据包混淆,发生错误。因为 TIME_WAIT 状态持续 2MSL,可以保证当成功建立一个新的TCP连接时,来自旧连接的数据包已经在网络中消失。

在Linux中,可以通过以下命令查看 TIME_WAIT 参数:

sysctl -a | grep time | grep wait

输出结果:

net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120

可以看到,TIME_WAIT 时间为120秒。对于服务器来说,如果出现 TIME_WAIT 数过多,就需要对相关的TCP参数进行修改,比如允许 TIME-WAIT Socket 的复用。

关于RST

在TCP协议中 RST 表示复位,用来异常的关闭连接。发送端会丢弃缓存区的包直接发送 RST 包;接收端收到 RST 包后,不必发送 ACK 包确认。

RST 出现的原因主要有以下两种:

  • 端口未打开
  • 向一个已关闭的 Socket 发送数据。例如客户端向服务器发送数据,服务器接收到数据,但是发现 Socket 已经关闭,会返回 RST 给客户端。此时客户端就会提示 Connection reset 或类似的信息。
有一种攻击称为 RST 攻击,就是利用了 RST 的特点,比如客户端和服务器之间建立了TCP连接,此时第三方伪造客户端发了一个 RST 给服务器,使服务器异常的断开了与客户端之间的连接。

OSI模型与TCP/IP参考模型

TCP 三次握手与四次挥手_第4张图片
OSI 模型是理想模型,但其实用性不强,TCP/IP 参考模型是现今广泛使用的体系结构。TCP/IP 参考模型分为四层,但实际上起作用的主要是上面三层。

你可能感兴趣的:(Protocol)