对应 2 周计划:D5 | 优先级:🟡 高频 | 大厂一面必考,TCP/HTTP 是经典送命题

〇、设计哲学:网络与 OS 为什么这么设计?(先读这节)

一句话主线:底层设计都在和「不可靠」作斗争——网络会丢包、会乱序、会拥塞;CPU 和 IO 速度差万倍。所以 TCP 用确认重传换可靠、HTTP 不断演进换并发、epoll 用事件驱动换海量连接、虚拟内存用隔离换安全。

图 0 · 四大设计动机

四大设计动机 → 对应机制① 为什么 TCP 可靠IP 网络会丢包乱序还被用作"不可靠"→ 序号+ACK+重传→ 握手确认双向通用开销换正确② 为什么 HTTP 演进1.0 短连接太慢队头阻塞卡住→ keep-alive/多路复用→ QUIC 绕开 TCP为并发不断优化③ 为什么 epollselect 每轮遍历全部O(n) 扛不住万连接→ 红黑树+就绪链表→ 只返回就绪的事件驱动 O(1)

各机制的设计思路(为什么):

  • 为什么 TCP 要三次握手:网络不可靠,客户端发的连接请求可能"迷路后又突然到达"。两次握手只能让服务端确认"客户端能发",却无法确认"客户端能收"——服务端白白分配资源等一个根本没收到的连接。三次才双向确认收发能力,同时用 seq/ack 同步初始序号。
  • 为什么 HTTP/2 用多路复用:HTTP/1.1 一个 TCP 连接同一时刻只能处理一个请求(队头阻塞),并发靠开多个连接(浪费)。多路复用把请求拆成帧、在同一条连接上交错传输,彻底解决应用层队头阻塞。
  • 为什么 HTTP/3 换 UDP(QUIC):HTTP/2 仍跑在 TCP 上,TCP 一个包丢了整个连接的所有流都要等重传(传输层队头阻塞)。QUIC 在 UDP 上自己实现可靠+拥塞控制,丢包只影响单个流,还能 0-RTT 建连、IP 变化不断连。
  • 为什么 epoll 比 select 快:select 每次调用都要把全部 fd 从用户态拷到内核态、再全量遍历(O(n));epoll 用红黑树管理 fd、就绪事件进链表,只把"就绪的"返回给应用(O(1)),万级连接也不退化。

一、TCP 协议

Q1: TCP 三次握手?

图 1 · 三次握手时序(双向确认收发能力)

客户端服务端① SYN, seq=x② SYN+ACK, seq=y, ack=x+1③ ACK, ack=y+1连接建立 ✓② 是 SYN+ACK 合并发送,所以总共 3 次而非 4 次

答案要点:

1. 客户端 → 服务端:SYN=1, seq=x(请求建立连接)

2. 服务端 → 客户端:SYN=1, ACK=1, seq=y, ack=x+1(同意并确认)

3. 客户端 → 服务端:ACK=1, seq=x+1, ack=y+1(确认收到,连接建立)

深追问(必问):

  • 为什么是三次不是两次?→ 防止失效的连接请求突然到达服务端造成资源浪费;两次握手无法确认客户端收到了服务端的 SYN(同步序列编号)。三次才能同时确认双方收发能力
  • 为什么不是四次?→ 三次已足够确认双向能力,四次多余
  • SYN 洪水攻击?→ 服务端收到 SYN 后进入 SYN_RECV 半连接状态,攻击者伪造大量 SYN 不回复 ACK,耗尽半连接队列;防护:SYN Cookie、缩短超时、增大队列

Q2: TCP 四次挥手?

答案要点:

1. 主动方 → 被动方:FIN=1(不再发数据)

2. 被动方 → 主动方:ACK(确认收到 FIN)

3. 被动方 → 主动方:FIN=1(被动方数据也发完了)

4. 主动方 → 被动方:ACK,进入 TIME_WAIT(等待 2MSL)

