我们先前学习了HTTP应用层协议,那么我们就现在就继续从应用层往下学习传输层协议。
前言:先搞懂「传输层」是干嘛的?
我们先从网络分层的最基础逻辑说起:
- 网络层(IP 协议):只负责把数据从「一台主机」送到「另一台主机」(比如把你的电脑数据传到服务器),它不管数据到了主机后,该给哪个应用用。
- 传输层(UDP/TCP):负责端到端的精准交付------ 数据到了主机后,找到对应的应用程序(比如浏览器、微信、游戏),实现应用之间的通信。
举个例子:你电脑同时开了浏览器 (访问网页)和微信 (发消息),网络层把数据传到电脑后,传输层靠端口号区分:给 80 端口的是网页数据,给微信端口的是聊天数据,绝不会搞混。
1:传输层核心基础:端口号
1:基本特性
- 16 位数字,范围:
0 ~ 65535 - 一个进程可以绑定多个端口;一个端口只能被一个进程绑定 (否则会报
Address already in use错误)
2:端口号分类
| 端口范围 | 类型 | 用途 | 典型例子 |
|---|---|---|---|
| 0 ~ 1023 | 知名端口 | 系统 / 通用服务固定占用 | HTTP(80)、HTTPS(443)、SSH(22)、FTP(21) |
| 1024 ~ 65535 | 动态端口 | 操作系统随机分配给客户端 | 浏览器、微信等客户端的临时端口 |
3:五元组
网络中用5 个信息 确定唯一的通信链路,用netstat -n命令可查看:源IP + 源端口 + 目的IP + 目的端口 + 协议号(TCP/UDP)

2:UDP协议:简单粗暴的寄信
UDP 全称用户数据报协议(User Datagram Protocol) ,它的设计理念是极简、快速,像现实中「寄平信」:不用提前打招呼,丢了不负责,寄多少收多少。
1. UDP 报文格式
UDP 头部只有8 字节,非常轻量,结构如下:

- 源 / 目的端口:找到对应应用
- UDP 总长度:最大
64KB(首部 + 数据),超了需要应用层手动分包 - 校验和:校验数据是否损坏,出错直接丢弃
2:UDP核心四大特点
1:无连接
知道对方的IP + 端口 就能直接发数据,不需要提前建立连接,发完就走,效率极高。
2:不可靠
没有「确认收到」「丢包重传」机制:
- 数据丢了、乱序了,UDP 完全不管,也不会给应用层报错
- 像寄信,邮局不保证送达、不保证顺序
3:面向数据报
发多少,收多少,不拆分、不合并:
- 应用层发 100 字节,UDP 就发 100 字节
- 接收端必须一次 recvfrom 收完 100 字节,不能分 10 次每次收 10 字节
- 数据边界清晰,不会出现「粘包」
4:全双工
一个 UDP socket 既能读数据,也能写数据,双向通信无阻碍。
3. UDP 缓冲区特性
- 无发送缓冲区 :调用
sendto直接把数据交给内核,立刻发往网络层 - 有接收缓冲区:但不保证数据顺序,缓冲区满了,新数据直接丢弃
4. UDP 适用场景
追求速度、实时性,能容忍少量丢包的场景:
- 域名解析:DNS
- 动态 IP 分配:DHCP
- 实时直播、视频通话、网络游戏
- 简单文件传输:TFTP
3:TCP协议:严谨可靠的打电话
TCP 全称传输控制协议(Transmission Control Protocol) ,设计理念是可靠 + 高效,像现实中「打电话」:先接通、再通话、确认听到、最后挂断,绝不丢数据、不乱序。
TCP 为了实现可靠性,设计了一整套复杂机制,也是我们学习的重点。
1:TCP报文格式
TCP 头部最小 20 字节,最大 60 字节,核心字段如下:

