MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

TCP的三次握手与四次挥手过程

2021-04-105.4k 阅读

TCP 协议简介

TCP(Transmission Control Protocol)即传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。在网络通信中,TCP 协议通过一系列机制来确保数据的可靠传输,其中三次握手和四次挥手是 TCP 连接建立和断开过程中的关键步骤。

TCP 协议的设计目标是在不可靠的网络环境中提供可靠的数据传输服务。它通过对数据进行编号、确认、重传等操作,来保证数据的完整性和顺序性。例如,在一个文件传输场景中,TCP 协议能够确保文件的每一个字节都被正确无误地从发送方传输到接收方,不会出现数据丢失或乱序的情况。

三次握手过程

第一次握手(SYN)

在这个阶段,客户端向服务器发送一个 SYN(Synchronize)包,该包中包含一个随机生成的序列号(Sequence Number,简称 seq),假设这个序列号为 x。这个包的作用是向服务器表明客户端想要建立一个 TCP 连接,并告知服务器自己初始的序列号。

从原理上来说,客户端发送 SYN 包就像是在向服务器“打招呼”,告诉服务器:“我想和你建立连接,这是我的起始序列号 x”。此时客户端进入 SYN_SENT 状态,等待服务器的回应。

第二次握手(SYN + ACK)

服务器接收到客户端的 SYN 包后,会向客户端发送一个 SYN + ACK 包作为回应。这个包中包含两个重要信息:一是服务器自己生成的序列号 y,二是对客户端序列号的确认号(Acknowledgment Number,简称 ack),确认号为客户端的序列号 x 加 1,即 x + 1。

这一步相当于服务器对客户端说:“我收到了你的连接请求,我也准备好建立连接了,这是我的起始序列号 y,同时我确认你发过来的序列号 x”。服务器发送完这个包后进入 SYN_RCVD 状态。

第三次握手(ACK)

客户端收到服务器的 SYN + ACK 包后,会向服务器发送一个 ACK 包。这个 ACK 包中的确认号为服务器的序列号 y 加 1,即 y + 1,序列号为客户端在第一次握手中发送的序列号 x 加 1,即 x + 1。

这一步就像是客户端对服务器说:“我收到了你准备建立连接的回应,我确认你的序列号 y”。当服务器收到这个 ACK 包后,连接就正式建立成功,双方都进入 ESTABLISHED 状态,可以开始进行数据传输了。

三次握手的必要性

为什么要进行三次握手呢?如果只进行两次握手,当客户端发送的第一个 SYN 包因为网络延迟等原因在网络中滞留了一段时间,然后客户端重新发送了一个 SYN 包并成功建立了连接。当连接关闭后,那个滞留的 SYN 包到达了服务器,服务器会误以为是客户端又发起了一个新的连接请求,从而响应这个 SYN 包,这样就会造成资源的浪费。而三次握手可以有效地避免这种情况的发生,确保连接的可靠性。

三次握手的代码示例(Python + socket 模块)

import socket

# 创建客户端 socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 12345)

# 尝试连接服务器(触发三次握手)
try:
    client_socket.connect(server_address)
    print("成功与服务器建立连接")
except socket.error as e:
    print(f"连接失败: {e}")
finally:
    client_socket.close()

上述代码使用 Python 的 socket 模块创建了一个客户端 socket,并尝试连接到本地服务器(地址为 127.0.0.1,端口为 12345)。在执行 connect 方法时,就会触发 TCP 的三次握手过程。如果连接成功,会打印“成功与服务器建立连接”;如果失败,则会打印错误信息。

四次挥手过程

第一次挥手(FIN)

当客户端想要关闭连接时,它会向服务器发送一个 FIN(Finish)包,这个包中包含客户端的序列号(假设为 u)。这相当于客户端对服务器说:“我已经没有数据要发送了,准备关闭连接”。此时客户端进入 FIN_WAIT_1 状态。

第二次挥手(ACK)