深追问(必问):

  • 为什么挥手要四次?→ 被动方收到 FIN 时可能还有数据要发,ACK 和 FIN 不能合并
  • 为什么 TIME_WAIT 要等 2MSL?(必背)
  • MSL:报文最大生存时间(默认 2 分钟,Linux 常为 30s-60s)
  • 保证最后 ACK 能到达(丢失则被动方重发 FIN)
  • 让旧连接报文在网络中消失,防止影响新连接
  • 大量 TIME_WAIT 怎么处理?→ 服务端调小 TIME_WAIT 超时、开启 reuse/quickack 参数、避免短连接频繁创建(连接池)

Q3: TCP 与 UDP 区别?

| 维度 | TCP | UDP |

|---|---|---|

| 连接 | 面向连接(三次握手) | 无连接 |

| 可靠性 | 可靠,确认重传 | 不可靠,尽最大努力 |

| 有序性 | 有序 | 无序 |

| 传输方式 | 字节流(粘包问题) | 报文(边界清晰) |

| 速度 | 慢 | 快 |

| 应用 | HTTP/FTP/SMTP | DNS/视频/语音/游戏 |

深追问:

  • 粘包/拆包是什么?怎么解决?
  • TCP 字节流无边界,多个包粘在一起(粘包)或被拆开(拆包)
  • 解决:固定长度、分隔符(如 \n)、消息头声明长度(最常用,如 Netty LengthFieldBasedFrameDecoder)
  • 为什么 UDP 视频不卡?→ 丢包不重传,实时性优先

Q4: TCP 可靠传输机制?

答案要点:

  • 确认应答 ACK + 超时重传
  • 滑动窗口(流量控制):接收方窗口大小控制发送速率
  • 拥塞控制:慢开始(指数增长)、拥塞避免(线性)、快重传、快恢复
  • 序号机制保证有序

深追问:

  • 流量控制 vs 拥塞控制?→ 前者是端到端(接收方能力),后者是网络中间(网络负载)
  • 重传机制:超时重传、快速重传(收到 3 个重复 ACK)、SACK(选择性确认)

二、HTTP 协议

Q5: HTTP 1.0/1.1/2/3 区别?

图 2 · HTTP 演进:每一代解决什么问题

1.0短连接每次建连1.1keep-alive 复用2多路复用(解应用层阻塞)3QUIC/UDP(解传输层阻塞)演进主线:减少连接开销 → 解决并发阻塞 → 绕开 TCP 本身的限制

| 版本 | 关键特性 |

|---|---|

| HTTP/1.0 | 短连接,每次请求建立/断开 TCP |

| HTTP/1.1 | 持久连接(keep-alive 默认开启)、管线化(有限)、Host 头、分块传输 |

| HTTP/2 | 多路复用(单连接并发请求)、二进制分帧、头部压缩 HPACK、服务端推送 |

| HTTP/3 | 基于 QUIC(UDP),0-RTT,解决队头阻塞 |

深追问(必问):

  • HTTP/1.1 队头阻塞?→ 一个请求响应慢,后续请求排队等待
  • HTTP/2 解决了应用层队头阻塞,但 TCP 层队头阻塞仍在(丢包导致整个流暂停)
  • HTTP/3 为什么用 UDP?→ QUIC 在应用层实现可靠传输,避免 TCP 队头阻塞,连接迁移(IP 变化不断连)

Q6: HTTPS 握手过程?

答案要点:

1. 客户端发送 ClientHello(支持的加密套件、随机数)

2. 服务端返回 ServerHello(选定算法、随机数)+ 证书(Cerificate)+ ServerHelloDone

3. 客户端验证证书(信任链、域名、有效期)→ 生成 pre-master secret,用证书公钥加密发送

4. 双方用三个随机数生成会话密钥

5. 客户端发送 Finished,服务端验证后也发 Finished

6. 之后用对称密钥加密通信

