4、传输层协议详解:TCP三次握手与四次挥手、UDP协议特点、端口与套接字

传输层,说白了就是网络通信的「调度中心」。它负责把数据从源端送到目的端,至于怎么送、送得稳不稳,就看选 TCP 还是 UDP 了。我做了这么多年网络抓包和协议分析,可以负责任地告诉你:搞懂传输层,你才算真正入门了网络数据采集。

4.1 TCP 协议:可靠的「快递员」

TCP 全称是传输控制协议。它的核心就两个字:可靠。你发出去的数据,它保证能按顺序、不丢失、不重复地到达对端。代价是什么?慢,而且占用资源多。

TCP 的核心特性:

  • 面向连接:通信前必须先建立连接,通信完必须释放连接。
  • 全双工通信:双方可以同时收发数据。
  • 可靠传输:通过确认、重传、序号、校验和等机制保证数据完整。
  • 流量控制:防止发送方太快,把接收方「撑死」。
  • 拥塞控制:网络堵了,就主动降速,别给网络添乱。

4.1.1 三次握手:建立连接的门票

TCP 建立连接的过程,就是著名的「三次握手」。为什么是三次,不是两次或四次?我当年面试时也被问过这个问题。咱们一步步拆解。

  1. 第一次握手(SYN):客户端发送一个 SYN 报文,告诉服务器「我想跟你建立连接,我的初始序号是 x」。
  2. 第二次握手(SYN + ACK):服务器收到后,回复一个 SYN+ACK 报文,意思是「收到你的请求,我同意。我的初始序号是 y,你的序号 x 我确认了」。
  3. 第三次握手(ACK):客户端再发一个 ACK 报文,确认服务器的序号 y。至此,连接建立完成。

我个人的经验: 抓包分析时,三次握手是最容易识别的特征。如果你看到 SYN、SYN+ACK、ACK 三个报文连续出现,基本可以断定这是 TCP 连接的开始。我曾经排查过一个连接超时问题,发现第三次握手丢了,客户端一直重传 SYN,服务器却以为连接已经建立——典型的「半连接」状态。

为什么会这样?因为三次握手能防止「历史连接」干扰。如果只有两次握手,服务器收到一个延迟的旧 SYN 报文,就会误以为客户端要建立新连接,白白浪费资源。三次握手让客户端有机会在第三步拒绝这个历史连接。

TCP 三次握手流程图 客户端 服务器 ① SYN (seq=x) ② SYN+ACK (seq=y, ack=x+1) ③ ACK (seq=x+1, ack=y+1) CLOSED → SYN_SENT → ESTABLISHED LISTEN → SYN_RCVD → ESTABLISHED 三次握手的核心目的 1. 确认双方收发能力正常 2. 同步初始序号,防止历史连接干扰

4.1.2 四次挥手:优雅地告别

断开连接比建立连接多一次握手,一共四次。为什么?因为 TCP 是全双工的,每一方向都要单独关闭。

  1. 第一次挥手(FIN):客户端发送 FIN 报文,告诉服务器「我没数据要发了」。
  2. 第二次挥手(ACK):服务器回复 ACK,表示「知道了,但我还有数据要发,你先等着」。
  3. 第三次挥手(FIN):服务器数据发完了,发送 FIN 报文,告诉客户端「我也没数据了」。
  4. 第四次挥手(ACK):客户端回复 ACK,然后进入 TIME_WAIT 状态,等待 2MSL 后彻底关闭。

我曾经踩过的坑: 有一次做高并发服务器压测,发现大量连接处于 TIME_WAIT 状态,导致端口被占满,新连接无法建立。原因就是客户端主动关闭连接后,TIME_WAIT 持续了 2MSL(约 2 分钟)。解决方案是调整内核参数,或者让服务器主动关闭连接。嗯,这里要注意:TIME_WAIT 不是 bug,它是为了保证最后一个 ACK 能到达对端,防止旧连接的数据干扰新连接。

4.2 UDP 协议:简单粗暴的「飞毛腿」

UDP 全称是用户数据报协议。它跟 TCP 完全相反:不可靠、无连接、尽最大努力交付。你发出去的数据,它不保证能到,也不保证顺序。但好处是快,开销小。

特性 TCP UDP
连接方式 面向连接 无连接
可靠性 可靠,有确认重传 不可靠,尽最大努力
传输效率 低,有拥塞控制 高,无拥塞控制
报文边界 字节流,无边界 数据报,有边界
典型应用 HTTP、FTP、SMTP DNS、DHCP、视频直播

你想想看,视频通话时如果丢了一帧画面,你是愿意等它重传导致画面卡顿,还是直接跳过这一帧继续播放?显然是后者。这就是 UDP 的用武之地。

我个人的建议: 做网络数据采集时,如果采集的是实时流媒体或游戏数据,优先考虑 UDP。但如果你要采集网页内容或文件传输,必须用 TCP。选错了协议,数据要么不完整,要么延迟高到没法用。

4.3 端口与套接字:通信的「门牌号」

IP 地址定位到主机,端口号定位到进程。一个端口号是 16 位的,范围 0~65535。其中 0~1023 是知名端口,比如 HTTP 用 80,HTTPS 用 443,DNS 用 53。

套接字(Socket)是什么?说白了就是 IP 地址 + 端口号 的组合。一个 TCP 连接由四元组唯一标识:(源 IP,源端口,目的 IP,目的端口)。

端口分类:

  • 知名端口(0~1023):系统服务使用,需要管理员权限绑定。
  • 注册端口(1024~49151):用户进程使用,IANA 登记。
  • 动态/私有端口(49152~65535):客户端临时使用,由系统自动分配。

我记得有一次排查一个 Web 服务无法启动的问题,发现是端口 80 被另一个进程占用了。用 netstat -ano 一看,果然有个残留的 nginx 进程没杀掉。所以做网络编程时,端口冲突是个很常见的坑。

4.3.1 套接字编程的简单示例

下面是一个最简单的 TCP 客户端代码,用 Python 写的。你一看就明白套接字是怎么用的。

import socket

# 创建套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 连接服务器(IP + 端口)
server_address = ('192.168.1.100', 8080)
sock.connect(server_address)

# 发送数据
sock.sendall(b'Hello, Server!')

# 接收响应
data = sock.recv(1024)
print('Received:', data.decode())

# 关闭连接
sock.close()

这段代码做了四件事:创建套接字、连接、收发数据、关闭。你想想看,三次握手就发生在 connect() 这一步,四次挥手发生在 close() 这一步。底层细节都被操作系统封装好了,但作为网络工程师,你必须知道背后发生了什么。

注意: UDP 套接字不需要 connect(),直接用 sendto() 和 recvfrom() 收发数据。如果你用 UDP 写了一个客户端,却调用了 connect(),那它实际上只是绑定了一个对端地址,并没有建立真正的连接——UDP 的 connect() 是「伪连接」。

4.4 本章小结

传输层是网络协议栈的「腰部」,承上启下。TCP 用三次握手和四次挥手保证了可靠性,UDP 用无连接换取了速度。端口和套接字则是通信的「门牌号」,没有它们,数据就找不到对应的应用程序。

我个人习惯在抓包时先看传输层:如果是 TCP,重点关注握手和挥手是否完整;如果是 UDP,关注报文长度和校验和。这些细节,往往就是问题所在。