4、传输层基石:TCP三次握手与四次挥手、UDP无连接通信、端口号的作用

聊到网络协议,很多人一上来就啃报文格式,结果越看越晕。我个人习惯是,先搞懂传输层到底在干嘛。

你想想看,IP层已经把数据包从北京扔到了上海,但它不关心这个包有没有丢、顺序对不对。传输层就是干这个的——它给上层应用提供可靠的、或者高效的通信管道。说白了,传输层是应用和网络之间的“翻译官”兼“快递员”。

4.1 端口号:应用的门牌号

先讲端口号,因为它是理解传输层的基础。一台服务器上跑着Web服务、SSH、数据库,数据包到了服务器IP之后,怎么知道该交给谁?

端口号就是答案。它是一个16位的数字,范围0~65535。我刚开始做嵌入式开发时,总把端口号和协议号搞混,后来踩了坑才记住:端口号是传输层的概念,用来区分同一台主机上的不同应用。

端口范围 分类 常见例子
0 - 1023 Well-Known Ports(系统端口) HTTP(80), HTTPS(443), SSH(22), FTP(21)
1024 - 49151 Registered Ports(注册端口) MySQL(3306), Redis(6379), Docker(2375)
49152 - 65535 Dynamic/Private Ports(动态端口) 客户端临时分配,比如浏览器访问网页时随机用
小技巧:抓包时看到源端口是 54321,别慌。那是操作系统临时分配的动态端口,用完就回收。我曾经在调试一个嵌入式设备时,发现设备连不上服务器,查了半天才发现是动态端口被防火墙拦了。

端口号配合IP地址,就构成了“套接字”(Socket)。一个连接由四元组唯一标识:源IP + 源端口 + 目的IP + 目的端口。这个四元组,你会在后面的TCP/UDP编程中反复见到。

4.2 TCP:面向连接的可靠传输

TCP(Transmission Control Protocol)是传输层的“老大哥”。它提供面向连接、可靠、基于字节流的通信。什么叫面向连接?就是通信前先“握手”,通信完再“挥手”。

4.2.1 三次握手:建立连接

为什么是三次,不是两次或四次?我当年面试时也被问过这个问题。咱们直接看流程:

  1. 第一次握手(SYN):客户端发送SYN包(seq=x),进入SYN_SENT状态。意思是:“我想跟你说话,这是我的初始序号x。”
  2. 第二次握手(SYN+ACK):服务器收到后,回复SYN+ACK包(seq=y, ack=x+1),进入SYN_RCVD状态。意思是:“收到,我准备好了,这是我的初始序号y。”
  3. 第三次握手(ACK):客户端发送ACK包(seq=x+1, ack=y+1),进入ESTABLISHED状态。服务器收到后也进入ESTABLISHED。意思是:“好的,确认收到,咱们开始聊。”

核心要点:三次握手的本质是双方互相确认对方的收发能力正常,并且同步初始序号。两次握手无法防止“已失效的连接请求”到达服务器后建立错误连接。

我在项目中遇到过一个问题:一个低功耗设备每隔5分钟上报数据,每次都要重新建立TCP连接。结果发现设备频繁断连,抓包一看,是服务器端半连接队列满了。嗯,这就是典型的SYN Flood攻击雏形,或者服务器并发处理能力不足。

TCP 三次握手流程图 客户端 服务器 ① SYN (seq=x) ② SYN+ACK (seq=y, ack=x+1) ③ ACK (seq=x+1, ack=y+1) CLOSED → SYN_SENT SYN_SENT → ESTABLISHED LISTEN → SYN_RCVD SYN_RCVD → ESTABLISHED 三次握手完成后,双方进入 ESTABLISHED 状态,开始传输数据

4.2.2 四次挥手:断开连接

断开连接比建立连接多一次,因为TCP是全双工的——两边可以同时收发。挥手时,每一方都要单独关闭自己的发送通道。

  1. 第一次挥手(FIN):主动关闭方发送FIN包(seq=u),进入FIN_WAIT_1状态。意思是:“我这边没数据要发了。”
  2. 第二次挥手(ACK):被动关闭方回复ACK(ack=u+1),进入CLOSE_WAIT状态。主动方收到后进入FIN_WAIT_2。意思是:“知道了,但我可能还有数据要发。”
  3. 第三次挥手(FIN):被动关闭方发送FIN包(seq=v),进入LAST_ACK状态。意思是:“我这边也发完了,可以关了。”
  4. 第四次挥手(ACK):主动关闭方回复ACK(ack=v+1),进入TIME_WAIT状态。被动方收到后直接CLOSED。主动方等待2MSL后CLOSED。