服务器接收到客户端的 FIN 包后,会向客户端发送一个 ACK 包作为回应。这个 ACK 包中的确认号为客户端的序列号 u 加 1,即 u + 1,服务器自己的序列号为 v。这一步表示服务器已经知道客户端要关闭连接了,但是服务器可能还有数据要发送给客户端。服务器发送完这个 ACK 包后进入 CLOSE_WAIT 状态。

第三次挥手(FIN)

当服务器处理完自己要发送的数据后,它会向客户端发送一个 FIN 包,这个包中包含服务器的序列号(假设为 v)。这相当于服务器对客户端说:“我也没有数据要发送了,同意关闭连接”。服务器发送完这个包后进入 LAST_ACK 状态。

第四次挥手(ACK)

客户端接收到服务器的 FIN 包后,会向服务器发送一个 ACK 包。这个 ACK 包中的确认号为服务器的序列号 v 加 1,即 v + 1,客户端自己的序列号为 u + 1。客户端发送完这个 ACK 包后进入 TIME_WAIT 状态。经过一段时间(通常是 2MSL,MSL 为最大段生命周期)后,客户端正式关闭连接。服务器在收到客户端的 ACK 包后,也会立即关闭连接。

四次挥手的必要性

四次挥手的过程确保了双方都能有条不紊地关闭连接,并且保证数据不会丢失。因为在客户端发送 FIN 包后,服务器可能还有未发送完的数据,所以服务器需要先回应 ACK 表示知道客户端要关闭连接,等自己数据发送完后再发送 FIN 包。同样,客户端收到服务器的 FIN 包后,也要回应 ACK 确认,这样才能保证双方都正常关闭连接。

四次挥手的代码示例(Python + socket 模块)

import socket

# 创建服务器 socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 12345)
server_socket.bind(server_address)
server_socket.listen(1)

print("等待客户端连接...")
client_socket, client_address = server_socket.accept()
print(f"客户端 {client_address} 已连接")

try:
    # 接收数据(模拟业务处理)
    data = client_socket.recv(1024)
    print(f"接收到客户端数据: {data.decode()}")

    # 关闭连接(模拟四次挥手)
    client_socket.send(b'FIN')
    print("向客户端发送 FIN 包")
    ack = client_socket.recv(1024)
    print(f"收到客户端的 ACK: {ack.decode()}")

    client_socket.send(b'FIN')
    print("再次向客户端发送 FIN 包")
    final_ack = client_socket.recv(1024)
    print(f"收到客户端的最终 ACK: {final_ack.decode()}")
finally:
    client_socket.close()
    server_socket.close()

上述代码首先创建了一个服务器 socket,绑定到本地地址 127.0.0.1 和端口 12345,并监听客户端连接。当客户端连接后,模拟接收客户端数据,然后通过发送 FIN 包来模拟四次挥手的过程。在每次发送 FIN 包后,等待接收客户端的 ACK 包,并打印相关信息。

深入理解三次握手和四次挥手的细节

序列号和确认号的作用

在三次握手和四次挥手过程中,序列号和确认号起着至关重要的作用。序列号用于标识发送方发送的数据字节流中的位置,而确认号则用于告知发送方接收方已经成功接收了哪些数据。通过序列号和确认号,TCP 协议能够实现数据的有序传输和可靠交付。例如,在数据传输过程中,如果发送方发送的数据丢失或损坏,接收方可以根据确认号告知发送方需要重传哪些数据。

状态转换

在三次握手和四次挥手过程中,客户端和服务器会经历不同的状态转换。以客户端为例,在初始状态下,客户端处于 CLOSED 状态。当客户端发送 SYN 包后,进入 SYN_SENT 状态;收到服务器的 SYN + ACK 包后,进入 ESTABLISHED 状态;当客户端发送 FIN 包后,进入 FIN_WAIT_1 状态,等等。了解这些状态转换有助于我们理解 TCP 连接的整个生命周期以及可能出现的问题。

常见问题及解决方法

在实际网络环境中,可能会出现一些与三次握手和四次挥手相关的问题。例如,在三次握手过程中,如果客户端长时间收不到服务器的 SYN + ACK 包,可能是网络延迟、服务器故障等原因导致的。此时客户端可以通过设置超时重传机制来重新发送 SYN 包。在四次挥手过程中,如果客户端在 TIME_WAIT 状态等待的时间过长,可能会占用过多的系统资源。可以通过合理调整 TIME_WAIT 时间来解决这个问题。

