计算机网络面试题
计算机网络专题
入门 / 中等 / 高难各 20 题以上,★ 是高频题。10 文档第八节是浓缩版,这里是完整版。 答网络题的技巧:先说结论,再画过程(握手、挥手画成时序),最后说「为什么这么设计」。面试官最爱追问的就是「为什么」。 能结合项目的地方都标了:SSE 流式输出、HAProxy 粘性会话、SSH 隧道做数据源跳板、优雅停机摘流量。
入门(24 题)
1. ★ OSI 七层模型和 TCP/IP 四层模型?每层做什么?
| OSI 七层 | TCP/IP 四层 | 作用 | 协议 / 设备 |
|---|---|---|---|
| 应用层 | 应用层 | 为应用提供服务 | HTTP、DNS、SSH、FTP、SMTP |
| 表示层 | 应用层 | 数据格式、加密、压缩 | TLS(常被归到这里)、JPEG |
| 会话层 | 应用层 | 建立和管理会话 | RPC |
| 传输层 | 传输层 | 端到端(进程到进程)传输,用端口区分进程 | TCP、UDP |
| 网络层 | 网络层 | 主机到主机,寻址和路由 | IP、ICMP;路由器 |
| 数据链路层 | 网络接口层 | 相邻节点之间传帧,用 MAC 地址 | 以太网、ARP;交换机 |
| 物理层 | 网络接口层 | 传比特(电信号、光信号) | 网线、光纤;集线器 |
2. ★ 数据从发送到接收经历了什么?(封装与解封装)
发送方从上往下逐层加头:应用数据 → 加 TCP 头(端口)成为段(segment)→ 加 IP 头(IP 地址)成为包(packet)→ 加以太网头(MAC 地址)成为帧(frame)→ 比特流。接收方从下往上逐层拆头,交给对应端口的进程。
3. IP 地址、子网掩码、CIDR 是什么?
- IPv4 地址 32 位,写成
192.168.1.10; - 子网掩码区分网络号和主机号:
255.255.255.0表示前 24 位是网络号; - CIDR 写法
192.168.1.0/24:前 24 位是网络号,这个网段有 2⁸ = 256 个地址(可用 254 个,去掉网络地址和广播地址)。 - 判断两台机器是否在同一网段:IP 和掩码按位与,结果相同就在同一网段。
4. ★ 公网 IP 和私网 IP?NAT 是什么?
- 私网地址段:
10.0.0.0/8、172.16.0.0/12、192.168.0.0/16,只在局域网内有效,不能直接在公网路由; - NAT(网络地址转换):路由器把内网的「私网 IP:端口」映射成「公网 IP:端口」出去,回来时再映射回来。一个公网 IP 可以让整个局域网上网,缓解了 IPv4 地址不够用的问题;
- 副作用:外网不能主动连进内网 —— 这就是为什么我们访问客户内网数据库要用 SSH 反向隧道(见高难第 24 题)。
5. ★ 端口是什么?常见端口有哪些?
端口是传输层用来区分同一台机器上不同进程的 16 位数字(0–65535)。一个连接由四元组唯一确定:源 IP、源端口、目的 IP、目的端口。 常见端口:22(SSH)、53(DNS)、80(HTTP)、443(HTTPS)、3306(MySQL)、5432(PostgreSQL)、6379(Redis)、9092(Kafka)、6333(Qdrant)、7687(Neo4j Bolt)。 0–1023 是知名端口,普通用户的进程不能直接监听(要 root 或者 capability)。
6. ★ TCP 和 UDP 的区别?
| TCP | UDP | |
|---|---|---|
| 连接 | 面向连接(要先握手) | 无连接 |
| 可靠性 | 保证不丢、不重、按序到达 | 不保证 |
| 传输单位 | 字节流(没有消息边界) | 数据报(每个包是独立的消息) |
| 首部 | 20–60 字节 | 8 字节 |
| 控制 | 流量控制、拥塞控制 | 无 |
| 通信方式 | 一对一 | 一对一、一对多、广播、多播 |
| 应用 | HTTP/1.1、HTTP/2、SSH、数据库、邮件 | DNS、音视频通话、直播、游戏、HTTP/3(QUIC) |
7. ★ TCP 三次握手的过程?
客户端 服务端
| ---- SYN, seq=x ----------------> | 客户端:CLOSED → SYN_SENT
| | 服务端:LISTEN → SYN_RCVD
| <--- SYN+ACK, seq=y, ack=x+1 ---- |
| ---- ACK, ack=y+1 --------------> | 双方:ESTABLISHED第三次握手的 ACK 可以携带数据,前两次不行。
8. ★ TCP 四次挥手的过程?
主动关闭方 被动关闭方
| ---- FIN, seq=u ----------------> | 主动方:FIN_WAIT_1
| <--- ACK, ack=u+1 --------------- | 被动方:CLOSE_WAIT;主动方:FIN_WAIT_2
| (被动方可能还在继续发数据) |
| <--- FIN, seq=w ----------------- | 被动方:LAST_ACK
| ---- ACK, ack=w+1 --------------> | 主动方:TIME_WAIT(等 2MSL)→ CLOSED客户端和服务端都可以主动关闭。
9. ★ HTTP 是什么?为什么说它是无状态的?
超文本传输协议,基于请求-响应模式的应用层协议,HTTP/1.1 和 HTTP/2 跑在 TCP 上,HTTP/3 跑在 QUIC(UDP)上。 无状态:服务器不记得之前的请求,每个请求都是独立的。好处是服务端简单、好扩展;需要「记住用户」时靠 Cookie / Session / Token 实现。
10. ★ HTTP 有哪些请求方法?
GET(获取)、POST(提交 / 创建)、PUT(整体替换)、PATCH(部分修改)、DELETE(删除)、HEAD(只要响应头,不要响应体)、OPTIONS(询问支持哪些方法,CORS 预检用它)、CONNECT(建立隧道,HTTPS 代理用它)、TRACE。 幂等:GET、PUT、DELETE、HEAD、OPTIONS 是幂等的(执行多次和一次效果相同),POST、PATCH 不是。
11. ★ HTTP 状态码有哪几类?常见的有哪些?
| 类别 | 含义 | 常见 |
|---|---|---|
| 1xx | 信息 | 101 Switching Protocols(WebSocket 升级) |
| 2xx | 成功 | 200 OK、201 Created、204 No Content、206 Partial Content(断点续传) |
| 3xx | 重定向 | 301 永久、302 临时、304 Not Modified(协商缓存命中)、307 / 308(保持请求方法的重定向) |
| 4xx | 客户端错误 | 400、401 未认证、403 无权限、404、405 方法不允许、409 冲突、413 请求体太大、422 校验失败、429 请求太多 |
| 5xx | 服务端错误 | 500、502 Bad Gateway(网关收到上游的无效响应 / 上游挂了)、503 服务不可用、504 Gateway Timeout(网关等上游超时) |
12. ★ GET 和 POST 的区别?
- 语义:GET 获取资源、应该是只读和幂等的;POST 提交数据、会改变服务端状态;
- 参数位置:GET 一般在 URL 里(会被记录在浏览器历史、服务器日志里,有长度限制,这个限制来自浏览器和服务器,不是 HTTP 协议规定的);POST 在请求体里;
- 缓存:GET 可以被缓存,POST 默认不缓存;
- 常见误区:「POST 比 GET 安全」—— 都是明文,安全靠 HTTPS;「GET 不能有请求体」—— 协议没禁止,但语义上不应该有,很多服务器和代理会忽略。
13. ★ HTTP 和 HTTPS 的区别?
HTTPS = HTTP + TLS。区别:明文 vs 加密传输;HTTPS 需要 CA 证书;默认端口 80 vs 443;HTTPS 多了 TLS 握手的开销(TLS 1.3 下只多 1 个 RTT)。HTTPS 解决三个问题:窃听(加密)、篡改(完整性校验)、冒充(证书身份认证)。
14. ★ Cookie 和 Session 的区别?
| Cookie | Session | |
|---|---|---|
| 存在哪 | 浏览器 | 服务端(内存 / Redis / 数据库) |
| 大小 | 单个约 4KB | 不限 |
| 安全 | 用户能看到和修改 | 用户只拿到 session id |
| 关系 | Session 通常依赖 Cookie 存 session id | |
Cookie 的重要属性:Domain、Path、Expires / Max-Age、HttpOnly(JS 读不到)、Secure(只走 HTTPS)、SameSite(防 CSRF)。 |
15. ★ DNS 是什么?解析过程?
把域名翻译成 IP 地址的系统。查询顺序:浏览器缓存 → 操作系统缓存(含 hosts 文件)→ 本地 DNS 服务器(运营商或 8.8.8.8 这类)→ 根域名服务器 → 顶级域名服务器(.com)→ 权威域名服务器(example.com)→ 拿到 IP,各级缓存,缓存时间由记录的 TTL 决定。 常见记录类型:A(域名 → IPv4)、AAAA(→ IPv6)、CNAME(别名,CDN 常用)、MX(邮件)、TXT(验证、SPF)、NS(权威服务器)。
16. URL 由哪几部分组成?
https://[email protected]:8443/v1/cards?id=1&page=2#top 协议(scheme)、用户信息、主机、端口、路径、查询参数、片段(# 后面的部分不会发给服务器,只在浏览器里用)。URL 里的特殊字符和中文要做 URL 编码(百分号编码,空格 → %20)。
17. ★ 从输入 URL 到页面显示,发生了什么?
- 解析 URL;查浏览器缓存(强缓存命中就直接用);
- DNS 解析得到 IP;
- TCP 三次握手;HTTPS 再做 TLS 握手;
- 发 HTTP 请求;
- 服务端处理:负载均衡 → Web 服务器 → 应用 → 数据库 / 缓存 → 返回响应;
- 浏览器解析 HTML 构建 DOM、解析 CSS 构建 CSSOM,合成渲染树 → 布局 → 绘制;遇到 JS、CSS、图片继续发请求;
- 连接保持复用(keep-alive)或四次挥手关闭。
18. ping 的原理?用的什么协议?
ICMP 协议(网络层,没有端口)。发 Echo Request,对方回 Echo Reply,根据往返时间算延迟,根据丢包率判断网络质量。ping 不通不一定是网络不通,可能是对方防火墙禁了 ICMP(云服务器常见)。
19. ★ MAC 地址和 IP 地址的区别?ARP 是干什么的?
- MAC 地址:网卡的物理地址(48 位),在局域网内一跳一跳传帧时用;
- IP 地址:逻辑地址,在全网端到端寻址用;
- 类比:IP 是收件人地址,MAC 是每一段路上快递员交接的下一站。数据包经过路由器时,IP 地址不变,MAC 地址每一跳都换。
- ARP:知道对方 IP,问「这个 IP 的 MAC 是多少」—— 在局域网内广播询问,对方单播回复,结果缓存在 ARP 表里。
20. ★ 长连接和短连接?
- 短连接:每次请求都新建 TCP 连接,用完就关(HTTP/1.0 默认);
- 长连接:一个 TCP 连接上连续发多个请求(HTTP/1.1 默认
Connection: keep-alive)。省掉了每次握手和 TLS 的开销,也减少了 TIME_WAIT。 - 数据库连接池、Redis 连接池、HTTP 客户端连接池都是长连接复用的思想。
21. Socket 是什么?
操作系统提供给应用程序的网络编程接口,是对 TCP / UDP 的封装。服务端流程:socket → bind(绑定 IP 和端口)→ listen(开始监听)→ accept(取出一个已建立的连接)→ recv / send → close;客户端:socket → connect(触发三次握手)→ send / recv → close。
22. ★ 127.0.0.1、localhost、0.0.0.0 的区别?
127.0.0.1:本机回环地址,数据不经过网卡,只有本机能访问;localhost:一个域名,通常解析到127.0.0.1(也可能是 IPv6 的::1);0.0.0.0:服务端监听时表示「监听本机所有网卡」,外部机器也能访问。- 开发中常见问题:服务监听在
127.0.0.1,在 Docker 容器里跑时外面访问不到 → 要监听0.0.0.0。
23. 什么是代理?正向代理和反向代理的区别?
- 正向代理:代表客户端去访问服务器,服务器不知道真实客户端是谁(翻墙代理、公司上网代理);
- 反向代理:代表服务器接收请求,客户端不知道真实服务器是哪台(Nginx、HAProxy、Caddy)。用途:负载均衡、TLS 终止、缓存、限流、隐藏后端。
- 我们的部署:Caddy / HAProxy 作为反向代理,按路径把
/api/console/*、/api/gateway/*、其他分到三个后端服务。
24. 什么是 CDN?
内容分发网络:把静态资源缓存在离用户近的边缘节点上,用户通过 DNS(通常是 CNAME 到 CDN 域名)被调度到最近的节点。好处:快、减轻源站压力、抗 DDoS。
中等(26 题)
1. ★ 为什么握手是三次,不是两次或四次?
- 两次不行:
- 防止历史连接:网络里滞留的一个旧 SYN 迟到了,如果两次握手,服务端收到就直接建立连接、分配资源,而客户端根本不需要;三次握手时客户端收到不对的 SYN+ACK 会回 RST 终止。(RFC 793 给出的首要原因)
- 同步双方的初始序列号:双方都要告诉对方自己的序列号并得到确认,两次只能确认一方的。
- 避免资源浪费。
- 四次没必要:服务端的 SYN 和 ACK 可以合并在一个包里发,三次就够了。
2. ★ 为什么挥手是四次?能不能变成三次?
TCP 是全双工的,两个方向要分别关闭。被动方收到 FIN 时只表示对方不再发了,自己可能还有数据要发,所以先回 ACK,等数据发完再发 FIN,这两个包通常分开。 能变三次:如果被动方收到 FIN 时正好没有数据要发,并且开启了延迟确认,就可以把 ACK 和 FIN 合并成一个包发出,抓包时经常看到三次挥手。
3. ★ TIME_WAIT 是什么?为什么要等 2MSL?
主动关闭方发完最后一个 ACK 后进入 TIME_WAIT,等 2MSL(MSL 是报文最大生存时间;Linux 的 TIME_WAIT 固定 60 秒)才真正关闭。原因:
- 保证最后一个 ACK 能到达:如果它丢了,被动方会重发 FIN,主动方还在 TIME_WAIT 就能再回 ACK;如果已经关闭了,会回 RST,被动方异常关闭;
- 让旧连接的报文在网络中消失:防止延迟到达的旧报文被同样四元组的新连接误收。 为什么是 2MSL:一来一回(ACK 过去最多 1 MSL,重发的 FIN 回来最多 1 MSL)。
4. ★ TIME_WAIT 过多有什么危害?怎么处理?
- 危害:占用内存(不多);如果是客户端,占用本地端口,端口耗尽后新连接建不起来(
Cannot assign requested address)。 - 谁会产生:主动关闭的一方。HTTP 短连接下如果服务端主动关闭,服务端就会堆积;Nginx 用短连接访问后端时,Nginx 这一侧会堆积。
- 处理:
- 用长连接 / 连接池(根本解法);
net.ipv4.tcp_tw_reuse = 1:作为客户端发起新连接时,可以复用处于 TIME_WAIT 的端口(依赖 TCP 时间戳,安全);- 扩大本地端口范围
ip_local_port_range; - 不要用
tcp_tw_recycle:在 NAT 环境下会丢包,Linux 4.12 已经删除。
5. ★ 服务端出现大量 CLOSE_WAIT 是什么原因?
CLOSE_WAIT 是被动关闭方收到 FIN、回了 ACK、但自己还没调用 close 的状态。大量出现几乎都是应用程序的 bug:连接用完没关(连接泄漏)、处理线程卡住了没走到 close、异常分支漏了关闭。排查:ss -tanp | grep CLOSE_WAIT 找到是哪个进程、连向哪里,再查代码里那段连接的关闭逻辑。
6. ★ 半连接队列和全连接队列是什么?
- 半连接队列(SYN 队列):服务端收到 SYN、回了 SYN+ACK、还在等第三次 ACK 的连接;
- 全连接队列(accept 队列):三次握手完成、等待应用
accept()取走的连接,大小是min(listen 的 backlog, net.core.somaxconn)。 - 全连接队列满了:默认丢弃第三次 ACK(客户端以为连上了,发数据却没回应);
tcp_abort_on_overflow = 1时直接回 RST。现象是高并发时偶发连接超时或connection reset。查看:ss -lnt的Recv-Q(当前排队数)和Send-Q(队列上限)。 - 解决:调大 backlog 和
somaxconn;更重要的是让应用 accept 得更快(worker 数够、不阻塞)。
7. ★ SYN 洪水攻击是什么?怎么防?
攻击者伪造大量源 IP 发 SYN,不回第三次 ACK,把服务端的半连接队列占满,正常用户连不进来。 防护:SYN Cookie(tcp_syncookies = 1:队列满时不再保存半连接状态,而是把信息编码进 SYN+ACK 的序列号里,收到 ACK 时再验证还原);调大半连接队列;减少 SYN+ACK 重试次数(tcp_synack_retries);前面挂高防 / 云厂商的 DDoS 防护。
8. ★ TCP 怎么保证可靠传输?
- 序列号:每个字节都有编号,接收方据此排序、去重;
- 确认应答(ACK):累积确认,
ack = N表示 N 之前的都收到了; - 重传:超时重传、快速重传;
- 校验和:检测数据损坏;
- 流量控制:滑动窗口,不让发送方把接收方撑爆;
- 拥塞控制:不让发送方把网络撑爆;
- 连接管理:三次握手、四次挥手。
9. ★ TCP 的重传机制有哪些?
- 超时重传:发出数据后启动计时器,超过 RTO(重传超时时间,根据 RTT 动态计算)没收到 ACK 就重传;每次超时 RTO 翻倍(指数退避);
- 快速重传:收到 3 个重复 ACK(说明后面的包到了、中间某个包丢了),不等超时立刻重传丢失的那个;
- SACK(选择性确认):接收方在 TCP 选项里告诉发送方「我收到了哪些区间」,发送方只重传真正丢的那些,而不是丢失点之后的全部;
- D-SACK:告诉发送方「这个包我收到了两次」,帮助发送方判断是 ACK 丢了还是网络延迟。
10. ★ 滑动窗口和流量控制?
- 接收方在每个 ACK 里通过窗口大小字段告诉发送方「我的缓冲区还能收多少字节」,发送方在窗口范围内可以连续发多个包不用等确认 —— 提高吞吐量;
- 接收方处理慢、缓冲区满了,就通告窗口为 0,发送方停止发送;之后发送方会定时发窗口探测报文,询问窗口是否恢复(防止接收方的窗口更新报文丢了导致双方死等);
- 窗口字段只有 16 位(最大 64KB),高带宽网络下通过窗口扩大选项(Window Scale)放大。
11. ★ 拥塞控制的四个算法?
发送窗口 = min(接收方通告窗口,拥塞窗口 cwnd)。
- 慢启动:cwnd 从很小的值(通常 10 个 MSS)开始,每收到一个 ACK 加 1,相当于每个 RTT 翻倍(指数增长);
- 拥塞避免:cwnd 达到慢启动阈值 ssthresh 后,改成每个 RTT 加 1(线性增长);
- 超时(严重拥塞):ssthresh 设为当前 cwnd 的一半,cwnd 重置为初始值,重新慢启动;
- 快重传 + 快恢复(收到 3 个重复 ACK,轻度拥塞):ssthresh 设为 cwnd 的一半,cwnd 也设为差不多这个值,直接进入拥塞避免,不用从头慢启动。 流量控制 vs 拥塞控制:前者看接收方的能力,后者看整个网络的状况。
12. Nagle 算法和延迟确认是什么?为什么有时要关掉?
- Nagle 算法:发送方有未确认的数据时,把后续的小包攒起来,等 ACK 回来或攒够一个 MSS 再发,减少大量小包;
- 延迟确认:接收方不立刻回 ACK,等一小会儿(最多几十到 200 毫秒),看能不能和响应数据一起发;
- 两者一起作用时,小请求可能被白白延迟几十到 200 毫秒。**对延迟敏感的场景(RPC、游戏、Redis 客户端、SSE 推送小事件)**通常设置
TCP_NODELAY关闭 Nagle。
13. ★ 什么是粘包和拆包?怎么解决?
TCP 是字节流,没有消息边界:发送方连续发两条消息,接收方可能一次读到两条(粘包),也可能一条分两次才读完(拆包)。原因:Nagle 合并、MSS 分段、接收缓冲区里攒了多条。 解决(在应用层定义边界):
- 固定长度;
- 特殊分隔符:HTTP 头用
\r\n\r\n结束,SSE 用空行\n\n分隔事件(我们前端读流式输出时要自己按\n\n切,并把最后一段不完整的留到下次); - 长度前缀(最常用):消息头里写明消息体长度,比如 HTTP 的
Content-Length、Redis 协议 RESP、大多数 RPC 协议。 UDP 是数据报,天然有边界,没有粘包问题。
14. ★ TCP 的 keepalive 和 HTTP 的 keep-alive 是一回事吗?
不是。
- HTTP keep-alive:应用层的连接复用,一个 TCP 连接上发多个 HTTP 请求;
- TCP keepalive:传输层的保活探测,连接空闲一段时间后(Linux 默认 2 小时)发探测包,对方没回应就判定连接已死。默认时间太长,实际应用通常自己做应用层心跳。
15. MTU 和 MSS 的区别?IP 分片有什么问题?
- MTU:链路层一帧能承载的最大数据(以太网 1500 字节);
- MSS:TCP 报文段里数据部分的最大长度,通常 = MTU − IP 头 20 − TCP 头 20 = 1460 字节,握手时双方协商;
- 为什么 TCP 要有 MSS 而不是交给 IP 分片:IP 分片后任何一片丢了,整个 IP 包都要重传(IP 层没有重传,只能靠 TCP 重传整个段);TCP 按 MSS 自己分段,丢哪段重传哪段。
- VPN、隧道会额外加头,导致实际 MTU 变小,出现「小请求正常、大请求卡住」的现象,要调小 MTU / MSS。
16. ★ HTTP/1.0、1.1、2、3 的区别?
| 版本 | 关键改进 | 遗留问题 |
|---|---|---|
| 1.0 | 每个请求一个 TCP 连接 | 连接开销大 |
| 1.1 | 长连接默认开启;管道化(基本没人用);Host 头(一个 IP 多个网站);分块传输编码(chunked);更多缓存控制 | 一个连接上请求-响应串行,队头阻塞;浏览器对同一域名开 6 个连接;头部冗余 |
| 2 | 二进制分帧;多路复用(一个连接上并发多个请求);头部压缩 HPACK;流优先级;服务端推送(已被主流浏览器弃用) | 底层还是 TCP,丢一个包所有流都要等(TCP 层队头阻塞) |
| 3 | 基于 QUIC(UDP);彻底解决队头阻塞;0-RTT / 1-RTT 建连;连接迁移(手机切 Wi-Fi / 4G 连接不断);头部压缩 QPACK | UDP 可能被部分网络限速或屏蔽;服务端实现更复杂 |
17. ★ 什么是队头阻塞?分别发生在哪一层?
- HTTP 层(HTTP/1.1):同一个连接上,前一个响应没回来,后面的请求只能等。HTTP/2 用多路复用解决了;
- TCP 层(HTTP/2):TCP 保证字节按序交付,某个包丢了,后面已经到达的包(哪怕属于别的流)也要等它重传。HTTP/3 换成 QUIC,每个流独立按序,一个流丢包不影响其他流。
18. ★ HTTPS 的 TLS 握手过程(TLS 1.2)?
- Client Hello:客户端发送支持的 TLS 版本、加密套件列表、一个随机数 Client Random;
- Server Hello:服务端选定版本和加密套件,发送随机数 Server Random;接着发证书;如果用 ECDHE,还发送服务端的 DH 参数(用私钥签名);
- 客户端验证证书(是否由可信 CA 签发、域名是否匹配、是否过期 / 吊销),然后生成预主密钥(RSA 方式:用服务器公钥加密发过去;ECDHE 方式:发送自己的 DH 参数,双方各自算出同一个值);
- 双方用 Client Random + Server Random + 预主密钥算出会话密钥;
- 双方互发
Finished(用会话密钥加密的握手摘要,验证握手没被篡改),之后用对称加密传输数据。 TLS 1.2 要 2 个 RTT。
19. RSA 和 ECDHE 密钥交换有什么区别?什么是前向安全?
- RSA:预主密钥由客户端用服务器公钥加密传输。如果服务器私钥将来泄露,之前截获的所有流量都能被解密 —— 不具备前向安全;
- ECDHE:每次握手临时生成 DH 密钥对,会话密钥由双方各自计算、从不在网络上传输。即使私钥泄露,过去的会话也解不开 —— 前向安全。
- TLS 1.3 已经去掉了 RSA 密钥交换,只保留具备前向安全的算法。
20. ★ TLS 1.3 相比 1.2 有什么改进?
握手从 2-RTT 减到 1-RTT(客户端第一个消息就带上密钥交换参数);支持 0-RTT 会话恢复(有重放攻击风险,只适合幂等请求);删除了不安全的算法(RSA 密钥交换、RC4、SHA-1 等);证书在握手中也被加密。
21. ★ 证书是怎么验证的?什么是中间人攻击?
- 证书包含:域名、公钥、有效期、签发者,以及 CA 用私钥对这些内容的签名;
- 验证:浏览器 / 操作系统内置了根 CA 的公钥,沿着证书链(服务器证书 → 中间 CA → 根 CA)逐级用上级公钥验证签名;再检查域名、有效期、吊销状态;
- 中间人攻击:攻击者在中间冒充服务器,给客户端发自己的证书。因为这个证书不是可信 CA 签的(或者域名对不上),浏览器会报警。
- 抓包工具(Charles、mitmproxy)能看 HTTPS 内容,原理就是让你手动信任它的根证书,它就能当中间人。对安全要求高的 App 会做证书固定(只认特定证书)来防这个。
22. ★ HTTP 缓存机制:强缓存和协商缓存?
- 强缓存:响应头
Cache-Control: max-age=31536000(优先)或Expires(绝对时间,受客户端时钟影响)。有效期内浏览器不发请求,直接用缓存(状态显示 200 from cache); - 协商缓存:强缓存过期后,带上验证信息去问服务器:
If-None-Match: <ETag>(内容指纹,优先)↔ 响应头ETag;If-Modified-Since: <时间>↔ 响应头Last-Modified(精度只到秒);- 没变化返回 304(不带内容),变了返回 200 和新内容。
Cache-Control常用值:no-cache(可以缓存但每次都要协商)、no-store(完全不缓存)、private(只允许浏览器缓存,CDN 不行)、public。- 实践:带哈希文件名的静态资源(
app.3f2a.js)设长期强缓存;HTML 设no-cache。
23. ★ 什么是跨域?CORS 怎么工作?
- 同源:协议、域名、端口都相同。浏览器的同源策略限制 JS 读取跨源请求的响应(请求其实发出去了,是响应被浏览器拦下);
- 简单请求(GET / HEAD / POST,且只用了简单的头和 Content-Type):直接发,响应头里有
Access-Control-Allow-Origin匹配就放行; - 非简单请求(PUT、DELETE、带
Content-Type: application/json、带自定义头):先发一个 OPTIONS 预检请求,服务端返回允许的方法、头、源,通过后再发真正的请求;Access-Control-Max-Age缓存预检结果; - 带 Cookie:前端
credentials: 'include',服务端Access-Control-Allow-Credentials: true,且 Allow-Origin 不能是*; - 其他方案:同域反向代理(我们的 Next.js rewrite 就是这样,从根上避免跨域)。
24. ★ WebSocket 的握手过程?和 HTTP 是什么关系?
WebSocket 借用 HTTP 完成握手,然后升级成独立的协议:
- 客户端发 HTTP GET,带
Upgrade: websocket、Connection: Upgrade、Sec-WebSocket-Key; - 服务端返回 101 Switching Protocols,带
Sec-WebSocket-Accept(由 Key 计算得到,证明服务端理解 WebSocket 协议); - 之后这个 TCP 连接上跑 WebSocket 帧,全双工,可以随时双向发消息。
- 需要心跳(ping / pong 帧)保持连接、检测断线。经过 Nginx 要配置
proxy_set_header Upgrade和Connection。
25. ★ SSE、WebSocket、长轮询怎么选?
| SSE | WebSocket | 长轮询 | |
|---|---|---|---|
| 方向 | 服务端 → 客户端单向 | 双向 | 模拟推送 |
| 协议 | 普通 HTTP(text/event-stream,分块传输) | 独立协议 | 普通 HTTP |
| 断线重连 | 浏览器 EventSource 自动重连,带 Last-Event-ID | 自己实现 | 天然 |
| 数据 | 文本 | 文本 / 二进制 | 任意 |
| 经过代理 / 网关 | 兼容性好(就是 HTTP) | 需要代理支持 Upgrade | 好 |
| 适合 | LLM 流式输出、通知、进度 | 聊天、协同编辑、远程控制 | 兼容性兜底 |
| 我们对话用 SSE;浏览器扩展、远程节点和后端之间要双向收发指令,用 WebSocket。 |
26. ★ DNS 为什么主要用 UDP?递归查询和迭代查询?
- 用 UDP:一次查询一问一答、数据小,UDP 不用握手,快;响应超过 512 字节(老标准)或做区域传输(主从 DNS 同步)时用 TCP;
- 递归查询:客户端问本地 DNS,本地 DNS 负责一路查到底再告诉客户端(客户端 → 本地 DNS 是递归);
- 迭代查询:本地 DNS 问根服务器,根服务器说「你去问 .com 服务器」,本地 DNS 再去问……(本地 DNS → 各级服务器是迭代)。
- DNS 劫持:运营商或攻击者篡改解析结果。解决:HTTPDNS(通过 HTTPS 直接问 DNS 服务,App 常用)、DoH / DoT(DNS over HTTPS / TLS)。
高难(25 题)
1. TCP 有哪些状态?画一下状态机。
11 种:CLOSED、LISTEN、SYN_SENT、SYN_RCVD、ESTABLISHED、FIN_WAIT_1、FIN_WAIT_2、CLOSE_WAIT、CLOSING、LAST_ACK、TIME_WAIT。
- 正常握手:客户端
CLOSED → SYN_SENT → ESTABLISHED;服务端LISTEN → SYN_RCVD → ESTABLISHED; - 正常挥手:主动方
ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED;被动方ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED; CLOSING:双方同时发 FIN(同时关闭),各自收到对方的 FIN 而还没收到对自己 FIN 的 ACK。- 线上排查用
ss -tan | awk '{print $1}' | sort | uniq -c统计各状态数量。
2. 初始序列号(ISN)为什么要随机?
- 如果每次从 0 开始,旧连接的延迟报文很容易落在新连接的序列号范围内被误收;
- 防止攻击者猜出序列号伪造报文(TCP 序列号预测攻击)。
- 现代实现:ISN 基于时钟(大约每 4 微秒加 1)再加上对四元组和密钥做的哈希(RFC 6528),既随时间增长又不可预测。
3. ★ 已经建立的连接,客户端突然断电 / 进程崩溃,服务端会怎样?
- 进程崩溃:操作系统还在,会代替进程关闭连接 —— 正常发 FIN,服务端能感知;
- 机器断电 / 网线被拔:客户端什么都发不出来。
- 服务端如果此时有数据要发:发出去收不到 ACK → 不断超时重传(指数退避),重传达到上限(Linux
tcp_retries2,默认 15 次,大约十几分钟)后断开; - 服务端如果一直不发数据:这个连接会一直挂着,直到 TCP keepalive 探测发现(默认 2 小时后才开始探测)。
- 客户端重启后,服务端再发数据,客户端内核发现没有这个连接,回 RST。
- 服务端如果此时有数据要发:发出去收不到 ACK → 不断超时重传(指数退避),重传达到上限(Linux
- 结论:不能依赖 TCP 及时发现死连接,长连接要做应用层心跳。
4. ★ 服务端出现大量 TIME_WAIT,是什么场景?怎么办?
- 场景:服务端主动关闭连接。常见于:
- HTTP 短连接,服务端处理完主动关;
- 客户端请求头
Connection: close,或 HTTP keep-alive 超时 / 超过最大请求数,由服务端关闭; - Nginx 作为代理访问后端时用短连接(Nginx 到后端默认是 HTTP/1.0 短连接,要配置
proxy_http_version 1.1+keepalive)。
- 影响:服务端监听端口是固定的,TIME_WAIT 不会耗尽服务端的端口(四元组里客户端端口不同),主要是占内存和连接表;真正危险的是代理 / 客户端一侧的 TIME_WAIT 耗尽本地端口。
- 处理:开启长连接、配置上游 keepalive 连接池、
tcp_tw_reuse、调大ip_local_port_range。
5. ★ 一台服务器最多能支持多少个 TCP 连接?
- 理论上:一个连接由四元组确定,服务端监听一个固定端口,客户端 IP × 客户端端口的组合约 2³² × 2¹⁶,远超实际;
- 实际限制:
- 文件描述符:每个连接占一个 fd,受
ulimit -n(进程级)和fs.file-max(系统级)限制,默认 1024 要调大; - 内存:每个 TCP 连接内核要几 KB 的结构 + 收发缓冲区,空闲连接约 3–4KB,有数据传输时更多;
- 应用本身的模型:一个连接一个线程的模型几千个就扛不住,epoll + 协程可以到几十万;
- 文件描述符:每个连接占一个 fd,受
- 经验:单机几十万到百万级长连接是可以做到的(C10K → C10M 问题)。
- 作为客户端连同一个目标(同一个 IP:端口)时,受本地端口数限制,约 2.8 万(默认端口范围 32768–60999)—— 这就是要用连接池的原因。
6. ★ 客户端偶发 Connection reset by peer,可能是什么原因?
RST 的常见触发:
- 连接到一个没有进程监听的端口;
- 对方进程崩溃后重启,收到旧连接的数据;
- 对方的全连接队列满且
tcp_abort_on_overflow = 1; - 对方调用 close 时接收缓冲区里还有没读完的数据(会发 RST 而不是 FIN);
- 设置了
SO_LINGER为 0 的强制关闭; - 连接池里的连接已被服务端关闭(空闲超时),客户端还在用 —— 最常见,见下一题;
- 中间的防火墙 / 负载均衡 / NAT 设备超时清理了连接表项,之后的包被它回 RST。 排查:
tcpdump抓包看 RST 是谁发的;对比两端的空闲超时配置。
7. ★ 连接池偶发报错:用连接池访问数据库 / HTTP 服务,空闲一阵后第一次请求总失败
- 原因:服务端(或中间的负载均衡、NAT、防火墙)的空闲超时比客户端连接池的空闲回收时间短,服务端已经把连接关了,客户端不知道,拿出来一用就失败(RST 或 broken pipe)。
- 解决:
- 客户端连接池的空闲回收时间小于服务端和中间设备的空闲超时(SQLAlchemy 的
pool_recycle); - 取连接前探活(SQLAlchemy 的
pool_pre_ping,代价是每次多一个往返); - 对幂等请求做一次自动重试;
- 定期心跳保持连接活跃。
- 客户端连接池的空闲回收时间小于服务端和中间设备的空闲超时(SQLAlchemy 的
- 我们的 SQLAlchemy 配置里
pool_recycle和pool_pre_ping都开了,就是为了这个。
8. ★ SSE 流式接口经过 Nginx 后,前端一直收不到增量,最后一次性收到全部内容,怎么排查?
- 代理缓冲:Nginx 默认
proxy_buffering on,会把上游响应攒起来再发 → 关闭:proxy_buffering off,或者后端响应头加X-Accel-Buffering: no(我们的 SSE 响应头里都加了); - 压缩:gzip 会攒数据再压缩 → 对
text/event-stream关闭 gzip; - HTTP 版本:上游要用 HTTP/1.1(
proxy_http_version 1.1)才能用分块传输; - 超时:
proxy_read_timeout默认 60 秒,模型长时间思考没输出时连接会被代理断掉 → 调大,并且后端定期发心跳(SSE 允许发以冒号开头的注释行: ping\n\n,前端会忽略); - 应用层:确认后端真的是边生成边 flush,而不是攒完再返回(Flask 要返回生成器,gunicorn 要用 gevent 这类异步 worker);
- CDN / WAF 也可能缓冲,要单独配置。
9. ★ 长连接(SSE / WebSocket)用一段时间后被「静默断开」,两端都没收到关闭通知
- 原因:中间设备(负载均衡、NAT 网关、防火墙、云厂商的 SLB)有空闲超时(几十秒到几分钟),超时后直接清掉连接表项,不通知任何一端;之后某一端发数据,可能被 RST 或者一直超时重传。
- 解决:应用层心跳,间隔小于路径上最短的空闲超时(比如每 15–30 秒一次):SSE 发注释行,WebSocket 发 ping 帧;客户端检测到断线后自动重连(我们的 SSE 重连后从 Redis Stream 重放,不丢内容)。
10. ★ 一个接口偶发很慢(几秒),怎么从网络层面定位慢在哪?
- 先拆分耗时:
curl -w打印各阶段时间:curl -o /dev/null -s -w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://api.example.com/x- DNS 慢:DNS 服务器问题、没有缓存;
- connect 慢:网络延迟大、SYN 重传(全连接 / 半连接队列满、丢包)—— 偶发 1 秒、3 秒的延迟往往就是 SYN 重传的超时时间;
- TLS 慢:证书链太长、OCSP 查询慢、没有会话复用;
- TTFB(首字节时间)慢:服务端处理慢(这就不是网络问题了,去看应用和数据库);
- 传输慢:带宽、丢包重传、窗口太小。
- 工具:
mtr(逐跳看丢包和延迟)、ss -ti(看某个连接的 RTT、重传次数、拥塞窗口)、tcpdump+ Wireshark(看握手、重传、窗口)、netstat -s | grep retrans(全局重传统计)。
11. 怎么用 tcpdump 抓包分析三次握手?
tcpdump -i any -nn 'tcp port 5432 and (tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0)' -w out.pcap抓到的包用 Wireshark 打开:看 [S](SYN)、[S.](SYN+ACK)、[.](ACK)、[F](FIN)、[R](RST);看序列号、窗口大小、MSS 选项;Wireshark 的「专家信息」会标出重传、乱序、零窗口。常见发现:SYN 发了收不到回应(防火墙 / 安全组没放行)、收到 RST(端口没监听)、反复重传(丢包)。
12. ★ BBR 和 CUBIC 拥塞控制有什么区别?
- CUBIC(Linux 默认):基于丢包判断拥塞,丢包了才减速。问题:在高带宽、有随机丢包的网络(无线、跨国链路)里会误判拥塞,吞吐上不去;缓冲区很大的网络里会把缓冲区填满才丢包,导致延迟很高(bufferbloat)。
- BBR(Google 提出):基于带宽和延迟的测量:持续估计瓶颈带宽和最小 RTT,按「带宽 × 延迟」控制发送量,不以丢包作为主要信号。在有丢包的长距离链路上吞吐提升明显、延迟更低。
- 开启:
net.ipv4.tcp_congestion_control = bbr(需要内核 4.9+)。
13. ★ QUIC 是怎么在 UDP 上实现可靠传输的?解决了 TCP 的哪些问题?
- 在用户态(应用层)实现了 TCP 的那一套:包序号(单调递增,重传也用新序号,消除 TCP 重传歧义)、ACK、重传、流量控制、拥塞控制;
- 解决的问题:
- 队头阻塞:多个流在 QUIC 层各自独立排序,一个流丢包不影响其他流;
- 握手慢:传输握手和 TLS 1.3 握手合并,首次 1-RTT,恢复连接 0-RTT;
- 连接迁移:用 Connection ID 而不是四元组标识连接,手机从 Wi-Fi 切到 4G(IP 变了)连接不断;
- 协议僵化:TCP 在内核里,升级要等操作系统;中间设备会干涉 TCP 头。QUIC 在用户态、头部大部分加密,迭代快;
- 代价:UDP 在部分网络被限速 / 屏蔽(所以浏览器要能回退到 HTTP/2);用户态处理加密和协议,CPU 开销比内核 TCP 高。
14. HTTP/2 多路复用的原理?
- 把每个请求 / 响应拆成帧(HEADERS 帧、DATA 帧),每个帧带流 ID;一个 TCP 连接上多个流的帧可以交错发送,接收方按流 ID 重新组装;
- 头部压缩 HPACK:静态表(常用头)+ 动态表(本连接发过的头)+ 哈夫曼编码,重复的头只发一个索引;
- 有流级别和连接级别的流量控制、流优先级;
- 服务端推送:理论上能提前推资源,实际效果不好,Chrome 等主流浏览器已经移除支持;
- 一个域名一个连接就够了,HTTP/1.1 时代的「域名分片」「资源合并」反而成了负优化。
- gRPC 就是基于 HTTP/2 的(多路复用 + 双向流)。
15. HTTPS 性能怎么优化?
- 协议:升级 TLS 1.3(1-RTT)、启用 HTTP/2 / HTTP/3;
- 会话复用:Session ID / Session Ticket(TLS 1.2)、PSK(TLS 1.3),复用之前协商的密钥,省掉完整握手;
- OCSP Stapling:服务器自己定期去 CA 查证书吊销状态并在握手时附上,客户端不用再单独去查;
- 证书:用 ECDSA 证书(比 RSA 计算快、证书小),证书链精简;
- TLS 终止放在负载均衡 / 网关上集中处理,后端走内网 HTTP。
16. ★ 常见的 DDoS 攻击和防护?
| 攻击 | 层 | 原理 | 防护 |
|---|---|---|---|
| SYN Flood | 传输层 | 占满半连接队列 | SYN Cookie、高防 |
| UDP Flood / 反射放大(DNS、NTP、Memcached 反射) | 网络 / 传输层 | 小请求换大响应打到受害者,用带宽压垮 | 运营商 / 云厂商的流量清洗、黑洞 |
| HTTP Flood(CC 攻击) | 应用层 | 大量看起来正常的请求打昂贵的接口 | 限流(按 IP / 用户)、验证码、WAF、缓存、人机识别 |
| 慢速攻击(Slowloris) | 应用层 | 建大量连接、极慢地发请求头,占满服务器连接 | 请求头读取超时、限制每 IP 连接数、用 Nginx 这类事件驱动的服务器挡在前面 |
| 对 AI 应用特别要注意:模型调用接口是最贵的接口,一定要按用户 / 组织做配额和限流,否则一个脚本就能把模型额度刷光。 |
17. Cookie 的 SameSite 属性是怎么防 CSRF 的?
Strict:跨站请求一律不带 Cookie(从外站点链接进来也不带,体验差);Lax(现代浏览器默认):跨站的「顶级导航 GET」(点链接跳转)带 Cookie,跨站的 POST、iframe、AJAX 不带 —— 挡住了大部分 CSRF;None:都带,但必须同时设置Secure。嵌入第三方页面(比如我们的 OEM 场景:页面被嵌进合作方系统的 iframe)需要SameSite=None; Secure,这时就要额外的 CSRF 防护(token、校验 Origin)。- 「同站(same-site)」和「同源(same-origin)」不同:同站只看注册域名(
a.example.com和b.example.com是同站不同源)。
18. ★ 四层负载均衡和七层负载均衡的区别?
| 四层(L4) | 七层(L7) | |
|---|---|---|
| 依据 | IP + 端口(TCP / UDP) | HTTP 内容:URL 路径、Host、Header、Cookie |
| 能力 | 转发快、开销小,看不到请求内容 | 能按路径路由、改写请求、TLS 终止、做缓存、限流、灰度 |
| 代表 | LVS、云厂商的 NLB、HAProxy(TCP 模式) | Nginx、HAProxy(HTTP 模式)、Envoy、云厂商的 ALB |
- 我们的 HAProxy 是七层用法:按路径
/api/console/*、/api/gateway/*分流到不同服务;web 侧按 JWT 里的用户 ID 做一致性哈希,让同一个用户尽量落到同一个副本(提高本地订阅命中,减少跨副本),但正确性不依赖它(状态都在 Redis / PG)。 - 负载均衡算法:轮询、加权轮询、最少连接、源 IP 哈希、一致性哈希。
19. 发版时怎么做到不中断用户的请求?(网络视角)
- 负载均衡要先摘掉要下线的实例,再让它退出;
- 我们的做法:实例收到 SIGTERM 后先让健康检查返回 503,等约 15 秒让代理通过连续两次健康检查失败把它摘掉,再触发 gunicorn 优雅退出,在
graceful_timeout内处理完存量请求;Swarmstart-first先起新容器; - 长连接(SSE):旧实例关闭时连接会断,前端自动重连到新实例,从 Redis Stream 重放 —— 用户看到的内容是连续的。
- 关键原则:新连接不再进来 → 存量请求处理完 → 关闭,而不是直接杀进程。
20. ★ gRPC 和 REST 的区别?
| REST(HTTP + JSON) | gRPC | |
|---|---|---|
| 协议 | HTTP/1.1 或 2 | HTTP/2 |
| 数据格式 | JSON(文本,可读) | Protobuf(二进制,小且快) |
| 接口定义 | OpenAPI(可选) | .proto 文件(强制),自动生成多语言代码 |
| 通信模式 | 请求-响应 | 一元、服务端流、客户端流、双向流 |
| 浏览器支持 | 原生 | 需要 gRPC-Web 代理 |
| 适合 | 对外 API、前后端交互 | 内部微服务之间、高性能、强类型 |
21. DNS 切换(比如换机房、换 IP)后,为什么部分用户很久都还访问旧地址?
- DNS 记录有 TTL,各级缓存(运营商 DNS、操作系统、浏览器、应用进程内)在 TTL 内都会用旧结果;有些运营商 DNS 不遵守 TTL;
- Java 应用:JVM 默认缓存 DNS 结果(开启安全管理器时是永久缓存),要配置
networkaddress.cache.ttl;连接池里的长连接本身就连着旧 IP,DNS 变了也不会重连; - 做法:切换前提前把 TTL 调小(比如从 1 小时调到 60 秒,等旧 TTL 过期后再切);旧地址保留一段时间并转发;连接池设置最大存活时间,让连接定期重建。
22. IPv6 有哪些变化?
地址 128 位(几乎用不完,不再需要 NAT);头部简化(固定 40 字节,扩展头);路由器不再分片(只能由发送端分片,依赖路径 MTU 发现);自动配置地址(SLAAC);ARP 被 NDP(邻居发现协议)替代;内置 IPsec 支持。现状:IPv4 / IPv6 双栈并存,服务端监听时注意 :: 和 0.0.0.0 的区别。
23. 什么是 TCP 的 Fast Open?
TFO:第一次连接时服务端给客户端一个 Cookie,之后客户端再连时可以在 SYN 包里就带上数据,服务端校验 Cookie 后直接处理,省掉 1 个 RTT。限制:SYN 里的数据可能被重放,只适合幂等请求;部分中间设备不支持。QUIC 的 0-RTT 是同样的思路。
24. ★ SSH 隧道的原理?-L 和 -R 有什么区别?(项目真实场景)
- SSH 隧道:在一条加密的 SSH 连接里转发其他 TCP 流量(端口转发);
- 本地转发
ssh -L 本地端口:目标:目标端口 跳板机:访问本机的端口,流量经跳板机转发到目标。例子:本地开发时ssh -L 8001:内网HAProxy:8001 jump,访问localhost:8001就等于访问测试环境的服务; - 远程转发
ssh -R 跳板机端口:目标:目标端口 跳板机:在跳板机上开一个端口,流量反向经这条 SSH 连接转发到发起方所在网络的目标。 - 我们的数据源跳板:客户的数据库在他们的内网里,我们的查询服务从外面连不进去(NAT 只允许出、不允许进)。所以在客户机器上的节点主动向外连我们的专用 sshd,用
ssh -R把客户的数据库端口挂到我们这边,我们这边连那个端口就等于连到了客户的数据库。专用 sshd 只允许端口转发(不给 shell、sftp、pty),减少攻击面。 - 这题很能体现「网络基础 + 实际落地」,推荐主动讲。
25. ★ 综合题:用户反馈「网页打不开」,你怎么一步步排查?
按「从近到远、从下到上」:
- 是一个人还是所有人?只是一个页面还是全部?(缩小范围)
- DNS:
nslookup/dig域名,解析结果对不对、是否被劫持; - 网络连通:
ping(可能被禁)、telnet/nc -zv host 443看端口通不通;mtr看哪一跳丢包; - TLS:
curl -v/openssl s_client -connect host:443看证书是否过期、域名是否匹配、链是否完整; - HTTP:
curl -i看状态码:502(后端挂了 / 网关连不上后端)、504(后端超时)、503(限流或实例被摘除)、4xx(客户端请求问题); - 服务端:负载均衡健康检查、应用日志、资源(CPU、内存、连接数、fd)、依赖(数据库、Redis、模型服务);
- 浏览器侧:开发者工具 Network 面板看是哪个请求失败、CORS 报错、混合内容(HTTPS 页面里加载 HTTP 资源)、缓存了旧资源。 答这题的关键是有顺序、有工具、有判断依据。
原有提问清单
HTTP 协议了解吗?
TCP/IP 协议了解吗?
三次握手
四次挥手
UDP 协议和 TCP 协议
六、网络与 HTTP
入门
- 常用 HTTP 方法和语义? GET(读,幂等)、POST(创建 / 非幂等操作)、PUT(整体替换,幂等)、PATCH(部分修改)、DELETE(幂等)。
- 常用状态码? 200、201(创建成功)、204(无内容)、301 / 302(重定向)、304(未修改,协商缓存命中)、400、401(未认证)、403(无权限)、404、409(冲突)、422(参数校验失败)、429(限流)、500、502(网关收到上游的无效响应)、503(服务不可用)、504(网关等上游超时)。
- GET 和 POST 区别? 语义不同(读 vs 写);GET 参数在 URL(有长度限制、会被记录在日志和历史里),POST 在请求体;GET 可缓存、幂等。
中等
- TCP 三次握手、四次挥手? 握手:SYN → SYN+ACK → ACK(三次是为了双方都确认对方的收发能力,防止历史连接请求)。挥手:FIN → ACK →(服务端发完数据)FIN → ACK,因为 TCP 是全双工,两个方向分别关闭。
- TIME_WAIT 是什么?为什么大量出现? 主动关闭方要等 2MSL 才真正释放,保证最后的 ACK 能到达、防止旧报文干扰新连接。短连接高并发时主动关闭方(通常是客户端或代理)会堆积大量 TIME_WAIT,占用端口。解法:用长连接 / 连接池;
tcp_tw_reuse。 - HTTP/1.1、HTTP/2、HTTP/3?
- 1.1:长连接(keep-alive),但一个连接上请求串行(队头阻塞),浏览器对同一域名开 6 个连接;
- 2:二进制分帧、多路复用(一个连接并发多个请求)、头部压缩(HPACK)、服务端推送;但 TCP 层丢包仍会阻塞所有流;
- 3:基于 QUIC(UDP),解决 TCP 层的队头阻塞,连接建立更快(0-RTT),网络切换时连接不断。
- HTTPS 握手过程? TLS 1.2:客户端发支持的算法 → 服务端发证书 → 客户端验证证书、协商出会话密钥(ECDHE 密钥交换)→ 之后用对称加密通信。TLS 1.3 简化为 1-RTT。证书由 CA 签名,客户端用内置的根证书验证证书链。
- 长连接、短轮询、长轮询、SSE、WebSocket?
方向 特点 适合 短轮询 客户端拉 简单,浪费 低频状态查询 长轮询 客户端拉 服务端有数据或超时才返回 兼容性要求高的推送 SSE 服务端推(单向) 基于 HTTP、自动重连、文本 LLM 流式输出、通知 WebSocket 双向 独立协议、全双工 聊天、协同编辑、实时游戏 我们对话用 SSE;浏览器扩展、客户机器上的远程节点和后端之间要双向收发指令,用 WebSocket(flask-sock)做中继。 - SSE 的格式?
Content-Type: text/event-stream,每个事件是若干行event: xxx/data: xxx/id: xxx,以空行结束。浏览器EventSource断线自动重连并带上Last-Event-ID。经过 Nginx 要关闭缓冲(proxy_buffering off或X-Accel-Buffering: no)。
高难
- 从浏览器到我们后端,一个请求经过了哪些环节? DNS → TCP / TLS → Caddy / HAProxy(TLS 终止、按路径分到 web / console / gateway 三个服务、web 侧按用户 ID 做一致性哈希粘性)→ gunicorn master → gevent worker → Flask 路由 → 鉴权 → 业务 → PG / Redis / 模型。能讲清这条链路就能回答大部分「排查慢请求」的问题。
- 服务停机时怎么做到不丢请求?(项目真实做法)收到 SIGTERM 后先让健康检查接口返回 503(等约 15 秒,让前面的代理通过健康检查发现它、把流量摘掉),然后才触发 gunicorn 的优雅退出,在
graceful_timeout内等正在处理的请求结束。
八、计算机网络基础
这里是浓缩版,完整的三档 75 题见 11-计算机网络.md。
1. ★ OSI 七层和 TCP/IP 四层?
| OSI 七层 | TCP/IP 四层 | 协议举例 |
|---|---|---|
| 应用层、表示层、会话层 | 应用层 | HTTP、HTTPS、DNS、SSH、FTP、SMTP、WebSocket |
| 传输层 | 传输层 | TCP、UDP |
| 网络层 | 网络层 | IP、ICMP(ping)、ARP(有的归到链路层) |
| 数据链路层、物理层 | 网络接口层 | 以太网、Wi-Fi |
2. ★ TCP 和 UDP 的区别?
| TCP | UDP | |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠:不丢、不重、有序 | 不保证 |
| 传输方式 | 字节流(没有消息边界,会「粘包」) | 数据报(有边界) |
| 速度 / 开销 | 慢一些,头部 20 字节起 | 快,头部 8 字节 |
| 拥塞控制 | 有 | 无 |
| 应用 | HTTP(1.1 / 2)、数据库连接、SSH | DNS、视频直播、游戏、HTTP/3(QUIC 在 UDP 上自己实现可靠性) |
3. ★ 三次握手的过程?为什么是三次不是两次?
- 客户端发
SYN(seq = x),进入 SYN_SENT; - 服务端回
SYN + ACK(seq = y,ack = x + 1),进入 SYN_RCVD; - 客户端发
ACK(ack = y + 1),双方进入 ESTABLISHED。
为什么三次:
- 双方都要确认「自己能发、对方能收」以及对方的初始序列号;
- 防止历史连接:网络里滞留的旧 SYN 到达服务端时,如果两次握手服务端就直接建立连接了,浪费资源;三次握手时客户端会发现这不是自己想要的连接,回 RST。
- SYN 洪水攻击:攻击者只发 SYN 不回 ACK,把服务端的半连接队列占满。防护:SYN Cookie。
4. ★ 四次挥手的过程?为什么是四次?
- 主动方发
FIN,进入 FIN_WAIT_1; - 被动方回
ACK,进入 CLOSE_WAIT(主动方进入 FIN_WAIT_2)—— 此时被动方还可以继续发数据; - 被动方发完数据后发
FIN,进入 LAST_ACK; - 主动方回
ACK,进入 TIME_WAIT,等 2MSL 后关闭。
为什么四次:TCP 是全双工的,两个方向要分别关闭;被动方收到 FIN 时可能还有数据没发完,所以 ACK 和 FIN 分两次发。
- TIME_WAIT 为什么等 2MSL:保证最后一个 ACK 能到达(丢了对方会重发 FIN);让本连接的旧报文在网络里消失,不干扰后续用同样端口的新连接。
- 大量 CLOSE_WAIT:通常是程序没有调用 close(连接泄漏),是代码 bug。
5. ★ TCP 怎么保证可靠传输?
- 序列号 + 确认应答:每个字节有序号,接收方回 ACK;
- 超时重传:没收到 ACK 就重发;快速重传:连续收到 3 个重复 ACK 就立刻重传;
- 校验和:检查数据损坏;
- 流量控制:接收方通过滑动窗口告诉发送方自己还能收多少,防止把接收方撑爆;
- 拥塞控制:防止把网络撑爆。四个算法:慢启动(拥塞窗口从小开始指数增长)→ 到阈值后拥塞避免(线性增长)→ 丢包时快重传 + 快恢复(窗口减半而不是归零)。
- 流量控制和拥塞控制的区别:前者照顾接收方,后者照顾整个网络。
6. ★ 什么是粘包?怎么解决?
TCP 是字节流,没有消息边界,发送方发的两条消息接收方可能一次读到(粘在一起),也可能一条分两次读到。解决:应用层自己定义边界 —— 固定长度、特殊分隔符(SSE 用空行 \n\n 分隔事件)、消息头带长度(最常用)。UDP 没有这个问题。
7. ★ DNS 解析过程?
浏览器缓存 → 操作系统缓存(hosts 文件)→ 本地 DNS 服务器(运营商)→ 根域名服务器 → 顶级域名服务器(.com)→ 权威域名服务器 → 返回 IP,逐级缓存。DNS 主要用 UDP(53 端口),响应太大或区域传输时用 TCP。
8. ★ HTTP 请求和响应的结构?
请求:请求行(方法 URL 版本)+ 请求头 + 空行 + 请求体。响应:状态行(版本 状态码 原因)+ 响应头 + 空行 + 响应体。常用头:Host、Content-Type、Content-Length、Authorization、Cookie / Set-Cookie、Cache-Control、User-Agent。
9. ★ HTTP 缓存:强缓存和协商缓存?
- 强缓存:
Cache-Control: max-age=3600(或老的Expires),有效期内浏览器直接用本地缓存,不发请求; - 协商缓存:过期后带上
If-None-Match: <ETag>或If-Modified-Since问服务端,没变就返回 304(不带内容),变了返回 200 和新内容。
10. ★ Cookie 和 Session 的区别?
Cookie 存在浏览器,每次请求自动带上,有大小限制(约 4KB);Session 存在服务端,浏览器的 Cookie 里只存 session id。多副本部署时 Session 要放 Redis 共享(或者用 JWT 这种无状态令牌)。
11. ★ HTTP 和 HTTPS 的区别?
HTTPS = HTTP + TLS:加密(防窃听)、完整性校验(防篡改)、证书身份验证(防冒充)。默认端口 80 / 443。TLS 握手用非对称加密协商出会话密钥,之后用对称加密传输数据(对称加密快)。
12. 对称加密和非对称加密?
对称:加解密同一个密钥(AES),快,问题是密钥怎么安全地交给对方;非对称:公钥加密、私钥解密(RSA、ECC),慢,用来交换对称密钥和做数字签名。
13. ★ 从输入 URL 到页面显示
DNS 解析 → TCP 三次握手 → TLS 握手 → 发送 HTTP 请求 → 服务端处理(负载均衡 → 应用 → 数据库)→ 返回响应 → 浏览器解析 HTML / CSS / JS、渲染 → 连接复用或四次挥手。(每一步都可以展开,面试官会挑一个追问)
14. ping 用的什么协议?
ICMP,网络层协议,没有端口。