避坑指南:我曾经调试一个嵌入式网关,发现设备重启后无法立即重连服务器。抓包看到大量TIME_WAIT状态的连接。原因是主动关闭方要等待2MSL(Maximum Segment Lifetime,通常2分钟)才能彻底关闭。解决方案是调整SO_REUSEADDR选项,或者让服务器主动关闭连接。

4.3 UDP:无连接的“快枪手”

UDP(User Datagram Protocol)和TCP完全是两个路子。它不建立连接,不保证可靠,不保证顺序,甚至不保证数据一定到达。那它有什么用?

说白了,UDP就是“发了就不管”。但正是这种简单,让它拥有了极低的延迟和极小的开销。

特性 TCP UDP
连接状态 面向连接(三次握手) 无连接
可靠性 可靠(重传、确认、排序) 不可靠(尽力交付)
头部开销 20~60字节 8字节
传输方式 字节流 数据报(保留消息边界)
典型应用 HTTP、FTP、SSH、SMTP DNS、DHCP、视频直播、在线游戏

UDP的头部只有4个字段:源端口、目的端口、长度、校验和。简单到令人发指。我做过一个音视频传输的项目,最开始用TCP,结果因为重传导致画面卡顿严重。换成UDP后,丢几个包画面花一下,但整体流畅度大幅提升。这就是UDP的典型场景——宁可丢包,不要延迟。

经验之谈:很多初学者觉得UDP不可靠就没用。其实不然。在局域网内,UDP的丢包率极低。很多物联网设备用UDP+应用层ACK的方式,既保留了UDP的低延迟,又实现了可靠性。我习惯把这种方案叫“轻量级可靠UDP”。

4.4 TCP vs UDP:怎么选?

这个问题没有标准答案,但有几个判断维度:

  • 数据完整性要求高?选TCP。比如文件传输、网页浏览、邮件。
  • 实时性要求高?选UDP。比如语音通话、视频会议、游戏。
  • 资源受限?选UDP。嵌入式设备内存小,TCP的状态机太复杂。
  • 广播/多播?只能用UDP。TCP是点对点的。

我记得有一次给一个智能家居设备选型,设备只有64KB RAM,跑TCP协议栈都费劲。最后用了UDP+自定义重传机制,完美解决。说白了,没有最好的协议,只有最合适的协议。

传输层协议选择决策树 需要可靠传输? 是 → 选 TCP 否 → 选 UDP HTTP / FTP / SSH / SMTP DNS / DHCP / 视频 / 游戏 实际项目中,也常见 TCP + UDP 混合使用

4.5 实战:抓包看三次握手

光说不练假把式。我建议你装个Wireshark,随便访问一个网站,然后过滤 tcp.port == 80 或 tcp.port == 443。你会看到类似下面的交互:

# 三次握手(简化版)
1  0.000000  192.168.1.100 → 93.184.216.34  TCP  SYN  Seq=0
2  0.045123  93.184.216.34 → 192.168.1.100  TCP  SYN, ACK  Seq=0, Ack=1
3  0.045456  192.168.1.100 → 93.184.216.34  TCP  ACK  Seq=1, Ack=1

# 数据传输(HTTP GET)
4  0.045789  192.168.1.100 → 93.184.216.34  HTTP  GET / HTTP/1.1
5  0.089234  93.184.216.34 → 192.168.1.100  TCP  ACK  Seq=1, Ack=100
6  0.120567  93.184.216.34 → 192.168.1.100  HTTP  HTTP/1.1 200 OK

# 四次挥手
7  1.200000  192.168.1.100 → 93.184.216.34  TCP  FIN, ACK  Seq=100, Ack=200
8  1.201234  93.184.216.34 → 192.168.1.100  TCP  ACK  Seq=200, Ack=101
9  1.205678  93.184.216.34 → 192.168.1.100  TCP  FIN, ACK  Seq=200, Ack=101
10 1.206012  192.168.1.100 → 93.184.216.34  TCP  ACK  Seq=101, Ack=201

注意:实际抓包中Seq和Ack是相对值(Wireshark默认开启相对序列号),方便阅读。真实网络中是随机初始化的32位整数。

看到没?三次握手和四次挥手的每一个包都清晰可见。我刚开始学的时候,就是靠反复抓包、反复看,才真正理解了状态机的流转。纸上得来终觉浅,绝知此事要躬行。

好了,传输层的基石就讲到这里。端口号是门牌,TCP是可靠的快递员,UDP是随叫随到的跑腿。下一层,我们会进入应用层,看看HTTP、DNS这些协议是怎么站在传输层肩膀上工作的。