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建立连接需要三次交互,俗称「三次握手」。为什么是三次?不是两次也不是四次?我当年也困惑过,后来才明白——三次握手是为了确认双方的收发能力都正常。
流程如下:
- 客户端 → 服务器:SYN
客户端发送SYN=1,随机生成初始序号seq=x。此时客户端进入SYN_SENT状态。 - 服务器 → 客户端:SYN+ACK
服务器收到后,回复SYN=1、ACK=1,确认号ack=x+1,并带上自己的序号seq=y。服务器进入SYN_RCVD状态。 - 客户端 → 服务器:ACK
客户端收到后,发送ACK=1,确认号ack=y+1。双方进入ESTABLISHED状态。
个人经验:我在排查一个HTTP服务连接超时问题时,用tcpdump抓包发现三次握手只完成了前两步,第三步的ACK始终没发出去。最后定位到是客户端的防火墙把出站SYN+ACK的响应给丢了。所以遇到连接建不起来的场景,先抓包看握手到哪一步断了。
4.5 四次挥手:断开连接
断开连接需要四次交互,因为TCP是全双工的——每一方都需要单独关闭自己的发送通道。
- 主动方 → 被动方:FIN
主动方发送FIN=1,序号seq=u。主动方进入FIN_WAIT_1状态。 - 被动方 → 主动方:ACK
被动方回复ACK=1,确认号ack=u+1。被动方进入CLOSE_WAIT状态,主动方进入FIN_WAIT_2状态。 - 被动方 → 主动方:FIN
被动方数据发送完毕后,发送FIN=1。被动方进入LAST_ACK状态。 - 主动方 → 被动方: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 本章知识体系
下面这张图把传输层的核心概念串起来了:
传输层就讲到这里。端口号是基础,UDP和TCP各有各的战场。我个人建议:能选TCP就别用UDP,除非你很清楚自己在做什么。但反过来,如果你做的是实时音视频,硬上TCP反而会把自己坑死——重传导致的延迟比丢包更可怕。