4. 传输层详解:端口、UDP与TCP

传输层是整个TCP/IP协议栈的「心脏」。网络层负责把数据包送到正确的机器,但机器上跑着那么多应用——浏览器、邮件客户端、游戏——数据该交给谁?这就是传输层要解决的核心问题。

我刚开始学网络时,总觉得IP地址就够了,干嘛还要端口?后来自己写了个简单的聊天程序,才发现两台机器虽然能ping通,但数据根本不知道往哪个进程送。嗯,端口号就是干这个的。

4.1 端口号:应用的「门牌号」

端口号是一个16位的数字,范围0~65535。它和IP地址组合在一起,构成一个套接字(Socket),唯一标识网络中的一个进程。

核心公式:Socket = IP地址 + 端口号

端口号分三类:

类别 范围 说明 常见例子
知名端口 0~1023 系统保留,需管理员权限 HTTP(80), HTTPS(443), SSH(22)
注册端口 1024~49151 可由用户进程使用 MySQL(3306), Redis(6379)
动态/私有端口 49152~65535 临时分配给客户端 浏览器随机分配

避坑指南:我曾经在部署Web服务时,发现端口8080被另一个进程占用了。用netstat -tuln一查,才发现是之前测试的Tomcat没关干净。所以启动服务前,养成先检查端口的好习惯。

4.2 UDP协议:简单粗暴,但快

UDP(用户数据报协议)是传输层的「轻骑兵」。它不做连接建立,不保证可靠交付,不关心顺序——说白了就是「发了就不管了」。

4.2.1 UDP的特点

  • 无连接:发送前不需要握手,直接发
  • 不可靠:数据丢了不重传,乱序了不调整
  • 面向报文:应用层给多少,UDP就发多少,不拆分也不合并
  • 低开销:头部只有8字节

你想想看,什么时候用UDP?视频通话、在线游戏、DNS查询——这些场景丢一两个包无所谓,但延迟高了就卡死。UDP正好合适。

4.2.2 UDP报文结构

UDP头部只有4个字段,每个字段2字节:

字段 长度 说明
源端口 16位 发送方端口(可选,可为0)
目的端口 16位 接收方端口
长度 16位 UDP头部+数据的总长度
校验和 16位 检测传输错误(可选,IPv4中可设为0)
// UDP报文结构示意
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          源端口          |         目的端口           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           长度           |          校验和            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            数据                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

注意:UDP的校验和在IPv4中是可选的。如果设为0,接收方就不做校验。这在某些高性能场景下可以接受,但生产环境我建议永远不要关掉校验和。我曾经见过一个嵌入式设备因为关了UDP校验和,偶尔收到损坏的数据,排查了整整两天才找到原因。

4.3 TCP协议:可靠,但代价不小

TCP(传输控制协议)和UDP正好相反。它提供面向连接、可靠、基于字节流的传输服务。说白了,TCP就是快递的「保价+签收」服务——慢是慢了点,但东西一定到。

4.3.1 TCP的特点

  • 面向连接:通信前必须建立连接(三次握手)
  • 可靠传输:确认重传、序号机制、流量控制
  • 面向字节流:应用层数据被TCP拆分成合适的段
  • 全双工通信:双方可以同时收发数据
  • 头部较大:标准头部20字节,带选项可达60字节

4.3.2 TCP报文结构

TCP头部比UDP复杂得多。核心字段如下:

字段 长度 说明
源端口 16位 发送方端口
目的端口 16位 接收方端口
序号 32位 本报文段第一个字节的序号
确认号 32位 期望收到的下一个字节序号
数据偏移 4位 头部长度(单位:4字节)
标志位 6位 URG/ACK/PSH/RST/SYN/FIN
窗口 16位 接收窗口大小(流量控制)
校验和 16位 强制字段,必须计算
紧急指针 16位 URG=1时有效

关键标志位速记:

  • SYN:发起连接(Synchronize)
  • ACK:确认收到(Acknowledgment)
  • FIN:结束连接(Finish)
  • RST:重置连接(Reset)
  • PSH:立即推送(Push)
  • URG:紧急数据(Urgent)

4.4 三次握手:建立连接

TCP建立连接需要三次交互,俗称「三次握手」。为什么是三次?不是两次也不是四次?我当年也困惑过,后来才明白——三次握手是为了确认双方的收发能力都正常。

流程如下:

  1. 客户端 → 服务器:SYN
    客户端发送SYN=1,随机生成初始序号seq=x。此时客户端进入SYN_SENT状态。
  2. 服务器 → 客户端:SYN+ACK
    服务器收到后,回复SYN=1、ACK=1,确认号ack=x+1,并带上自己的序号seq=y。服务器进入SYN_RCVD状态。
  3. 客户端 → 服务器:ACK
    客户端收到后,发送ACK=1,确认号ack=y+1。双方进入ESTABLISHED状态。

个人经验:我在排查一个HTTP服务连接超时问题时,用tcpdump抓包发现三次握手只完成了前两步,第三步的ACK始终没发出去。最后定位到是客户端的防火墙把出站SYN+ACK的响应给丢了。所以遇到连接建不起来的场景,先抓包看握手到哪一步断了。

4.5 四次挥手:断开连接

断开连接需要四次交互,因为TCP是全双工的——每一方都需要单独关闭自己的发送通道。

  1. 主动方 → 被动方:FIN
    主动方发送FIN=1,序号seq=u。主动方进入FIN_WAIT_1状态。
  2. 被动方 → 主动方:ACK
    被动方回复ACK=1,确认号ack=u+1。被动方进入CLOSE_WAIT状态,主动方进入FIN_WAIT_2状态。
  3. 被动方 → 主动方:FIN
    被动方数据发送完毕后,发送FIN=1。被动方进入LAST_ACK状态。
  4. 主动方 → 被动方:ACK
    主动方回复ACK=1,确认号ack=v+1。主动方进入TIME_WAIT状态,等待2MSL后关闭。

注意TIME_WAIT:主动关闭方在发送最后一个ACK后,会进入TIME_WAIT状态,持续2MSL(约1~4分钟)。为什么要等?万一最后一个ACK丢了,被动方会重发FIN,主动方需要能重新回复ACK。我曾经在压测时遇到大量TIME_WAIT连接耗尽端口,后来通过调整net.ipv4.tcp_tw_reuse参数解决了。

4.6 本章知识体系

下面这张图把传输层的核心概念串起来了:

传输层知识体系 传输层知识体系 传输层 端口号 (0~65535) UDP协议 TCP协议 知名端口 (0~1023) 注册端口 (1024~49151) 动态端口 (49152~65535) 无连接 · 不可靠 头部8字节 · 面向报文 应用:DNS · 视频 · 游戏 面向连接 · 可靠 头部20~60字节 · 字节流 三次握手 · 四次挥手 Socket = IP地址 + 端口号 传输层为应用层提供端到端通信

传输层就讲到这里。端口号是基础,UDP和TCP各有各的战场。我个人建议:能选TCP就别用UDP,除非你很清楚自己在做什么。但反过来,如果你做的是实时音视频,硬上TCP反而会把自己坑死——重传导致的延迟比丢包更可怕。