深追问(必问):

  • 为什么混合加密?→ 非对称(RSA/ECDHE)安全但慢,对称(AES)快;用非对称协商密钥,用对称传输数据
  • 证书作用?→ 防中间人攻击,证明服务端身份;CA 信任链:根证书→中间证书→站点证书
  • 如何验证证书?→ 检查有效期、域名匹配、CA 签名(用 CA 公钥验签)、吊销状态(CRL/OCSP)

Q7: GET 与 POST 区别?

  • GET:幂等、可缓存、参数在 URL(长度限制)、用于查询
  • POST:非幂等、不可缓存、参数在 body、用于提交
  • 安全:GET 参数暴露在日志/历史记录,敏感信息用 POST

深追问:

  • 幂等是什么?→ 同一请求执行多次结果相同;GET/PUT/DELETE 幂等,POST 非幂等
  • 状态码速记:200 OK、201 Created、301 永久重定向、302 临时重定向、304 Not Modified(缓存)、400 参数错误、401 未认证、403 无权限、404 不存在、500 服务器错误、502 网关错误、503 服务不可用、504 网关超时

Q8: 浏览器输入 URL 到页面展示全过程?

答案要点(必背八股):

1. DNS 解析:浏览器缓存 → 系统 hosts → 本地 DNS → 根/顶级/权威 DNS 迭代查询

2. TCP 连接:三次握手(HTTPS 加 TLS 握手)

3. 发送 HTTP 请求:请求行/头/体

4. 服务器处理:经负载均衡 → 应用 → 返回响应

5. 浏览器渲染:解析 HTML 构建 DOM、解析 CSS 构建 CSSOM、合成渲染树、布局、绘制

6. 资源加载:CSS 阻塞渲染、JS 阻塞解析(可 defer/async)、图片懒加载

深追问:

  • DNS 用 TCP 还是 UDP?→ 查询用 UDP(53),区域传输用 TCP
  • CDN 原理?→ 内容分发,就近节点缓存,回源;调度用 DNS 或 Anycast

三、操作系统

Q9: 进程、线程、协程区别?

| 维度 | 进程 | 线程 | 协程 |

|---|---|---|---|

| 资源 | 独立内存空间 | 共享进程内存 | 共享线程栈 |

| 调度 | 操作系统 | 操作系统 | 用户态自行调度 |

| 切换成本 | 高 | 中 | 极低 |

| 通信 | IPC(管道/共享内存/消息队列) | 共享内存+锁 | 语言层面 |

深追问:

  • 为什么协程切换快?→ 用户态切换,不涉及内核态/系统调用;Java 21 虚拟线程就是 JVM 实现的协程
  • 进程间通信方式?→ 管道、消息队列、共享内存、信号量、信号、Socket
  • 进程 vs 线程崩溃影响?→ 进程隔离,一个进程崩不影响其他;线程共享,一个线程崩溃(如 OOM)可能拖垮整个进程

Q10: 虚拟内存与分页?

答案要点:

  • 虚拟内存:每个进程有独立虚拟地址空间,通过页表映射物理内存
  • 好处:隔离、按需分配、可超物理内存(磁盘交换)
  • 页面置换算法:FIFO、LRU、LFU、Clock(改进版 LRU)

深追问:

  • 缺页中断?→ 访问的页不在内存,触发缺页,从磁盘加载(慢)
  • 内存映射 mmap 是什么?→ 文件/匿名内存映射到进程地址空间,零拷贝基础之一

Q11: IO 模型与 NIO?

答案要点(5 种):

1. 阻塞 IO(BIO):同步阻塞,线程等待

2. 非阻塞 IO(NIO):同步非阻塞,轮询

3. IO 多路复用:select/poll/epoll,一个线程监控多个连接

4. 信号驱动 IO

5. 异步 IO(AIO):完成通知

图 3 · select 全量遍历 vs epoll 只给就绪

select / poll每次遍历全部 fdO(n),1 个就绪也要扫完epoll红黑树管理全部fd就绪链表只返回这些事件驱动 O(1)

