4. 传输层基石
聊到网络协议,很多人一上来就啃报文格式,结果越看越晕。我个人习惯是,先搞懂传输层到底在干嘛。
你想想看,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(动态端口) | 客户端临时分配,比如浏览器访问网页时随机用 |
端口号配合IP地址,就构成了“套接字”(Socket)。一个连接由四元组唯一标识:源IP + 源端口 + 目的IP + 目的端口。这个四元组,你会在后面的TCP/UDP编程中反复见到。
4.2 TCP:面向连接的可靠传输
TCP(Transmission Control Protocol)是传输层的“老大哥”。它提供面向连接、可靠、基于字节流的通信。什么叫面向连接?就是通信前先“握手”,通信完再“挥手”。
4.2.1 三次握手:建立连接
为什么是三次,不是两次或四次?我当年面试时也被问过这个问题。咱们直接看流程:
- 第一次握手(SYN):客户端发送SYN包(seq=x),进入SYN_SENT状态。意思是:“我想跟你说话,这是我的初始序号x。”
- 第二次握手(SYN+ACK):服务器收到后,回复SYN+ACK包(seq=y, ack=x+1),进入SYN_RCVD状态。意思是:“收到,我准备好了,这是我的初始序号y。”
- 第三次握手(ACK):客户端发送ACK包(seq=x+1, ack=y+1),进入ESTABLISHED状态。服务器收到后也进入ESTABLISHED。意思是:“好的,确认收到,咱们开始聊。”
核心要点:三次握手的本质是双方互相确认对方的收发能力正常,并且同步初始序号。两次握手无法防止“已失效的连接请求”到达服务器后建立错误连接。
我在项目中遇到过一个问题:一个低功耗设备每隔5分钟上报数据,每次都要重新建立TCP连接。结果发现设备频繁断连,抓包一看,是服务器端半连接队列满了。嗯,这就是典型的SYN Flood攻击雏形,或者服务器并发处理能力不足。
4.2.2 四次挥手:断开连接
断开连接比建立连接多一次,因为TCP是全双工的——两边可以同时收发。挥手时,每一方都要单独关闭自己的发送通道。
- 第一次挥手(FIN):主动关闭方发送FIN包(seq=u),进入FIN_WAIT_1状态。意思是:“我这边没数据要发了。”
- 第二次挥手(ACK):被动关闭方回复ACK(ack=u+1),进入CLOSE_WAIT状态。主动方收到后进入FIN_WAIT_2。意思是:“知道了,但我可能还有数据要发。”
- 第三次挥手(FIN):被动关闭方发送FIN包(seq=v),进入LAST_ACK状态。意思是:“我这边也发完了,可以关了。”
- 第四次挥手(ACK):主动关闭方回复ACK(ack=v+1),进入TIME_WAIT状态。被动方收到后直接CLOSED。主动方等待2MSL后CLOSED。
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的典型场景——宁可丢包,不要延迟。
4.4 TCP vs UDP:怎么选?
这个问题没有标准答案,但有几个判断维度:
- 数据完整性要求高?选TCP。比如文件传输、网页浏览、邮件。
- 实时性要求高?选UDP。比如语音通话、视频会议、游戏。
- 资源受限?选UDP。嵌入式设备内存小,TCP的状态机太复杂。
- 广播/多播?只能用UDP。TCP是点对点的。
我记得有一次给一个智能家居设备选型,设备只有64KB RAM,跑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这些协议是怎么站在传输层肩膀上工作的。