--- id: cs.network-os.http-tcp title: 网络基础:从输入 URL 到页面展示、TCP 握手挥手与粘包、HTTP/1.1 / 2 / 3、HTTPS、长连接与 SSE type: lesson domain: cs stack: [network-os] tags: [TCP, 三次握手, 四次挥手, TIME_WAIT, 粘包, HTTP/2, HTTP/3, HTTPS, TLS, DNS, keep-alive, SSE, WebSocket] level: basic prerequisites: [] est_hours: 3 related: [cs.network-os.os-basics, cs.network-os.scenario.connection-troubleshooting, devops.nginx.https-stream-limit] week: [14, 16] order: 1 --- # 网络基础:从输入 URL 到页面展示、TCP 握手挥手与粘包、HTTP/1.1 / 2 / 3、HTTPS、长连接与 SSE > BOSS直聘 2026-10-23 的调研中有岗位提到计算机基础(网络 / 操作系统)。前端出身的你对 HTTP 和浏览器很熟,这里补齐 TCP 和服务端视角。 ## 解释说明 ### 从输入 URL 到页面展示(经典题,按层回答) 1. **DNS 解析**:浏览器缓存 → 系统缓存 / hosts → 递归解析器 → 根 / 顶级域 / 权威服务器,得到 IP。 2. **建立连接**:TCP 三次握手;HTTPS 再进行 TLS 握手(TLS 1.3 为 1 个往返);HTTP/3 基于 QUIC(UDP),连接和加密握手合并。 3. **发送请求**,经过 CDN / 负载均衡 / 网关到达应用服务器。 4. **服务器处理并响应**。 5. **浏览器渲染**:解析 HTML 构建 DOM、CSS 构建 CSSOM、合成渲染树、布局、绘制、合成;遇到同步脚本会阻塞解析。 ### TCP - **三次握手**:SYN → SYN+ACK → ACK。为什么是三次:双方都要确认“对方能收到我发的”,并同步初始序号;两次无法防止过期的连接请求被误建立。 - **四次挥手**:FIN → ACK → FIN → ACK(因为一方关闭发送后,另一方可能还有数据要发)。 - **TIME_WAIT**:主动关闭方等待 2MSL,确保最后的 ACK 能被重传、旧连接的报文消失。短连接高并发时,主动关闭方(常常是作为客户端的服务)会积累大量 TIME_WAIT —— 解决办法是**连接复用**(连接池、keep-alive),而不是盲目修改内核参数。 - **可靠传输**:序号与确认、超时重传、滑动窗口(流量控制)、拥塞控制(慢启动、拥塞避免)。 - **字节流,没有消息边界**:“粘包 / 拆包”是应用层没有定义边界导致的,要用分隔符或长度前缀解决(示例中实际演示)。 ### HTTP 演进 | 版本 | 关键点 | | --- | --- | | HTTP/1.1 | 持久连接(keep-alive);一个连接同一时间只能处理一个请求(队头阻塞),浏览器对同域开多个连接 | | HTTP/2 | 二进制分帧、多路复用(一个连接并发多个流)、头部压缩;TCP 层的丢包仍会阻塞所有流 | | HTTP/3 | 基于 QUIC(UDP),解决 TCP 层队头阻塞,连接迁移(切换网络不断开) | ### HTTPS TLS 用非对称加密(或密钥交换)协商出会话密钥,再用对称加密传输数据;证书由 CA 签名,客户端校验证书链和域名,防止中间人。 ### 长连接推送:轮询、SSE、WebSocket | 方式 | 方向 | 适合 | | --- | --- | --- | | 轮询 | 客户端拉 | 简单、低频 | | **SSE** | 服务器 → 客户端,基于普通 HTTP,文本事件,浏览器自动重连 | **大模型流式输出**(OpenAI 等接口都用它) | | WebSocket | 双向,独立协议 | 聊天、协同编辑、实时语音 | ## 案例 AI 对话接口用 SSE 流式返回:经过 Nginx 时要关闭缓冲;HTTP/1.1 下浏览器对同一域名的并发连接数有限(通常 6 个),用户开多个标签页时 SSE 连接可能占满,HTTP/2 下多路复用没有这个问题。 ## 代码示例 [examples/tcp-basics](examples/tcp-basics/)(只用标准库): ```text $ python framing.py 发送方 send 了 3 次(每次 4 字节),接收方一次 recv 收到 12 字节 → 三条消息粘在一起,边界丢失 长度前缀分帧:每次只读 3 字节,仍然还原出 3 条完整消息:['第0条', '第1条', '第2条'] ``` 增量解码器(`test_framing.py` 用 100 种随机切分验证): ```python def feed(self, data: bytes) -> list[dict]: self.buf += data out = [] while len(self.buf) >= HEADER.size: (n,) = HEADER.unpack_from(self.buf) if n > self.max_frame: raise ValueError("帧长度超过上限") if len(self.buf) < HEADER.size + n: break out.append(json.loads(bytes(self.buf[HEADER.size:HEADER.size + n]))) del self.buf[:HEADER.size + n] return out ``` ## 易错点 - 认为一次 `send` 对应一次 `recv`。 - 为了解决 TIME_WAIT 乱改内核参数,而不是复用连接。 - 把 HTTP/2 的多路复用和 HTTP/1.1 的管线化混为一谈。 - 不设置超时(连接超时、读超时),上游挂起时线程 / 协程被耗尽。 ## 面试常问 - 从输入 URL 到页面展示发生了什么? - 为什么 TCP 握手是三次、挥手是四次?TIME_WAIT 的作用? - HTTP/1.1、2、3 的区别? - SSE 和 WebSocket 的区别?大模型流式输出为什么常用 SSE?