深追问(必问):

  • select/poll/epoll 区别?(大厂高频)
  • select:文件描述符数组有上限(1024),每次都要遍历全部、内核拷贝 fd 集合
  • poll:链表无上限,但仍全量遍历、效率 O(n)
  • epoll:红黑树 + 就绪链表,事件驱动回调,只返回就绪的 fd,O(1),Linux 独有
  • epoll 的 LT 和 ET?→ 水平触发(默认,有数据就通知)/ 边缘触发(状态变化才通知,需一次读完)
  • Netty 为什么快?→ NIO 多路复用 + 零拷贝 + 内存池 + 无锁串行设计(单线程处理一个 Channel 的事件)

Q12: 死锁?

答案要点(4 条件):

1. 互斥:资源排他

2. 持有并等待:持有一个等另一个

3. 不可剥夺:资源不能强抢

4. 循环等待:形成环

深追问:

  • 如何避免?→ 破坏任一条件:一次性申请所有资源、按序加锁(全局有序)、超时释放、银行家算法
  • 死锁排查?→ jstack 查看 BLOCKED 线程 + 锁信息;数据库死锁看 innodb 错误日志、show engine innodb status

四、Linux 常用命令(架构师必会)


top                     # CPU/内存/负载,按 1 看每核
free -h                 # 内存使用
df -h / du -sh *        # 磁盘容量
ps -ef | grep java      # 进程
netstat -tlnp           # 端口监听(ss -tlnp 更高效)
lsof -i:8080            # 端口占用
tail -f app.log         # 实时日志
grep 'ERROR' app.log | head -50
find / -name "*.log"    # 文件查找
awk '{print $1}' access.log | sort | uniq -c | sort -rn   # 日志分析
vmstat 1                # 系统整体状态
iostat -x 1             # 磁盘 IO
sar -n DEV 1            # 网络流量
curl -v http://localhost:8080/api   # 接口调试

五、漫画 · 三次握手打电话(理解"为什么三次")

三次握手 = 打个电话确认"能说能听"① 小明拨打明"喂?听得到吗?"(SYN)② 小红回应红"听得到!你呢?"(SYN+ACK)③ 小明确认明"听得到!"(ACK)④ 连接建立双方确认:明能说能听 ✓红能说能听 ✓双向能力都验证⑤ 为什么不能两次两次:小红不知道小明听不听得到→ 白等一个根本没收到的连接⑥ 额外好处同步初始序号seq/ack 对得上防失效连接省服务端资源

六、考前速记(10 条)

1. 三次握手防失效连接;四次挥手因为被动方可能还有数据

2. TIME_WAIT 2MSL:保证 ACK 到达 + 旧报文消失

3. 粘包解决:长度头 / 分隔符 / 固定长度

4. HTTP/2 多路复用解决应用层队头阻塞;HTTP/3 基于 QUIC(UDP) 解决传输层

5. HTTPS:非对称协商密钥 + 对称传输数据;证书防中间人

6. GET 幂等可缓存,POST 非幂等

7. 进程/线程/协程:切换成本递增,协程用户态调度

8. select 1024 上限全遍历;epoll 红黑树+就绪链表 O(1),LT/ET

9. Netty:多路复用 + 零拷贝 + 内存池 + 无锁串行

10. 死锁四条件:互斥/持有等待/不可剥夺/循环等待;jstack 排查

七、易错点提醒

  • 「三次握手」第 2 次是 SYN+ACK 合并(所以是 3 次不是 4 次)
  • HTTP/2 队头阻塞在 TCP 层仍在,HTTP/3 才彻底解决
  • TIME_WAIT 是主动关闭方进入的,不是被动方
  • select 上限 1024 是 fd 数量,不是连接数
  • DNS 默认 UDP 但区域传输(主从 - 同步)是 TCP

八、深挖 · 面试官连环追问 & 源码级原理