关键标志位
SYN:请求建立连接ACK:确认应答有效FIN:请求关闭连接RST:强制断开连接(异常)
2:TCP核心可靠机制
TCP 能做到不丢包、不乱序、不重复,全靠以下机制:
1:确认应答
TCP 给每个字节数据编序列号(SEQ) ,接收方收到数据后,回确认号(ACK):
- 发送方发
1~1000字节,SEQ=1 - 接收方收到后,回 ACK=1001(意思:「我收到 1~1000,下次从 1001 发」)
2:超时重传
发送方发完数据,超过固定时间没收到 ACK ,就自动重发;靠序列号去重:接收方收到重复数据,直接丢弃。
3:连接管理:三次握手+四次挥手
TCP 是面向连接的协议,通信前必须建立连接,结束后必须断开连接。
① 三次握手:建立连接(客户端→服务端)
通俗类比:
-
客户端:「我要和你通信,准备好了吗?」(发 SYN)
-
服务端:「我收到了,我也准备好了!」(回 SYN+ACK)
-
客户端:「收到,连接建立!」(回 ACK)
**为什么是三次?**防止「失效的连接请求」被服务端接收,导致建立无效连接浪费资源。
② 四次挥手:断开连接
TCP 是全双工(双方都能发数据),所以双方都要主动关闭:
-
客户端:「我发完数据了,要关闭发送通道」(发 FIN)
-
服务端:「收到,我知道了」(回 ACK)
-
服务端:「我也发完了,要关闭」(发 FIN)
-
客户端:「收到,我等 2MSL 再彻底关」(回 ACK)
3:关键状态
1:TIME_WAIT
主动关闭连接的一方,会进入TIME_WAIT,等待2MSL(报文最大生存时间):
- 防止最后一个 ACK 丢失,服务端重发 FIN
- 清理网络中残留的旧报文,避免干扰新连接
2:CLOSE_WAIT
服务端收到客户端的 FIN 后,进入CLOSE_WAIT,如果一直停留,说明代码没调用 close 关闭 socket,是程序 BUG

4:TCP性能优化机制
TCP在保证可靠的同时,还做了大量优化提升传输速度:
1:滑动窗口
不用「发一个包、等一个 ACK」,一次发多个包,窗口大小就是「无需等待 ACK 可发送的最大数据量」,大幅提升吞吐率。

窗口大小指的是无需等待确认应答而可以继续发送数据的最大值. 上图的窗口
大小就是 4000 个字节(四个段).
发送前四个段的时候, 不需要等待任何 ACK, 直接发送;
收到第一个 ACK 后, 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推;
操作系统内核为了维护这个滑动窗口, 需要开辟 发送缓冲区来记录当前还有哪
些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉;
窗口越大, 则网络的吞吐率就越高;

2:快重传
发送方连续接收到3次相同的ACK,立即重传对应数据,不用等超时,减少等待时间。
如果遭传输过程中数据包丢了

