适用读者:会写 CRUD,但被问"这个请求到底怎么走到对面"时答不上来的人。本系列第一篇。
前置:S01(能用curl、ss、ip等命令)。
实测环境:Ubuntu 24.04.1 / Linux 6.8(内核)/ curl 8.x / iproute2 / openssl 3.x;实测日期 2026-11-05。
覆盖旧文:A-1860(操作系统核心知识点,692 点)、A-1861(计算机网络核心知识点,742 点)= 1,434 点。
覆盖扩展:E-S02-001(虚拟内存与页表)、E-S02-010(HTTP 版本演进,只点名)。
下一篇:S02-B02 · 进程与线程。
1. 一句话结论
发一个请求,本质是"用户态的字节"被一层层套上头部(封装),交给网卡变成电信号;回包再一层层剥掉头部(解封装)。操作系统负责"进程与内存",网络栈负责"投递"。两者在 socket 这个接口上交汇。
你的进程 内核 网卡/线路
┌──────────┐ write() ┌─────────────┐ ┌──────┐ ┌──────┐ ┌────────┐
│ 应用数据 │ ─────────► │ socket 缓冲区 │→ │ TCP │→ │ IP │→ │ 以太网 │→ bits
│ "GET /" │ 系统调用 │ │ │ 段 │ │ 包 │ │ 帧 │
└──────────┘ └─────────────┘ └──────┘ └──────┘ └────────┘
▲ │
└──── read() ←───────────┘ ← 回包按相反方向逐层解封装
记住这个"两层世界":应用层看到的永远是"一次 write 写了 N 字节",而网络上实际发了几个包、怎么分片,应用层并不知道(这正是 S02-B05 粘包的根源)。
2. 用三条命令看同一次请求(实测)
以本博客域名做一次真实请求:
2.1 DNS:先要知道 IP
$ getent hosts clxhxhhr.top
2606:4700:3034::ac43:ab35 clxhxhhr.top
2606:4700:3036::6815:1d03 clxhxhhr.top
拿到的是 IPv6 地址(Cloudflare 边缘节点)。curl 实际连接时会根据本机网络能力选 A/AAAA 记录。
2.2 路由:包从哪张网卡出去
$ ip route
default via 10.0.23.1 dev ens17 proto static
10.0.23.0/24 dev ens17 proto kernel scope link src 10.0.23.56
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
读法:目的地址不在本地网段 → 走 default 网关 10.0.23.1,从 ens17 出去,源地址是 10.0.23.56。
2.3 HTTP 请求:把连接、加密、协议一次看全
$ curl -sv -o /dev/null https://clxhxhhr.top/
实测输出(截取):
* Connected to clxhxhhr.top (104.21.29.3) port 443
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* Server certificate:
* using HTTP/2
> GET / HTTP/2
> Host: clxhxhhr.top
三条信息量很大的结论:
| 观察 | 含义 |
|---|---|
Connected to ... port 443 |
TCP 连接建立成功(三次握手已完成,见 B03) |
TLSv1.3 ... Client hello / Server hello / Certificate |
TLS 握手在 TCP 之上又走了一轮(B06 详讲) |
using HTTP/2 |
应用层协议是 HTTP/2,不是 HTTP/1.1 |
同一台机器、同一个域名,四层协议栈全在现场:IP(路由)→ TCP(连接)→ TLS(加密)→ HTTP/2(应用)。
3. 端口、socket 与四元组
| 概念 | 定义 | 常见误解 |
|---|---|---|
| 端口 | 16 位数字,标识"主机上的哪个服务" | 以为端口是进程的属性——端口只是绑定关系 |
| socket | 内核里的一条连接,由四元组唯一确定 | 以为 socket 就是"一个 IP:端口" |
| 四元组 | (源IP, 源端口, 目的IP, 目的端口) |
忽略源端口,导致"为什么同一服务能接一万个连接"答不上来 |
ss 命令就是用来观察这些的(本机实测):
$ ss -s
Total: 239
TCP: 42 (estab 7, closed 8, orphaned 1, timewait 4)
$ ss -ti state established | head -2
Recv-Q Send-Q Local Address:Port Peer Address:Port
0 0 127.0.0.1:46450 127.0.0.1:mysql
cubic wscale:7,7 rto:204 rtt:3.145/0.924 ... cwnd:10 ... bytes_sent:245816
第二段那些参数(cwnd、rtt、retrans)是内核在告诉你"这条连接的真实状态"——B04 与 J01 会逐项拆。
4. 一次请求的 12 步(从键盘到回包)
① 浏览器/DNS 解析域名 → IP 【应用 + DNS】
② 内核选源地址与出口网卡(查路由表) 【IP 层】
③ 若目的在本地网段 → ARP 查 MAC;否则发给网关 【链路层】
④ 应用调 connect() → 内核发起 SYN 【TCP】
⑤ 三次握手完成,socket 进入 ESTABLISHED 【TCP】
⑥ 应用 write() → 数据进 socket 发送缓冲区 【系统调用】
⑦ TCP 分段(受 MSS 限制)、加序号,交给 IP 【传输层】
⑧ IP 加源/目的地址,查路由,交给链路层 【网络层】
⑨ 链路层加帧头(源/目的 MAC),网卡 DMA 发出 【链路层】
⑩ 对端逐层解封装:帧 → 包 → 段 → 字节流 【内核】
⑪ 对端应用 read() 拿到字节,处理并回包 【应用】
⑫ 回包按 ⑨→⑦ 反向走一遍,本机 read() 拿到响应 【全链路】
为什么这 12 步值得背:排障时"卡在哪一步"决定了你该看哪个命令。
| 症状 | 大概率卡在 | 首查命令 |
|---|---|---|
Could not resolve host |
① | getent hosts / dig |
No route to host |
②③ | ip route |
Connection timed out |
④⑤ | ss -tan、对端防火墙 |
| 连上了但无响应 | ⑥⑪ | 应用日志、ss -ti 的 unacked |
| 数据错乱/粘包 | ⑦⑪ | 抓包或应用层解码(B05) |
5. 操作系统的四个核心概念(旧文 A-1860 的重组)
旧文 A-1860 把 OS 讲了 1,000 多行(中断、内核态、线程、进程、调度、内存、页表……)。这里只留与"发数据"直接相关的四个,其余进 §9 概念索引。
5.1 用户态与内核态
| 态 | 能做什么 | 代价 |
|---|---|---|
| 用户态 | 跑你的代码,不能直接碰硬件/网卡 | 快 |
| 内核态 | 能碰硬件、能调度、能管内存 | 切换有成本(保存/恢复上下文) |
read/write/connect 这些系统调用就是"过门"的动作:用户态 → 内核态 → 回到用户态。所以"每次网络收发都有一次态切换",这也是零拷贝、sendfile、io_uring 等技术存在的理由。
5.2 进程与地址空间
- 每个进程有独立的虚拟地址空间(页表映射到物理内存)。
- 进程间通信必须经过内核(管道、socket、共享内存)。
- 线程共享同一地址空间 → 通信成本低,但共享可变状态就是数据竞争的来源(S05 详讲)。
5.3 虚拟内存与页表(E-S02-001)
| 概念 | 一句话 |
|---|---|
| 虚拟地址 | 进程看到的地址,不是物理地址 |
| 页表 | 虚拟页 → 物理页的映射表 |
| 缺页中断 | 访问的页不在内存 → 内核去磁盘/交换区取 |
| 快表(TLB) | 页表的缓存,命中就不用查多级页表 |
与网络的联系:socket 缓冲区占用的是内核内存(不受进程堆限制),所以"应用内存没满但系统 OOM"是可能的。
5.4 中断与软中断
网卡收到包 → 触发硬中断 → 内核把处理"延后"到软中断(NAPI/softirq)→ 协议栈处理 → 唤醒等待的进程。
这解释了一个常见现象:高流量下 %soft(软中断 CPU 占比)飙高、ksoftirqd 吃满一个核——这不是你的代码慢,是收包路径饱和(J01 会给排查方法)。
6. 网络分层:OSI 七层是教学模型,TCP/IP 四层才是实现
| OSI(7 层) | TCP/IP(4 层) | 实际对应 | 典型协议 |
|---|---|---|---|
| 应用 / 表示 / 会话 | 应用层 | 你的 HTTP 客户端、TLS 库 | HTTP、DNS、SSH、WebSocket |
| 传输层 | 传输层 | 内核 socket | TCP、UDP、QUIC |
| 网络层 | 网络层 | 内核 IP 栈 | IP、ICMP |
| 数据链路 / 物理 | 链路层 | 网卡驱动、交换机 | Ethernet、ARP、Wi-Fi |
面试常考的"七层"要会说,但排障要看四层:说"表示层有问题"没人能修,说"TLS 握手失败"才有人能修。
6.1 封装:每层加什么头
| 层 | 加的头 | 里面有什么 |
|---|---|---|
| 传输层(TCP) | TCP 首部 20 字节起 | 源/目的端口、序号、确认号、窗口、标志位 |
| 网络层(IP) | IP 首部 20 字节起 | 源/目的 IP、TTL、协议号(6=TCP,17=UDP) |
| 链路层(以太) | 帧头 14 字节 | 源/目的 MAC、类型 |
| 物理层 | 前导码等 | 比特流 |
"MTU 1500"的由来:以太网帧最大 1518 字节 − 18 字节帧头尾 = 1500 字节 IP 包;再减 20 IP + 20 TCP = MSS 1460。这就是为什么"一次 send 100KB 会变成几十个包"。
7. 五个高频误区
| 误区 | 真相 |
|---|---|
| "TCP 保证消息边界" | ❌ TCP 只保证字节流有序可靠,边界要应用层自己划(B05) |
| "端口属于进程" | ❌ 端口是绑定关系;同一端口可以有多个连接(四元组不同) |
| "SYN 就是连接建立" | ❌ 收到 SYN 只说明对端可达;半连接队列满时仍会失败(B03) |
| "网卡收到的包,进程立刻能读到" | ❌ 要经过软中断与协议栈;高负载时延迟来自这里 |
| "带宽大就不卡" | ❌ 延迟(RTT)与丢包才是交互体验的决定因素(B04、J01) |
8. 自检
| # | 检查 | 命令 | 预期 |
|---|---|---|---|
| 1 | 能解析域名 | getent hosts <域名> |
打印 IP |
| 2 | 能看出口网卡 | ip route | head -1 |
default via ... dev ... |
| 3 | 能看到四层协议 | curl -sv https://<域名>/ -o /dev/null 2>&1 | grep -E "Connected|TLS|HTTP/" |
连接 + TLS + 协议版本 |
| 4 | 能读 socket 统计 | ss -s |
estab / timewait 计数 |
| 5 | 能读 TCP 细节 | ss -ti state established |
出现 cwnd、rtt |
9. 附录 · 概念索引(旧文 A-1860 / A-1861 的其余知识点)
这一节是"索引 + 一句话定义",用来保证旧文的每个概念都有落点;深入展开留给对应系列(S05 并发、S02-J01 网络排障)。
9.1 操作系统(A-1860)
| 概念 | 一句话 | 深入 |
|---|---|---|
| 冯·诺依曼结构 | 运算器/控制器/存储器/输入输出 + 总线 | — |
| 中断 | 打断当前执行去处理紧急事件;分硬/软中断 | §5.4 |
| 内核态/用户态 | 权限级别的两档;系统调用是通道 | §5.1 |
| 用户态线程 / 内核态线程 | 前者由库调度、后者由内核调度 | B02 |
| 协程 | 用户态轻量线程,切换不经过内核 | B02 |
| 线程安全 | 共享可变状态 + 并发访问 = 竞争 | S05 |
| 进程创建(fork/exec) | fork 复制地址空间,exec 换程序 | B02 |
| 进程状态机 | 新建/就绪/运行/阻塞/终止 | B02 |
| IPC | 管道、消息队列、共享内存、信号、socket | S05 |
| 信号 | 异步通知机制(SIGTERM/SIGKILL) | — |
| 进程同步 | 互斥量、信号量、条件变量 | S05 |
| 上下文切换 | 保存/恢复寄存器与页表,有成本 | B02 |
| 调度算法 | FCFS、SJF、时间片、优先级、多级反馈队列 | B02(E-002) |
| 守护进程 | 脱离终端在后台运行 | S15 |
| 孤儿/僵尸进程 | 父进程先死 / 子进程退出未被回收 | S15 |
| 死锁 | 互斥、占有等待、不可剥夺、循环等待 | S05 |
| fork 与写时复制 | 共享物理页,写时才复制 | B02 |
| 磁盘调度 | 先来先服务、最短寻道、电梯算法 | — |
| 虚拟地址 / 虚拟内存 | 进程视角的连续空间,映射到物理页 | §5.3 |
| 缺页中断 | 访问页不在内存时触发 | §5.3 |
| 页面置换 | FIFO、LRU、Clock、OPT | S05 |
| 局部性原理 | 时间局部性 + 空间局部性 | S05 |
| 页表 / 快表 / MMU | 映射表 / 表缓存 / 地址转换硬件 | §5.3 |
| 动态分区分配 | 首次适应、最佳适应、最坏适应 | — |
| 内存覆盖 / 交换 | 手工复用内存 / 换出到磁盘 | — |
9.2 计算机网络(A-1861)
| 概念 | 一句话 | 深入 |
|---|---|---|
| OSI 七层 / TCP-IP 四层 | 教学模型 / 实现模型 | §6 |
| 交换机 | 链路层设备,按 MAC 转发 | — |
| 路由器 | 网络层设备,按 IP 与路由表转发 | §2.2 |
| HTTP 状态码 | 1xx 信息、2xx 成功、3xx 重定向、4xx 客户端错、5xx 服务端错 | B06 |
| HTTP/1.1 → 2.0 → 3.0 | 队头阻塞 → 多路复用 → QUIC(UDP) | B06、E-010 |
| HTTPS | HTTP over TLS | B06、J02 |
| 请求/响应报文结构 | 起始行 + 头部 + 空行 + 体 | B06 |
| TCP 报文段 | 首部字段(序号/确认/窗口/标志) | B03、B04 |
| 粘包与拆包 | 字节流无边界,需应用层划 | B05 |
| 三次握手 / 四次挥手 | 建立/释放连接 | B03 |
| 流量控制 | 接收窗口限制发送速率 | B04 |
| 拥塞控制 | 慢启动、拥塞避免、快重传、快恢复 | B04、E-006 |
| socket | 内核连接对象的编程接口 | §3 |
| UDP 报文段 | 无连接、不可靠、首部 8 字节 | B04 |
| MAC 地址 | 链路层地址 | — |
| IP 地址 / 子网掩码 | 网络层寻址与网段划分 | — |
| 网关 | 跨网段的出口 | §2.2 |
| ping / ICMP | 连通性探测 | J01 |
| DNS | 域名 → IP 的分布式查询系统 | §2.1 |
| ARP | IP → MAC 的本地解析 | §4 ③ |
| 数字签名 | 私钥签、公钥验;保证完整性与来源 | B06、J02 |
| 加密算法 | 对称(AES)/ 非对称(RSA、ECDHE) | B06、J02 |
| 常见攻击(SQL 注入等) | 属应用安全,非网络层 | S13(Web 安全) |
10. 下一篇
S02-B02 · 进程与线程:为什么"多开一个线程"不等于"多一份 CPU"
下一篇把 §5.2–§5.4 的三件事展开:进程与线程的内存模型、上下文切换的真实成本、以及"线程越多越好吗"的实测拐点。旧文 A-1615 的面试口径会改写成"能解释为什么"的机制叙述。
本篇属于系列《S02 · 计算机与网络基础》。旧文来源:A-1860、A-1861;扩展点 E-S02-001/010。DNS、TLS、HTTP/2 与 socket 统计为 2026-11-05 实测。