源码级 · TCP 与 IO:

  • TCP 三次握手在内核实现(tcp_v4_conn_request),半连接队列 syn_table 存待确认连接;SYN Flood 靠 tcp_syncookies 用算法生成 seq 代替队列。
  • epoll 内核用红黑树存监听 fd、rdllist 存就绪 fd;LT(水平触发)是默认,ET(边缘触发)必须一次性读净(while read 读到 EAGAIN),否则事件丢失。
  • 零拷贝:sendfile/splice 在内核态直接挪数据,避免用户态拷贝;Netty 用 FileRegion 走零拷贝。

连环追问:

  • TIME_WAIT 出现在主动关闭方,为什么不能跳过?→ 否则旧连接残留报文可能被新连接误收;高并发短连接建议 tcp_tw_reuse + 连接池。
  • 为什么 UDP 没有粘包?→ UDP 是报文边界清晰,粘包是 TCP 字节流特性。
  • 惊群问题?→ epoll 的 ET 模式 + EPOLLEXCLUSIVE 可避免唤醒多个等待线程。
  • 常见陷阱:select 的 1024 是 fd 数量上限(Linux 默认),不是连接数;海量连接必须 epoll。

九、大厂真题话术(照着背)

真题 1(腾讯/字节):"TCP 为什么三次握手,不是两次?"

  • 第一句:"两次不行——Server 无法确认 Client 收得到自己的包,会建出半开连接,被洪水攻击拖垮。"
  • 展开:三次本质是"双方各自确认对方的收发能力";SYN 带序列号,ACK 确认,第三次 Client 回 ACK 才双向就绪。
  • 带节奏:"挥手要四次是因为 TCP 全双工,关闭得两边各自 FIN,被动方可能还有数据要发。"

真题 2(阿里):"epoll 和 select 区别?ET 和 LT 怎么选?"

  • 第一句:"select 每次遍历全部 fd,1024 上限,海量连接 O(N) 慢;epoll 用红黑树 + 就绪链表,只返回就绪的,复杂度 O(1)。"
  • 展开:LT 水平触发(没处理完下次还通知,安全但低效);ET 边缘触发(只通知一次,必须循环读到 EAGAIN,效率高但容易漏)。
  • 带节奏:"我们网络框架用 ET + 非阻塞循环读,配合 EPOLLEXCLUSIVE 避免惊群。"

真题 3(美团):"TIME_WAIT 是什么?太多怎么办?"

  • 第一句:"主动关闭方最后等 2MSL,确保最后 ACK 到达、旧报文消失,防端口复用串数据。"
  • 展开:高并发短连接会堆大量 TIME_WAIT;可启用 tcp_tw_reuse(客户端)或调小 tcp_max_tw_buckets,但别瞎开 tcp_tw_recycle(已废弃,NAT 下会丢包)。
  • 带节奏:"根治是改用长连接/连接池,而不是硬清 TIME_WAIT。"

真题 4(字节):"零拷贝是什么?你哪里用过?"

  • 第一句:"减少数据在内核态/用户态间的冗余拷贝和上下文切换,sendfile 最典型。"
  • 展开:传统 read+write 4 拷贝 4 切换,sendfile 降到 2-3 次;Java 用 FileChannel.transferTo,Kafka/Netty 都靠它提吞吐。
  • 带节奏:"mmap 也算一种零拷贝思路,适合文件随机读。"

十、易错点提醒(高频踩坑清单)

  • ⚠️ 粘包/拆包是 TCP 字节流特性,UDP 是报文边界清晰、不会粘包;应用层必须自己定界(长度/分隔符)。
  • ⚠️ select 的 1024 是 fd 数量上限(Linux 默认),不是连接数;海量连接必须 epoll。
  • ⚠️ ET 模式必须读到 EAGAIN 才停,否则剩余数据不会被再通知,会丢数据。
  • ⚠️ 不要乱开 tcp_tw_recycle(已废弃),NAT 环境下会导致丢包;用 tcp_tw_reuse 或连接池。
  • ⚠️ 惊群:多进程/多线程 accept 同一监听 socket 会同时被唤醒,用 EPOLLEXCLUSIVE 或 SO_REUSEPORT 分摊。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