当某一段报文段丢失之后, 发送端会一直收到 1001 这样的 ACK, 就像是在提醒
发送端 "我想要的是 1001" 一样;
如果发送端主机连续三次收到了同样一个 "1001" 这样的应答, 就会将对应的数
据 1001 - 2000 重新发送;
这个时候接收端收到了 1001 之后, 再次返回的 ACK 就是 7001 了(因为 2001 -
7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中;
这种机制被称为 "高速重发控制"(也叫 "快重传").
3:流量控制
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这
个时候如果发送端继续发送, 就会造成丢包, 继而引起丢包重传等等一系列连锁反应.
因此 TCP 支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量
控制**(Flow Control)**;
接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段, 通过 ACK 端通知发送端;
窗口大小字段越大, 说明网络的吞吐量越高;
接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
发送端接受到这个窗口之后, 就会减慢自己的发送速度;
如果接收端缓冲区满了, 就会将窗口置为 0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端

接收端如何把窗口大小告诉发送端呢? 回忆我们的 TCP 首部中, 有一个 16 位窗口字段, 就是存放了窗口大小信息;
那么问题来了, 16 位数字最大表示 65535, 那么 TCP 窗口最大就是 65535 字节么?
实际上, TCP 首部 40 字节选项中还包含了一个窗口扩大因子 M, 实际窗口大小是 窗口字段的值左移 M 位;
4:拥塞控制
虽然 TCP 有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题.
因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据, 是很有可能引起雪上加霜的.
TCP 引入 慢启动机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;

此处引入一个概念称为拥塞窗口
发送开始的时候, 定义拥塞窗口大小为 1;
每次收到一个 ACK 应答, 拥塞窗口加 1;
每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;
像上面这样的拥塞窗口增长速度, 是指数级别的. "慢启动" 只是指初使时慢, 但是增长速
度非常快.
为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.
此处引入一个叫做慢启动的阈值
当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长

当 TCP 开始启动的时候, 慢启动阈值等于窗口最大值;
在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回 1;
少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;
当 TCP 通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;
拥塞控制, 归根结底是 TCP 协议想尽可能快的把数据传输给对方, 但是又要避免给网络 造成太大压力的折中方案.
5:延迟应答
如果接收数据的主机立刻返回 ACK 应答, 这时候返回的窗口可能比较小。
假设接收端缓冲区为 1M. 一次收到了 500K 的数据; 如果立刻应答, 返回的窗口
就是 500K;
但实际上可能处理端处理的速度很快, 10ms 之内就把 500K 数据从缓冲区消费
掉了;
这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也
能处理过来;
如果接收端稍微等一会再应答, 比如等待 200ms 再应答, 那么这个时候返回的
窗口大小就是 1M;
一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
那么所有的包都可以延迟应答么? 肯定也不是;
数量限制: 每隔 N 个包就应答一次;
时间限制: 超过最大延迟时间就应答一次;
具体的数量和超时时间, 依操作系统不同也有差异; 一般 N 取 2, 超时时间取 200ms
6:捎带应答
在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是 "一发一收"的. 意味着客户端给服务器说了 "How are you", 服务器也会给客户端回一个 "Fine,thank you";
那么这个时候 ACK 就可以搭顺风车, 和服务器回应的 "Fine, thank you" 一起回给客户端

5:TCP面向字节流和粘包问题
1:面向字节流
TCP 有发送 / 接收缓冲区:
- 数据太长会拆分,太短会攒起来一起发
- 应用层读写无需一一匹配(写 100 次 1 字节,读 1 次 100 字节也可以)
2:粘包问题
因为 TCP 没有数据边界,应用层会收到连续的字节流,分不清「一个完整的数据包从哪开始、到哪结束」,这就是粘包。
解决方法:
- 定长包:固定每个包的大小
- 包头带长度:在数据包开头加「总长度」字段
- 分隔符:用特殊符号分隔数据包(如
\r\n)
6:TCP适用场景
追求可靠性,不能丢包的场景:
- 网页访问:HTTP/HTTPS
- 远程登录:SSH
- 文件传输:FTP
- 邮件发送:SMTP
4:UDP vs TCP 终极对比
| 对比维度 | UDP | TCP |
|---|---|---|
| 连接特性 | 无连接,知道 IP + 端口直接发 | 面向连接,必须三次握手建立、四次挥手断开 |
| 可靠性 | 不可靠,无确认、无重传、丢包 / 乱序不管 | 可靠,有 ACK 确认、超时重传、去重、严格有序 |
| 头部长度 | 固定8 字节,极小开销 | 最小20 字节,最大 60 字节,开销更大 |
| 传输模式 | 面向数据报,一次收发完整匹配 | 面向字节流,数据像水流,无固定边界 |
| 数据边界 | 有边界,无粘包问题 | 无边界,必然粘包,需应用层手动解决 |
| 缓冲区 | 无发送缓冲区;有接收缓冲区(不保证顺序,满则丢) | 有发送 + 接收缓冲区,自动排序、攒包 / 拆包 |
| 连接建立 | 无需建立,直接传输 | 必须三次握手,防止无效连接浪费资源 |
| 连接断开 | 直接断开,无挥手 | 必须四次挥手,全双工双向关闭 |
| 重传机制 | 无重传,丢包就丢 | 超时重传 + 快重传,保证数据必达 |
| 流量控制 | 无,发送方想发就发 | 有,接收方告知窗口大小,防止撑爆缓冲区 |
| 拥塞控制 | 无,网络堵了也照发 | 有,慢启动 + 拥塞避免,保护网络稳定 |
| 报文最大长度 | 限制64KB(含首部),超了需分包 | 无固定上限,由 MSS/MTU 自动分片 |
| 顺序保证 | 不保证,后发可能先到 | 严格保证顺序,按序列号重组 |
| 丢包处理 | 直接丢弃,不通知应用层 | 重传丢包,直到确认收到 |
| 全双工 | 支持,一个 Socket 双向通信 | 支持,全双工,双方可同时收发 |
| 错误处理 | 仅校验和,错了直接丢 | 校验 + 重传 + 排序,错误自动修复 |
| 编程复杂度 | 极低,API 简单,无需处理连接 / 重传 | 高,需处理连接、粘包、重传、状态机 |
| 系统资源 | 占用极低,无连接状态 | 占用高,需维护连接状态、缓冲区、窗口 |
| 传输效率 | 极高,延迟低,适合实时场景 | 较高,为可靠牺牲部分速度 |
| 核心设计理念 | 简单、快速、轻量化 | 可靠、有序、稳定、容错 |
| 典型应用协议 | DNS、DHCP、TFTP、直播、游戏、视频通话 | HTTP/HTTPS、FTP、SSH、SMTP、文件传输 |
| 适用场景 | 实时性优先,可容忍少量丢包 | 可靠性优先,绝对不能丢数据 |
5:总结
- 传输层的核心:靠端口号实现应用间通信,五元组唯一标识连接。
- UDP:极简、快速、无连接、不可靠,适合实时场景。
- TCP:复杂、可靠、面向连接,靠三次握手 / 四次挥手、确认重传、滑动窗口等机制,保证数据安全。
- 选择原则:要快选 UDP,要稳选 TCP。