网络基础:从一次请求到 TCP、TLS 与 HTTP¶
1. 先建立一条完整的请求链¶
后端网络知识最容易学散:DNS、TCP、TLS、HTTP 各自记住一些定义,却没有连成一次真实请求的执行链。更有效的方式是先记住一次 HTTPS 请求实际穿过的链路,再把每一层的机制挂回这条链路。
假设客户端第一次访问 https://api.example.com/users/42,本机没有可复用连接。应用首先需要把域名解析成 IP;拿到目标 IP 后,操作系统根据目标地址选择路由和下一跳,并通过网络接口发送报文;客户端和服务器建立 TCP 连接;TCP 成功后执行 TLS 握手,验证服务器身份并协商会话密钥;随后 HTTP 请求作为 TLS 加密后的应用数据进入 TCP 字节流;服务器收到完整请求后交给应用处理,响应沿相反方向返回。
可以把这条主线压缩成:
后面的知识都围绕一个问题展开:这一层保存了什么状态,它如何知道下一步该做什么,出现丢包、重复、延迟或并发后又怎样恢复。
2. 分层的意义:每一层只解决一类问题¶
常见五层模型可以理解为应用层、传输层、网络层、数据链路层和物理层。基础学习不需要把协议机械分类,重点是知道一个应用请求经过每层后多了什么信息。
应用层定义业务双方如何解释消息。例如 HTTP 规定方法、状态码、字段和消息语义。传输层负责进程到进程的通信,TCP 使用端口区分主机上的不同进程,并提供可靠、有序的字节流。网络层负责主机到主机的寻址与路由,IP 地址主要在这一层发挥作用。数据链路层解决同一链路或局域网络上的帧传输,例如以太网会使用 MAC 地址。物理层负责把比特真正变成电信号、光信号或无线信号传输。
一次 HTTP 请求不会直接“发送到另一个进程”。应用把字节交给 socket,TCP 加入自己的首部,IP 再加入源 IP 和目标 IP,链路层继续封装成帧。接收端沿相反顺序拆解,最终将字节交给监听相应端口的 socket。分层让 HTTP 不需要关心以太网如何发送帧,TCP 也不需要理解 HTTP 的 JSON 内容。
3. IP、端口与连接:后端真正需要记住的寻址信息¶
IP 地址解决“到哪台主机或哪个网络接口”,端口解决“交给这台主机上的哪个应用”。TCP 连接通常由四元组标识:源 IP、源端口、目标 IP、目标端口。同一台客户端可以同时建立大量到 server:443 的连接,因为每条连接的源端口可以不同。
10.0.0.8:53120 → 203.0.113.10:443
10.0.0.8:53121 → 203.0.113.10:443
10.0.0.8:53122 → 203.0.113.10:443
源端口通常来自操作系统的临时端口范围。大量短连接会快速创建和关闭连接,使临时端口、socket 状态和内核连接跟踪资源承受压力。这也是后端服务大量 TIME_WAIT 时需要关心端口耗尽和连接复用的原因。
路由决定 IP 包下一步发往哪里。客户端通常不会知道完整的互联网路径,它只根据本机路由表选择下一跳。若目标在同一局域网,可以直接解析目标的链路层地址;若目标在其他网络,通常先把帧发送给网关,再由路由器逐跳转发 IP 包。分析“输入 URL 到请求发出”时,知道路由和下一跳的角色已经足够,没必要把所有路由协议都展开。
4. DNS:从名字得到可连接的地址¶
应用更习惯使用 api.example.com,网络传输最终需要 IP 地址,因此 DNS 负责域名到资源记录的解析。实际系统会经过多级缓存:浏览器或应用缓存、操作系统缓存、本地 DNS resolver 缓存以及递归解析器缓存。缓存命中时可以直接得到结果,只有未命中时才继续向权威体系查询。
客户端通常把一个递归查询交给本地或运营商/公共 DNS 递归解析器。递归解析器负责替客户端继续寻找结果。当它自己也没有缓存时,会从根 DNS 开始获得下一步应该询问哪个服务器的信息,再查询顶级域服务器,最后到目标域名的权威 DNS。根服务器通常不会直接返回 api.example.com 的最终 IP,它会告诉解析器负责相应顶级域的服务器;顶级域服务器继续给出该域的权威服务器;权威服务器最终返回 A、AAAA 或其他相关记录。
因此“递归”和“迭代”可以按责任区分。客户端对递归解析器的典型请求要求对方给出最终结果,递归解析器随后向根、TLD、权威服务器逐步询问,每一步根据上一步的 referral 找到下一站。
DNS 传统查询常用 UDP 53,因为单次请求和响应开销小,不需要先建立传输层连接。DNS 同样支持 TCP;响应过大、区域传送以及现代规范要求的某些场景会使用 TCP。直接回答“DNS 永远使用 UDP”会过于绝对。
TTL 决定记录可以缓存多长时间。TTL 很长时解析压力低、变更传播慢;TTL 很短时变更更快生效,但递归查询和权威 DNS 压力会上升。后端做流量切换时,这种传播延迟经常比代码本身更值得关注。
5. TCP 的核心抽象:可靠、有序的字节流¶
TCP 向应用提供的是字节流。应用调用两次 send(),接收端可能一次 read() 得到两次发送的数据,也可能第一次只读到其中一部分。TCP 关注字节顺序和可靠交付,不保存应用调用 send() 时的消息边界。
例如发送端执行:
接收端可能看到:
也可能看到:
因此应用协议必须自己定义消息边界。常见方案包括固定长度、分隔符、长度字段。RPC 协议经常在消息头里记录 body 长度,接收端先读取固定长度 header,再根据其中的长度继续读取完整 body。所谓“TCP 粘包/拆包”主要反映了字节流与应用消息边界之间的差异。
6. 三次握手:双方确认序号空间和通信能力¶
TCP 需要在发送业务数据之前建立连接状态。连接双方各自维护发送序号、接收确认、窗口等状态。三次握手的核心作用是让双方确认对方可达,并同步双方初始序号。
假设客户端初始序号为 x,服务器初始序号为 y。典型过程为:
Client Server
CLOSED LISTEN
SYN, seq=x -------------------------------->
SYN-SENT SYN-RECEIVED
<---------------- SYN+ACK, seq=y, ack=x+1
ACK, ack=y+1 ------------------------------->
ESTABLISHED ESTABLISHED
第一次握手让服务器知道客户端的 SYN 已经到达,并获得客户端初始序号。第二次握手同时确认客户端的 SYN,并发送服务器自己的 SYN。第三次握手确认服务器的 SYN 已经到达客户端。完成后双方都知道自己的发送能力和对方的接收路径已经经过双向验证。
“两次为什么不够”可以从服务器状态考虑。如果服务器只收到一个历史网络中滞留的旧 SYN,并在第二步后就直接认为连接成立,它可能为一个客户端当前并不想建立的连接分配状态。第三次 ACK 让服务器获得客户端对当前这次握手的最终确认,也完成服务器初始序号的确认闭环。
SYN 会占用一个序号,因此确认号常见为 x+1。序号的存在还为后续可靠传输提供了统一的字节位置空间。
7. 四次挥手:两个方向分别关闭¶
TCP 连接是全双工的,A 到 B 和 B 到 A 两个方向可以独立停止发送,因此典型关闭过程有四个报文。假设客户端主动关闭:
Client Server
ESTABLISHED ESTABLISHED
FIN ------------------------------------------>
FIN-WAIT-1 CLOSE-WAIT
<----------------------------------- ACK
FIN-WAIT-2
<----------------------------------- FIN
LAST-ACK
ACK ------------------------------------------>
TIME-WAIT CLOSED
等待 2MSL
CLOSED
主动关闭方发送 FIN 后表示自己没有更多数据发送,但仍然可以接收对方剩余数据。服务器收到 FIN 后先 ACK,此时应用可能还需要处理或发送数据,所以服务器的 FIN 可以稍后出现。ACK 和 FIN 在某些情况下可以合并,协议状态仍然体现两个方向分别关闭的事实。
CLOSE_WAIT 经常暴露服务端代码问题。内核已经收到对端 FIN,并把连接置于 CLOSE_WAIT,应用如果迟迟没有调用 close(),连接会长期停留在这一状态。大量 CLOSE_WAIT 通常优先检查应用是否正确关闭 socket。
8. TIME_WAIT:保护连接关闭和旧报文边界¶
主动关闭方发送最后一个 ACK 后进入 TIME_WAIT,常见持续时间是 2MSL。MSL 表示一个 TCP 报文段在网络中允许存活的最大时间上界概念。
TIME_WAIT 有两个重要作用。第一,最后一个 ACK 可能丢失。如果服务器没有收到最终 ACK,它会重传 FIN;主动关闭方仍保留连接状态时可以再次回复 ACK,使对端正常结束。第二,等待足够时间让旧连接中可能滞留的报文从网络中消失,降低相同四元组被快速复用后旧报文干扰新连接的风险。
高 QPS 服务如果每个 HTTP 请求都新建 TCP 连接,主动关闭端会产生大量 TIME_WAIT。单纯看到 TIME_WAIT 数量大并不能直接判断故障,需要继续看连接创建速率、临时端口范围、文件描述符、请求是否启用 keep-alive、是否存在连接池以及是否真的出现端口耗尽或 connect failure。
9. TCP 如何保证可靠和有序¶
TCP 把应用字节编号。接收方依据序号判断数据应该放在字节流的什么位置,并通过 ACK 告知发送方已经连续收到的位置。重复数据可以根据序号识别,乱序数据可以先缓存,待缺失部分到达后再按顺序交给应用。
假设发送方发送三个连续区间:
如果第二段丢失,第三段先到达,接收方能够知道 1001..2000 仍然存在缺口。发送端最终需要重传缺失部分。TCP 可以通过重传定时器发现长时间未确认的数据,也可以根据重复 ACK 等信号更快推断中间段丢失。现代 TCP 还可以借助 SACK 告诉发送方已经收到哪些非连续区间,从而更准确地重传缺失数据。
RTO(Retransmission Timeout,重传超时)需要根据实际 RTT 动态估计。设置过小会在网络只是暂时延迟时频繁误重传,设置过大会让真正丢包后的恢复变慢。因此 TCP 会依据往返时间样本平滑调整超时估计。
可靠性的代价是状态和等待。TCP 需要保存未确认数据、接收乱序数据、处理重传,并在网络质量下降时降低发送速度。这些成本解释了为什么“TCP 可靠”并不等于“任何时候都低延迟”。
10. 流量控制:保护接收端¶
发送方可能远快于接收方。如果持续发送,接收端 socket 缓冲区会被填满。TCP 使用接收窗口 rwnd 告诉对方当前还能接收多少数据,这就是流量控制。
例如接收端最初还有 64 KB 缓冲空间,它在 ACK 中通告 rwnd=64KB。应用处理速度下降后缓冲区逐渐被占用,接收端可能把窗口降到 16 KB。发送方据此减少未确认数据量。如果窗口降到 0,发送方会暂停正常数据发送,并通过窗口探测机制等待接收端再次释放空间。
流量控制关注的是接收端能力。接收端 CPU 忙、应用迟迟不读 socket 或缓冲区过小,都可能导致 rwnd 下降。
11. 拥塞控制:保护网络路径¶
即使接收端有足够缓冲,网络中间链路也可能承载不了发送速度。路由器队列增长后会增加延迟,继续超载会产生丢包。TCP 因此还维护拥塞窗口 cwnd,根据网络反馈动态限制在途数据量。
发送方实际允许的未确认数据量通常受两者共同约束:
rwnd 来源于接收端,cwnd 来源于发送端对网络容量的估计。两者解决的问题不同。
经典拥塞控制会经历慢启动和拥塞避免。连接刚建立时发送方并不知道路径容量,因此从较小窗口开始,并随 ACK 到达较快增长;达到一定阈值后增长速度放缓。检测到拥塞信号后,发送方降低 cwnd,再逐渐探测可用带宽。具体算法可以是 CUBIC、BBR 等,基础学习更应关注为什么需要 cwnd、慢启动如何逐步探测和丢包后为什么降低发送速率。
这也解释了新连接与长连接的性能差异。频繁创建 TCP 连接会重复支付握手成本,还会不断重新经历初始拥塞窗口和路径探测。对大量短 RPC 来说,连接池的价值既包含减少握手,也包含复用已经建立好的传输状态。
12. TCP 与 UDP:选择来自语义需求¶
UDP 提供数据报语义,每次发送有明确边界,协议本身不建立连接,也没有 TCP 那套可靠重传、按序交付和拥塞控制状态。它的协议开销较小,应用可以自行决定可靠性、顺序、重传和时延策略。
实时音视频常允许丢少量旧数据,因为等待重传后的旧帧可能已经失去播放价值;DNS 的传统短查询也适合低开销的数据报。文件传输、数据库连接和传统 HTTP 需要可靠有序数据,因此长期大量使用 TCP。QUIC 则在 UDP 之上实现自己的可靠传输、拥塞控制和多路复用,说明“底层使用 UDP”并不代表应用最终得到的是不可靠语义。
13. HTTP/1.1:请求响应语义与连接复用¶
HTTP 定义资源交互语义。请求通常包含方法、目标、字段和可选 body,响应包含状态码、字段和可选 body。例如:
常见状态码可以按语义理解。2xx 表示请求成功处理;3xx 主要涉及重定向和缓存条件;4xx 表示请求侧条件导致服务器无法按预期处理;5xx 表示服务器处理链路出现问题。常见的 200、201、204、301/302/304、400、401、403、404、409、429、500、502、503、504,重点是知道它们在代理、鉴权、限流和上游失败中的含义。
HTTP/1.1 默认支持持久连接。一次 TCP 连接可以连续承载多个 HTTP 请求,服务端通常依据消息长度字段、chunked framing 等方式确定一条消息的边界。连接复用减少三次握手和 TLS 握手次数,也减少短连接产生的 TIME_WAIT 和临时端口压力。
HTTP 方法还有幂等语义。幂等表示同一个请求执行一次或多次,对资源产生的预期效果相同。GET、PUT、DELETE 在规范语义上具有幂等性,POST 通常没有这一保证。这个概念对客户端重试非常重要:网络超时时,客户端可能不知道服务器究竟有没有完成操作,幂等设计可以降低重复重试产生副作用的风险。
14. HTTP/1.1 的并发限制与 HTTP/2¶
HTTP/1.1 可以复用 TCP 连接,但一个连接上的请求和响应并发能力有限。实践中客户端经常建立多个 TCP 连接提高并发。HTTP/2 引入二进制帧和 stream,一条 TCP 连接可以同时承载多个独立的请求/响应交换,各 stream 的 frame 可以交错发送,因此大量并发请求不需要各占一条 TCP 连接。
一条 TCP 连接:
Stream 1: HEADERS --- DATA -------- DATA
Stream 3: HEADERS -- DATA
Stream 5: HEADERS ----- DATA
HTTP/2 还使用 HPACK 压缩重复的 header 字段,减少大量相似请求中 Host、Cookie、User-Agent 等头部重复占用带宽。RFC 9113 明确把字段压缩和同连接多路并发列为 HTTP/2 降低延迟、提高网络利用率的核心能力。
HTTP/2 仍运行在单条 TCP 字节流之上。如果底层 TCP 丢失某个报文段,TCP 必须先恢复缺失字节,后续字节才能按序交给 HTTP/2。此时多个 stream 都可能被一起拖住,这属于 TCP 层的队头阻塞。HTTP/2 解决了 HTTP/1.x 应用层并发问题,但无法消除底层 TCP 的有序字节流约束。
15. HTTP/3 与 QUIC:理解方向即可¶
HTTP/3 使用 QUIC 作为传输。QUIC 通常运行在 UDP 之上,但自身实现可靠传输、拥塞控制、加密和多个独立 stream。一个 stream 的丢包恢复不会强制其他 stream 等待同一条 TCP 字节流缺口,因此可以降低 HTTP/2 遇到的跨 stream TCP 队头阻塞。
基础学习通常不要求详细背 QUIC 包格式。能够解释 HTTP/2 多路复用仍受 TCP 丢包影响,以及 HTTP/3 为什么选择 QUIC,已经覆盖主要理解目标。
16. HTTPS 与 TLS 1.3:认证、密钥协商和数据加密¶
HTTPS 可以理解为 HTTP 运行在 TLS 提供的安全通道之上。TLS 目标包括服务器身份认证、数据机密性和完整性。TLS 1.3 的当前标准在 2026 年由 RFC 9846 更新,它保持 TLS 1.3 版本并对原 RFC 8446 做了澄清和安全要求更新。
一次典型 TLS 1.3 首次握手可以抽象为:客户端先发送 ClientHello,其中带有支持的 TLS 参数、随机信息和用于密钥协商的 key share;服务器选择参数,返回 ServerHello 和自己的 key share。双方利用临时密钥交换计算共享秘密,并从握手 transcript 派生后续密钥。
服务器随后提供证书链,并对握手 transcript 做数字签名来证明自己持有证书对应的私钥。客户端验证证书链是否能追溯到受信任 CA、证书是否在有效期内、证书标识是否匹配访问的主机名,并验证服务器的签名。通过这些检查后,客户端获得“当前握手对端确实控制了目标域名证书私钥”的证据。
大量应用数据使用对称加密保护。对称算法适合高速处理大规模数据;非对称密码学更适合身份认证、签名和建立共享秘密。TLS 1.3 中密钥交换和认证的细节与旧版 RSA key exchange 有明显差异,描述现代 HTTPS,优先按 TLS 1.3 的临时 Diffie-Hellman 类密钥交换和证书签名理解。
TLS 1.3 的普通完整握手通常需要在 TCP 建连后再进行一个往返左右才能获得应用数据密钥。会话恢复可以减少后续连接的握手成本。HTTP 长连接或连接池则能进一步避免每个请求都重复执行 TCP 和 TLS 建连过程。
17. HTTPS 如何降低中间人攻击风险¶
中间人可以截获网络流量,也可以尝试自己和客户端建立 TLS。如果攻击者只能提供一张自己生成的 api.example.com 证书,客户端会发现其签发链无法连接到受信任 CA,或者主机名校验失败。攻击者如果转发真实服务器证书,却没有对应私钥,也无法完成服务器对当前握手 transcript 的有效签名。
因此证书验证链、主机名校验和私钥持有证明共同建立服务器身份。客户端设备如果主动安装了攻击者控制的根 CA,或者证书验证被应用关闭,信任边界会发生变化,这类情况下中间人代理可以获得被客户端接受的证书链。
18. 从 URL 到服务器收到请求:完整推演¶
现在把前面的机制重新串起来。用户访问:
应用先解析 URL,得到 scheme 为 HTTPS、主机名 service.example.com、默认端口 443 和请求路径 /users/42。随后查询 DNS;本地缓存未命中时,把递归查询发送给配置的 DNS resolver,resolver 逐级获得权威答案并返回目标 IP。
操作系统查看路由表决定从哪个接口和下一跳发送数据。客户端创建 socket,选择临时源端口,并向目标 IP:443 发起 TCP 三次握手。连接建立后进行 TLS 1.3 握手,服务器发送证书和握手认证信息,客户端验证身份,双方得到应用流量密钥。
HTTP 请求随后被 TLS record 加密并交给 TCP。TCP 根据 MSS、窗口、拥塞状态等将字节流拆成适合发送的报文段,IP 再负责跨网络路由。服务器内核收到报文,TCP 根据序号重组字节并处理 ACK、重传和窗口,然后 TLS 解密,HTTP 层解析请求,最后由 Web server 或后端框架把请求交给应用 handler。
应用完成数据库、缓存或其他 RPC 后生成 HTTP 响应,整个过程反向返回。如果连接支持 keep-alive,双方可以保留 TCP/TLS 状态供后续请求复用。
19. 连接池为什么对后端延迟很重要¶
假设服务 A 每秒调用服务 B 15000 次,每次业务处理只有 8 ms。如果 A 每个请求都重新建立 TCP 和 TLS 连接,那么单请求延迟还要增加 DNS(若无缓存)、TCP 握手、TLS 握手以及新 TCP 连接初始拥塞窗口的成本。高并发下又会产生大量 socket 创建销毁、TIME_WAIT 和临时端口占用。
连接池把已经完成握手的连接保留下来。请求到来时优先借用现有连接,完成后归还。这样大多数请求可以直接进入 HTTP/RPC 数据传输阶段。连接池同时需要设置最大连接数、空闲超时、最大生命周期和等待队列。连接数过小会让请求在池里排队,连接数过大又可能把下游压垮,并增加文件描述符和内存压力。
因此看到“应用处理只有 10 ms,客户端 p99 却 200 ms”,不能只看服务端 handler。应把延迟拆成排队、DNS、connect、TLS、请求写入、等待首字节、响应读取等阶段,再判断时间到底消耗在哪一段。
20. listen、accept 与连接队列¶
服务端调用 listen() 后,内核开始维护 TCP 连接建立相关状态。工程上常把“半连接队列”和“全连接队列”作为概念讨论:收到 SYN 并回复 SYN+ACK 后,尚未完成最终 ACK 的连接处在握手中;三次握手完成后,连接等待应用 accept() 取走。如果应用 accept 太慢,或者队列容量不足,高并发建连时可能出现连接建立失败或重传。
这与业务线程是否忙存在联系,但两个阶段需要分开看。连接已经完成 TCP 握手却长时间没有被应用 accept,说明应用取连接的速度可能跟不上;大量 SYN 处于未完成状态,还要考虑网络丢包、SYN flood、backlog 和内核参数。实际排障应结合 ss、内核统计和抓包确认所处阶段。
21. 常见超时要按阶段区分¶
connect timeout 表示建立连接阶段没有在限定时间内完成,常见方向包括目标不可达、SYN/SYN+ACK 丢失、服务端握手队列压力、路由或防火墙问题。它与“HTTP handler 执行超时”属于不同阶段。
read timeout 通常表示连接已经存在,客户端等待响应数据过久。可能是服务端业务执行慢、下游依赖慢、服务端排队,也可能是网络传输问题。TLS handshake timeout 则把范围进一步收窄到 TCP 建连完成后的 TLS 协商阶段。
排障时把一个总 p99 拆成这些阶段,比直接说“网络慢”更有效。可以使用 curl 的阶段耗时、连接池指标、DNS 查询工具、ss socket 状态、抓包和服务端 trace 交叉验证。
22. 重传升高说明什么¶
TCP retransmission 升高说明发送方没有按预期获得确认,因此进行了重传。可能原因包括真实丢包、链路拥塞、无线质量差、路由异常,也可能是延迟突然增大导致超时判断。重传通常会同时影响吞吐和尾延迟,因为缺失字节需要恢复后才能继续按序交付。
如果业务 p99 高,但 retransmission 基本正常,同时每次请求前都重新进行 TCP 握手,并且客户端积累大量 TIME_WAIT,那么优先调查短连接和连接池更合理。如果 retransmission 明显升高,再把注意力转向网络路径和拥塞证据。这种根据证据缩小范围的方式比看到任何延迟都先调 TCP 参数更可靠。
23. HTTP 502、503、504 的后端语义¶
这些状态码经常出现在反向代理或网关链路。502 Bad Gateway 表示代理从上游得到无效响应或无法正常完成上游响应处理;503 Service Unavailable 常用于服务当前无法承载请求,例如过载、维护或没有可用 upstream;504 Gateway Timeout 表示网关等待上游响应超时。
实际系统会对状态码有自己的映射策略,因此排障时需要结合代理日志。例如客户端看到 504,真正慢的可能是网关到业务服务、业务服务到数据库,或者业务线程排队。HTTP 状态码提供症状位置,完整原因仍需要 trace 和各阶段耗时确认。
参考资料¶
- RFC 9293, Transmission Control Protocol (TCP): https://www.rfc-editor.org/rfc/rfc9293.html
- RFC 9846, The Transport Layer Security (TLS) Protocol Version 1.3: https://www.rfc-editor.org/rfc/rfc9846.html
- RFC 9110, HTTP Semantics: https://www.rfc-editor.org/rfc/rfc9110.html
- RFC 9113, HTTP/2: https://www.rfc-editor.org/rfc/rfc9113.html
- RFC 9114, HTTP/3: https://www.rfc-editor.org/rfc/rfc9114.html
- RFC 1034 / RFC 1035, Domain Names: https://www.rfc-editor.org/rfc/rfc1034.html , https://www.rfc-editor.org/rfc/rfc1035.html