抓包分析三次握手和四次挥手

为了更直观地理解三次握手和四次挥手的过程,我们可以使用抓包工具(如 Wireshark)来捕获网络数据包,并分析其中的 TCP 连接建立和断开过程。

使用 Wireshark 抓包

  1. 打开 Wireshark 软件,并选择要捕获数据包的网络接口。
  2. 在浏览器中访问一个网站(或者运行上述代码示例进行连接测试),同时在 Wireshark 中开始捕获数据包。
  3. 捕获一段时间后,停止捕获。

分析三次握手数据包

在 Wireshark 的数据包列表中,找到 TCP 连接建立的相关数据包。通常可以通过查找 SYN 标志位来定位第一次握手的数据包。可以看到该数据包的源 IP 地址和端口(客户端)、目的 IP 地址和端口(服务器)以及序列号等信息。接着找到服务器回应的 SYN + ACK 包,以及客户端发送的 ACK 包,分析它们的序列号、确认号等字段,验证三次握手的过程。

分析四次挥手数据包

同样地,在数据包列表中查找 TCP 连接断开的相关数据包。通过查找 FIN 标志位找到客户端发送的 FIN 包,然后依次找到服务器回应的 ACK 包、服务器发送的 FIN 包以及客户端发送的最终 ACK 包,分析这些数据包的各个字段,深入理解四次挥手的过程。

TCP 与 UDP 在连接过程上的对比

UDP(User Datagram Protocol)即用户数据报协议,是一种无连接的传输层协议,与 TCP 在连接建立和断开过程上有很大的区别。

UDP 无连接特性

UDP 不需要像 TCP 那样进行三次握手来建立连接,也不需要四次挥手来断开连接。应用程序可以直接向目标主机发送 UDP 数据包,而不需要事先与对方进行任何协商。这使得 UDP 的传输效率较高,适合一些对实时性要求较高但对数据准确性要求相对较低的场景,如视频流传输、实时音频通话等。

可靠性对比

由于 TCP 通过三次握手和四次挥手以及序列号、确认号、重传等机制来保证数据的可靠传输,而 UDP 不具备这些机制,所以 TCP 传输的数据更加可靠。但是在一些特定场景下,如网络游戏中的实时数据传输,少量的数据丢失可能对游戏体验影响不大,而实时性更为重要,此时 UDP 可能更适合。

不同操作系统下 TCP 三次握手和四次挥手的实现差异

虽然 TCP 的三次握手和四次挥手的基本原理是一致的,但不同操作系统在具体实现上可能会存在一些差异。

Linux 操作系统

Linux 内核在处理 TCP 连接时,对三次握手和四次挥手的实现进行了优化,以提高网络性能。例如,在处理 SYN 洪水攻击方面,Linux 内核采用了一些机制来防止恶意的 SYN 包耗尽系统资源。在四次挥手过程中,Linux 内核会根据不同的情况合理调整 TIME_WAIT 状态的时间。

Windows 操作系统

Windows 操作系统在 TCP 连接管理方面也有自己的特点。它对 TCP 连接的状态管理和资源分配进行了优化,以适应不同的网络应用场景。在处理网络拥塞时,Windows 操作系统采用的算法与 Linux 有所不同,这也会对三次握手和四次挥手的过程产生一定的影响。

其他操作系统

除了 Linux 和 Windows,其他操作系统如 macOS、FreeBSD 等在 TCP 连接的实现上也都有各自的特点。这些差异主要体现在对网络资源的管理、拥塞控制算法、状态转换机制等方面。深入了解这些差异,有助于开发人员在不同操作系统环境下进行更优化的网络编程。

通过对 TCP 的三次握手和四次挥手过程的详细介绍,包括原理、代码示例、抓包分析以及与 UDP 的对比、不同操作系统的实现差异等方面,相信读者对 TCP 连接的建立和断开过程有了更深入、全面的理解,这对于进行后端网络编程以及解决网络相关问题都具有重要的意义。