TCP/IP 与前端性能:从数据包到首次渲染的底层逻辑
我以前排首屏慢,第一反应总是看包体、看 JS 执行、看图片有没有压。后来有次页面本身不大,Performance 里主线程也不算忙,但用户就是等很久才看到第一屏。最后打开 Network 才发现,时间主要耗在连接和等待首字节上。
这是“从 URL 到页面展示”系列的第三篇。前面两篇聊完了浏览器导航、DNS 和传输层细节,这一篇继续往下钻一点:一个数据包究竟怎么跑完整条链路?TCP/IP 为什么会直接影响前端最关心的首次渲染?
前端性能优化不能只盯着 JS、CSS 和图片。用户从输入 URL 到看到页面,中间有一大段时间消耗在网络链路上。理解这条链路,很多性能优化手段才不只是“背结论”。
性能起点:TTFB 和 FP
排查页面加载慢时,我会先把两个时间点拆开看。
TTFB,也就是 Time To First Byte,表示从浏览器发起请求,到收到服务器第一个字节的时间。
它大致包含:
- DNS 解析
- TCP 连接
- TLS 握手
- 服务器处理
- 响应第一个字节传回浏览器
FP,也就是 First Paint,表示浏览器第一次把像素绘制到屏幕上的时间。
它大致可以理解为:
1FP = TTFB + HTML 解析 + CSSOM 构建 + 渲染树构建 + 布局 + 首次绘制
从这个链条能看出来,TTFB 是 FP 的前置成本。网络层越慢,浏览器越晚拿到 HTML,后面的解析和渲染也就越晚开始。
这就是为什么前端做性能优化时,不能只看包体积和代码执行时间。网络链路本身就是首屏体验的一部分。
数据包的旅程
互联网不是把一个完整文件一次性塞进网线里。
一个 HTML、JS、图片文件,都会被拆成一个个数据包,在网络中传输。到达对端后,再按规则重新组装。
为什么要拆包?
第一,单个文件可能很大。如果一次性占住整条链路,其他请求就只能等。
第二,拆成小包后,传输更灵活。某个包丢了,只需要重传这个包,不需要把整个文件重传。
第三,网络路径复杂,不同包可能经过不同节点。小包传输更适合互联网这种分布式网络。
这些数据最终会变成二进制帧,在物理介质上传输。前端看到的是一个请求,底层实际是很多包在网络里流动。
IP 层:尽力送到
IP,也就是 Internet Protocol,工作在网络层。
它的职责很单纯:根据 IP 地址,把数据包从源主机送到目标主机。
但 IP 提供的是“尽力而为”的服务。它不保证:
- 一定送达
- 一定按顺序送达
- 一定不重复
- 一定不出错
所以 IP 本身是不可靠的。
这对 Web 页面来说显然不够。HTML、CSS、JS 都需要完整、正确、有序地到达浏览器。一个关键字节出错,都可能导致资源解析失败。
可靠性要交给传输层处理。
UDP 和 TCP:两种传输方式
传输层运行在 IP 之上,负责把数据交付给目标主机上的具体应用。端口号就是用来定位应用的。
常见的两个传输层协议是 UDP 和 TCP。
UDP:只管快
UDP 不建立连接,直接发包。
它不保证顺序,不保证到达,也不会自动重传丢失的数据。
它的优势是头部开销小、速度快、延迟低。
适合场景包括:
- 音视频直播
- 视频通话
- 在线游戏
- 对实时性要求高、能容忍少量丢包的业务
对于这些场景,晚到的数据可能已经没有意义。与其等重传,不如继续处理新数据。
TCP:保证可靠
Web 页面资源更适合 TCP。
HTML、CSS、JS、图片这些资源通常要求完整、正确、有序。TCP 主要解决两个问题。
| 问题 | TCP 的解法 |
|---|---|
| 数据包传输过程中丢失 | 超时重传。发送端等待确认,超时未确认就重发 |
| 数据包到达顺序错乱 | 序号机制。每个包带序号,接收端按序重新组装 |
因为要保证可靠性,TCP 会比 UDP 更重。但 Web 页面需要的正是这种可靠传输。
前端页面慢,有时不是服务器慢,而是网络 RTT 高、丢包多、重传多,导致 TCP 层耗时增加。
三次握手:建立可靠连接
真正发送 HTTP 请求之前,TCP 需要先建立连接。
这个过程就是三次握手。它的核心目的,是同步双方初始序号,并确认双方都有发送和接收能力。
简化过程如下:
1客户端 -> 服务器:SYN,初始序号 J 2服务器 -> 客户端:SYN + ACK,确认 J+1,同时给出自己的初始序号 K 3客户端 -> 服务器:ACK,确认 K+1
换成更口语的说法:
1客户端:我想建立连接,我从 J 开始发,你听得到吗? 2服务器:听到了,你下一个从 J+1 发。我也从 K 开始发,你听得到吗? 3客户端:听到了,你下一个从 K+1 发。可以正式通信了。
为什么不是两次
两次握手无法让服务器确认客户端具备接收能力。
如果只有:
1客户端 -> 服务器:SYN 2服务器 -> 客户端:SYN + ACK
服务器并不知道客户端是否收到了自己的响应。连接状态就不够可靠。
为什么不是四次
四次当然也能完成确认,但没必要。
服务器在第二次握手里,把“确认客户端 SYN”和“发送自己的 SYN”合并成一条消息:
1SYN + ACK
这样刚好三次就能确认双方收发能力。
这就是三次握手最常见的解释:每一方的发送和接收能力都需要被确认,而服务器把两个动作合并了。
四次挥手:断开连接
数据传输结束后,TCP 连接需要释放资源。
断开过程通常是四次挥手。
1A -> B:FIN,序号 M 2B -> A:ACK,确认 M+1 3B -> A:FIN,序号 N 4A -> B:ACK,确认 N+1
可以理解为:
1A:我没有数据要发了,准备断开。 2B:知道了,但我可能还有数据没发完。 3B:我的数据也发完了,可以断开。 4A:收到,再见。
为什么挥手比握手多一次
因为 TCP 是全双工通信。
一端说“我发完了”,并不代表另一端也发完了。A 关闭发送方向后,B 可能还有数据要继续传。
所以 B 的 ACK 和 FIN 通常不能像握手时那样合并,必须分开处理。
这就是四次挥手的来源。
回到前端性能
理解 TCP 以后,很多前端性能优化手段就有了底层解释。
| 优化手段 | 背后的网络逻辑 |
|---|---|
| 减少请求数 | 每个连接都有建立成本,请求越碎,调度和连接成本越明显 |
| 使用 HTTP/2 多路复用 | 在单个 TCP 连接上并发传输多个请求和响应,减少重复握手 |
| 使用 CDN | 缩短用户到资源节点的物理距离,降低 RTT 和丢包概率 |
| preconnect | 提前完成 DNS、TCP、TLS,真正请求时直接复用连接 |
| 合理缓存静态资源 | 避免重复走网络链路 |
| 控制资源大小 | 减少传输时间、丢包重传和解析成本 |
早期经常强调雪碧图、文件合并,是因为 HTTP/1.1 下连接和请求成本明显。HTTP/2 之后,资源策略会更细,但减少关键链路上的网络等待仍然重要。
RTT 对 TTFB 的影响
RTT 是 Round Trip Time,也就是一次往返时延。
TCP 三次握手至少需要一次 RTT,TLS 握手还会继续增加耗时。用户离服务器越远,RTT 往往越高。
这也是 CDN 有用的原因。
如果用户在广州,请求却要绕到很远的源站,哪怕服务器处理很快,网络往返也会把 TTFB 拉高。
前端看到的是:
1页面白屏时间变长 2HTML 更晚返回 3首屏渲染更晚开始
所以 CDN、就近接入、预连接,本质上都是在减少等待。
丢包和重传
TCP 可靠,但可靠性不是免费的。
如果网络丢包,TCP 会重传。重传会增加请求完成时间。
弱网下,用户不只是下载速度慢,还可能因为丢包导致资源反复等待。一个关键 JS 包迟迟没下载完,页面交互就会被拖住。
这也是为什么移动端性能优化里,资源体积和关键资源数量都很重要。资源越大,传输过程越容易被网络质量放大影响。
preconnect 的实际意义
如果页面确定马上要请求某个域名,可以提前建立连接:
1<link rel="preconnect" href="https://cdn.example.com">
浏览器可以提前做:
- DNS 解析
- TCP 连接
- TLS 握手
等真正请求资源时,连接已经准备好,能减少等待。
但不要滥用 preconnect。每个连接都有成本,预连接太多会浪费资源。只给关键域名使用更合适。
一条链路串起性能
从网络角度看,一次页面加载大致可以串成:
1DNS 解析 2 -> TCP 三次握手 3 -> TLS 握手 4 -> HTTP 请求/响应 5 -> HTML 解析 6 -> CSSOM 构建 7 -> 渲染树构建 8 -> 布局 9 -> 首次绘制
其中 DNS、TCP、TLS、HTTP 都会进入 TTFB 或影响资源返回时间。TTFB 越晚,浏览器越晚开始后续渲染。
所以性能优化不是一个孤立动作。
减少 JS 体积、压缩图片、使用缓存、开启 CDN、优化接口、预连接关键域名,都在同一条链路上发挥作用。
TCP/IP 看起来是网络基础,但它和前端性能关系很直接。
IP 负责尽力送达,TCP 在 IP 之上提供可靠、有序传输。三次握手建立连接,四次挥手释放连接。RTT、丢包、重传、连接复用都会影响 TTFB,而 TTFB 又会影响 FP。
所以我现在看页面加载优化,不会只说“减少请求数、压缩资源”。我会顺着链路往前查:用户离资源有多远,DNS 是否慢,连接是否提前建立,资源是否命中缓存,关键请求是否被阻塞,弱网下有没有重传。把这些点串起来以后,CDN、preconnect、缓存、接口优化、资源压缩才不是零散动作,而是一条链路上的不同位置。