文章目录
- [计算机网络面试完全指南(Go 校招向)](#计算机网络面试完全指南(Go 校招向))
-
- 一、网络模型
-
- [OSI 七层 vs TCP/IP 四层](#OSI 七层 vs TCP/IP 四层)
- [二、TCP 协议(面试重中之重)](#二、TCP 协议(面试重中之重))
-
- [TCP 报文头部](#TCP 报文头部)
- 三次握手(建立连接)
- 四次挥手(断开连接)
- 半连接队列与全连接队列(面试高频)
- [TCP Keep-Alive(连接保活)](#TCP Keep-Alive(连接保活))
- 可靠传输机制
- 流量控制(接收方控制发送方速度)
- 拥塞控制(防止网络过载)
- [TCP 粘包与拆包](#TCP 粘包与拆包)
- [三、UDP 协议](#三、UDP 协议)
-
- [UDP 报文头部](#UDP 报文头部)
- [TCP vs UDP](#TCP vs UDP)
- [四、HTTP 协议](#四、HTTP 协议)
-
- [HTTP/1.0 → HTTP/1.1 → HTTP/2 → HTTP/3](#HTTP/1.0 → HTTP/1.1 → HTTP/2 → HTTP/3)
-
- [HTTP/1.0 问题](#HTTP/1.0 问题)
- [HTTP/1.1 改进](#HTTP/1.1 改进)
- [HTTP/2 改进](#HTTP/2 改进)
- [HTTP/3 改进](#HTTP/3 改进)
- [HTTP 请求方法](#HTTP 请求方法)
- [HTTP 状态码](#HTTP 状态码)
- [HTTP 缓存机制](#HTTP 缓存机制)
- [HTTPS 与 TLS 握手](#HTTPS 与 TLS 握手)
- 五、DNS
-
- [DNS 解析过程](#DNS 解析过程)
- 六、WebSocket
-
- [与 HTTP 的区别](#与 HTTP 的区别)
- 七、代理与负载均衡
-
- [正向代理 vs 反向代理(面试高频)](#正向代理 vs 反向代理(面试高频))
- 负载均衡算法
- [八、IP 与路由](#八、IP 与路由)
-
- [IP 地址与子网](#IP 地址与子网)
- ARP(地址解析协议)
- NAT(网络地址转换)
- [九、CORS 跨域(后端必懂)](#九、CORS 跨域(后端必懂))
-
- 什么是跨域
- [简单请求 vs 预检请求](#简单请求 vs 预检请求)
- [Go 中处理 CORS(Gin 示例)](#Go 中处理 CORS(Gin 示例))
- 十、常见网络攻击与防御
-
- DDoS(分布式拒绝服务)
- [SQL 注入](#SQL 注入)
- XSS(跨站脚本)
- CSRF(跨站请求伪造)
- [九、网络编程(Go 视角)](#九、网络编程(Go 视角))
-
- [TCP 服务器基础](#TCP 服务器基础)
- 设置超时
- [HTTP 客户端最佳实践](#HTTP 客户端最佳实践)
- 十、面试高频问题速答
- 十一、一张图记住核心
计算机网络面试完全指南(Go 校招向)
一、网络模型
OSI 七层 vs TCP/IP 四层
OSI 七层模型 TCP/IP 四层模型 代表协议
┌─────────────┐ ┌──────────────┐
│ 应用层 │ │ │ HTTP/HTTPS/FTP/DNS
│ 表示层 │ ──▶ │ 应用层 │ SMTP/WebSocket/gRPC
│ 会话层 │ │ │
├─────────────┤ ├──────────────┤
│ 传输层 │ ──▶ │ 传输层 │ TCP / UDP
├─────────────┤ ├──────────────┤
│ 网络层 │ ──▶ │ 网络层 │ IP / ICMP / ARP
├─────────────┤ ├──────────────┤
│ 数据链路层 │ │ │ Ethernet / WiFi
│ 物理层 │ ──▶ │ 网络接口层 │ 光纤 / 双绞线
└─────────────┘ └──────────────┘
每层的核心职责:
| 层 | 职责 | 关键词 |
|---|---|---|
| 应用层 | 面向用户的协议,定义数据格式 | HTTP、DNS、协议 |
| 传输层 | 端到端通信,区分进程(端口号) | TCP/UDP、端口、可靠传输 |
| 网络层 | 主机到主机,跨网络路由 | IP 地址、路由、TTL |
| 数据链路层 | 同一局域网内节点到节点 | MAC 地址、以太网帧 |
| 物理层 | 比特流的物理传输 | 光纤、电信号 |
面试答法:每层只关心本层的事,通过封装/解封装向上/向下交互。发送数据时每层加上本层头部(封装),接收时逐层去掉头部(解封装)。分层的好处是各层独立演化,比如 HTTP/2 替换 HTTP/1.1 不影响 TCP 层。
二、TCP 协议(面试重中之重)
TCP 报文头部
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─────────────────────────┬─────────────────────────────────────┤
│ 源端口(16位) │ 目标端口(16位) │
├─────────────────────────┴─────────────────────────────────────┤
│ 序列号(Seq,32位) │
├───────────────────────────────────────────────────────────────┤
│ 确认号(Ack,32位) │
├──────────┬─────────────┬────┬────┬────┬────┬────┬────┬───────┤
│ 数据偏移 │ 保留位 │URG │ACK │PSH │RST │SYN │FIN │ 窗口 │
├──────────┴─────────────┴────┴────┴────┴────┴────┴────┴───────┤
│ 校验和 │ 紧急指针 │
└──────────────────────────────────────────────────────────────┘
重要标志位:
SYN:建立连接请求
ACK:确认(Ack号有效)
FIN:请求断开连接
RST:强制重置连接(连接异常时)
PSH:提示接收方立即上交数据给应用层
重要字段:
序列号(Seq):本次发送的第一个字节的编号
确认号(Ack):期望收到对方下一个字节的编号(已收到 Ack-1)
窗口大小:告诉对方自己的接收缓冲区还有多少空间(流量控制)
三次握手(建立连接)
Client Server
│ │
│──── SYN(seq=x) ────────────────▶│ Server: SYN_RCVD
│ │
│◀─── SYN+ACK(seq=y, ack=x+1) ────│
│ │
│──── ACK(ack=y+1) ──────────────▶│ Server: ESTABLISHED
│ │
ESTABLISHED ESTABLISHED
为什么是三次,不是两次或四次:
- 两次不够:服务端发出 SYN+ACK 后不知道客户端是否收到(单向确认),无法确认双方的发送和接收能力都正常;且历史失效的连接请求可能让服务端白白分配资源
- 四次多余:服务端的 SYN 和 ACK 可以合并发送,没必要拆开
- 三次刚好:双方都能确认自己的发和收是正常的
SYN Flood 攻击:攻击者发大量 SYN 包但不回 ACK,导致服务端半连接队列(SYN Queue)满,无法接受新连接。
- 防御:SYN Cookie(不占用半连接队列资源,用加密 Cookie 验证合法性)
四次挥手(断开连接)
Client(主动关闭) Server(被动关闭)
│ │
│──── FIN(seq=u) ────────────────▶│ Server: CLOSE_WAIT
│ │(服务端可能还有数据要发)
│◀─── ACK(ack=u+1) ───────────────│
│ (等待服务端数据发完)
│◀─── FIN(seq=v) ─────────────────│ Server: LAST_ACK
│ │
│──── ACK(ack=v+1) ──────────────▶│ Server: CLOSED
│ │
TIME_WAIT(等 2MSL)
│
CLOSED
为什么四次(不能合并成三次) :
TCP 是全双工的,关闭是单向的。客户端发 FIN 只表示"我不再发数据了",但服务端可能还有数据要发给客户端,所以服务端的 ACK 和 FIN 不能合并(中间有时间差)。
TIME_WAIT 为什么等 2MSL(最大报文存活时间):
- 确保最后一个 ACK 能到达服务端。若服务端没收到,会重发 FIN,客户端需要能接收并重发 ACK
- 让本次连接产生的所有报文在网络中消失,防止旧报文影响同四元组的新连接
TIME_WAIT 过多的影响与解决:
bash
# 大量 TIME_WAIT 会耗尽本地端口(默认 28232 个)
ss -s | grep TIME-WAIT
# 解决(/etc/sysctl.conf):
net.ipv4.tcp_tw_reuse = 1 # 允许复用 TIME_WAIT 的端口(客户端侧)
net.ipv4.tcp_fin_timeout = 30 # 缩短 FIN_WAIT2 等待时间
半连接队列与全连接队列(面试高频)
服务端 listen() 后,内核维护两个队列:
客户端 SYN
│
▼
半连接队列(SYN Queue)
存放已收到 SYN、等待完成三次握手的连接
服务端发出 SYN+ACK,等待客户端 ACK
│
│ 收到客户端 ACK,三次握手完成
▼
全连接队列(Accept Queue)
存放已完成三次握手、等待 accept() 取走的连接
│
│ 应用层调用 accept()
▼
连接交给应用程序处理
队列满了会怎样:
- 半连接队列满:丢弃新 SYN(或开启 SYN Cookie 绕过)
- 全连接队列满:丢弃 ACK,客户端以为连接建立,服务端实际丢弃
相关内核参数:
bash
net.ipv4.tcp_max_syn_backlog = 1024 # 半连接队列大小
net.core.somaxconn = 128 # 全连接队列上限
# listen(fd, backlog) 中的 backlog 参数也影响全连接队列大小
# 实际大小 = min(backlog, somaxconn)
面试答法 :高并发服务需要同时调大
tcp_max_syn_backlog和somaxconn,并在代码的listen()调用中传入足够大的 backlog,否则连接会被静默丢弃,表现为客户端连接超时。
TCP Keep-Alive(连接保活)
HTTP Keep-Alive 是应用层的连接复用,TCP Keep-Alive 是传输层的连接探活机制,两者不同:
TCP 连接建立后,双方长时间无数据交换
→ 中间路由器/防火墙可能悄悄关闭连接(NAT 超时)
→ 但两端不知道,以为连接还在
TCP Keep-Alive:连接空闲一段时间后,自动发探测包确认对端仍存活
相关参数:
tcp_keepalive_time = 7200s # 空闲多久开始探测(默认 2 小时)
tcp_keepalive_intvl = 75s # 探测间隔
tcp_keepalive_probes = 9 # 探测失败多少次认定连接断开
Go 中启用 TCP Keep-Alive:
go
// net.Dial 默认已开启 Keep-Alive,间隔 15s
conn, _ := net.Dial("tcp", "host:port")
// 手动控制
tcpConn := conn.(*net.TCPConn)
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(30 * time.Second)
可靠传输机制
确认与重传(ARQ)
停止等待 ARQ:发一个包,等确认,再发下一个(效率低)
连续 ARQ(Go-Back-N):
发送窗口内的包都发出去,等确认
若包3丢失 → 重传包3及其后的所有包(Go-Back-N)
选择重传(Selective Repeat,TCP使用):
发送窗口内的包都发出去
若包3丢失 → 只重传包3,不影响已收到的包4/5/6
超时重传:发出数据包后启动定时器,超时未收到 ACK 则重传。
- RTO(重传超时时间)动态计算:
RTO = SRTT + 4 × RTTVAR(平滑往返时间 + 4倍时间波动) - 每次重传后 RTO 翻倍(指数退避),避免网络拥塞时雪上加霜
快速重传:收到 3 个重复 ACK 就立即重传,不等超时(更快)。
发送: 1 2 3 4 5
收到: ACK1 ACK2 ACK2 ACK2 ACK2 ← 收到 3 个重复 ACK2,说明 3 丢了
→ 立即重传 3,不等 RTO 超时
流量控制(接收方控制发送方速度)
滑动窗口:接收方在 ACK 报文的窗口字段告知发送方自己的缓冲区剩余空间:
接收方缓冲区: [已处理][ 已收到未处理 ][ 空闲 ]
↑
rwnd = 空闲大小,放入 ACK 的 window 字段
发送方最多发送 min(cwnd, rwnd) 的数据(cwnd 是拥塞窗口)
当 rwnd = 0:发送方停止发送,启动零窗口探测定时器
定期发 1 字节探测包,等接收方窗口恢复
拥塞控制(防止网络过载)
四个算法:
cwnd(拥塞窗口)随时间变化:
cwnd
│ ssthresh
│ ╱───────────────────────
│ ╱
│ ╱ ← 慢启动(指数增长)
│ ╱─────────────────────────── ← 拥塞避免(线性增长)
│ ╱
│ ──╱
└──────────────────────────────── 时间
↑ ↑
开始 触发拥塞
慢启动(Slow Start):
- 初始 cwnd = 1 MSS(最大报文段)
- 每收到一个 ACK,cwnd += 1(指数增长:1→2→4→8...)
- 到达 ssthresh(慢启动阈值)后转入拥塞避免
拥塞避免(Congestion Avoidance):
- 每个 RTT,cwnd += 1 MSS(线性增长,更保守)
拥塞发生时:
- 超时触发(严重):ssthresh = cwnd/2,cwnd = 1,重新慢启动
- 3个重复ACK(较轻,快速重传后):ssthresh = cwnd/2,cwnd = ssthresh,进入拥塞避免(TCP Reno)
TCP BBR(现代拥塞控制) :
不依赖丢包信号,而是测量带宽和 RTT,主动探测网络最优发送速率,Google 提出,Linux 4.9+ 支持,延迟更低,吞吐更高。
面试答法 :拥塞控制和流量控制都是控制发送速率,但目的不同。流量控制防止发送方压垮接收方 缓冲区,由接收方通过 window 字段控制。拥塞控制防止发送方压垮网络,由发送方根据网络反馈(丢包/时延)自主调节 cwnd。实际发送量 = min(rwnd, cwnd)。
TCP 粘包与拆包
粘包 :多个小数据包被合并成一个大数据包发送(Nagle 算法)
拆包:一个大数据包被拆成多个小包
应用层发送: [Hello][World]
实际收到: [HelloWorld](粘包)或 [Hel][loWorld](拆包)
解决方案:
1. 固定长度消息
2. 消息头包含长度字段(最常用):[4字节长度][消息体]
3. 特殊分隔符(如 \r\n,适合文本协议)
4. 使用有边界的协议(HTTP/gRPC 已处理好)
三、UDP 协议
UDP 报文头部
┌────────────────────┬────────────────────┐
│ 源端口(16位) │ 目标端口(16位) │
├────────────────────┼────────────────────┤
│ 长度(16位) │ 校验和(16位) │
└────────────────────┴────────────────────┘
│ 数据 │
└──────────────────────────────────────────┘
仅 8 字节头部,无连接建立、无确认、无重传、无流控、无拥塞控制。
TCP vs UDP
| 对比项 | TCP | UDP |
|---|---|---|
| 连接 | 需要三次握手 | 无连接,直接发 |
| 可靠性 | 确认+重传,保证送达 | 不保证 |
| 顺序 | 保证有序 | 不保证 |
| 流量控制 | 有(滑动窗口) | 无 |
| 拥塞控制 | 有 | 无 |
| 头部大小 | 20 字节起 | 8 字节 |
| 传输方式 | 字节流(无消息边界) | 数据报(有消息边界) |
| 速度 | 较慢 | 快 |
| 适用场景 | HTTP、数据库、文件传输 | DNS、视频直播、游戏、QUIC |
UDP 适用场景分析:
- 实时性 > 可靠性:视频通话(丢一帧画面,无需重传,重传了也来不及播放)
- 广播/多播:UDP 天然支持一对多
- 简单请求响应:DNS 查询(一问一答,丢了重问就行)
- QUIC 协议:基于 UDP 自己实现可靠传输,规避 TCP 的队头阻塞(HTTP/3 的基础)
四、HTTP 协议
HTTP/1.0 → HTTP/1.1 → HTTP/2 → HTTP/3
HTTP/1.0 问题
- 短连接:每个请求新建 TCP 连接,响应后断开,三次握手开销大
- 无 Host 头:一个 IP 只能部署一个网站
HTTP/1.1 改进
- 持久连接(Keep-Alive):默认复用 TCP 连接,减少握手次数
- 管道化(Pipelining):可以连续发多个请求,但响应必须按序返回
- 队头阻塞(Head-of-Line Blocking):前一个响应没回来,后面的请求被阻塞
HTTP/2 改进
HTTP/1.1:
请求1 ──→ 响应1 ──→ 请求2 ──→ 响应2(串行,或管道化但有队头阻塞)
HTTP/2(多路复用):
请求1 ──┐
请求2 ──┼──→ 单 TCP 连接(二进制分帧,Stream 并发)──→ 响应1
请求3 ──┘ 响应2
响应3
HTTP/2 核心特性:
- 二进制分帧:HTTP 报文被拆分为帧(Frame),帧有 StreamID,可以乱序传输
- 多路复用:同一 TCP 连接并发多个请求/响应,彻底解决应用层队头阻塞
- 头部压缩(HPACK):维护头部字典,只传差量,减少重复头部的开销
- 服务器推送:服务端可以主动推送客户端可能需要的资源(如 CSS/JS)
- 流优先级:可以设置 Stream 的优先级
HTTP/2 的问题:TCP 层的队头阻塞依然存在(丢了一个 TCP 包,所有 Stream 都要等待重传)
HTTP/3 改进
- 基于 QUIC(Quick UDP Internet Connections) 协议
- QUIC 跑在 UDP 上,自己实现可靠传输
- Stream 级别的独立可靠:一个 Stream 的丢包不影响其他 Stream
- 0-RTT / 1-RTT 握手:相比 TCP+TLS 的 3-RTT,大幅减少握手延迟
- 连接迁移:换 IP(切换 WiFi/4G)连接不断(用 ConnectionID 标识连接而非四元组)
HTTP 请求方法
| 方法 | 语义 | 幂等 | 有 Body |
|---|---|---|---|
| GET | 获取资源 | 是 | 否 |
| POST | 创建资源/提交数据 | 否 | 是 |
| PUT | 完整替换资源 | 是 | 是 |
| PATCH | 部分更新资源 | 否 | 是 |
| DELETE | 删除资源 | 是 | 否 |
| HEAD | 获取响应头(不含 Body) | 是 | 否 |
| OPTIONS | 查询支持的方法(CORS 预检) | 是 | 否 |
幂等:多次执行结果相同。PUT 幂等(多次 PUT 同一内容结果一样),POST 不幂等(多次 POST 可能创建多条记录)。
HTTP 状态码
1xx:信息性(100 Continue,101 Switching Protocols→WebSocket升级)
2xx:成功
200 OK
201 Created(POST 创建成功)
204 No Content(成功但无响应体,如 DELETE)
3xx:重定向
301 Moved Permanently(永久重定向,浏览器缓存)
302 Found(临时重定向)
304 Not Modified(缓存有效,不返回内容)
4xx:客户端错误
400 Bad Request(请求格式错误)
401 Unauthorized(未认证)
403 Forbidden(已认证但无权限)
404 Not Found
405 Method Not Allowed
429 Too Many Requests(限流)
5xx:服务端错误
500 Internal Server Error
502 Bad Gateway(网关收到上游无效响应)
503 Service Unavailable(服务不可用/过载)
504 Gateway Timeout(上游超时)
HTTP 缓存机制
强缓存(不请求服务器):
Cache-Control: max-age=3600 ← 1小时内直接用缓存(优先级更高)
Expires: Wed, 01 Jan 2025 ... ← 绝对过期时间(受客户端时间影响)
协商缓存(请求服务器确认):
ETag / If-None-Match ← 资源唯一标识符(精确)
Last-Modified / If-Modified-Since ← 资源最后修改时间(精度 1s)
客户端请求: If-None-Match: "abc123"
服务端响应:
├─ 内容未变 → 304 Not Modified(不返回 Body,节省带宽)
└─ 内容已变 → 200 OK + 新内容 + 新 ETag
优先级:Cache-Control > Expires > ETag > Last-Modified
HTTPS 与 TLS 握手
HTTP = HTTP + TCP
HTTPS = HTTP + TLS + TCP
TLS 1.2 握手(4-RTT):
Client Server
│──── ClientHello(支持的加密套件,随机数) ─▶│
│◀─── ServerHello(选定套件,随机数,证书) ───│
│ 验证证书(CA 签名链验证) │
│──── ClientKeyExchange(预主密钥,公钥加密)─▶│
│──── ChangeCipherSpec ────────────────────▶│
│◀─── ChangeCipherSpec ────────────────────│
│ 双方用三个随机数生成会话密钥(对称加密) │
│─── 加密的 HTTP 请求 ────────────────────▶│
TLS 1.3 优化(1-RTT,更快):
合并握手消息,减少往返次数
移除不安全的加密算法(RSA 密钥交换)
支持 0-RTT(Session 恢复时,第一个请求直接带数据)
证书验证流程:
服务端证书 → 中间CA证书 → 根CA证书(浏览器/OS内置信任)
验证链:
1. 证书未过期
2. 域名匹配(CN 或 SAN)
3. 证书签名有效(用上级 CA 的公钥验证)
4. 未被吊销(OCSP/CRL 查询)
对称加密 vs 非对称加密:
| 对比 | 对称加密 | 非对称加密 |
|---|---|---|
| 密钥 | 同一密钥加解密 | 公钥加密,私钥解密 |
| 速度 | 快(AES) | 慢(RSA,约慢 1000 倍) |
| 用途 | 大量数据加密 | 密钥交换、数字签名 |
| TLS 中的角色 | 传输阶段加密数据 | 握手阶段协商会话密钥 |
面试答法:HTTPS 先用非对称加密(RSA/ECDHE)安全地交换会话密钥,然后用对称加密(AES)加密实际数据。这样兼顾了安全性(非对称保证密钥交换安全)和性能(对称加密速度快)。
五、DNS
DNS 解析过程
浏览器输入 www.example.com
1. 浏览器 DNS 缓存 → 命中则直接用
2. 操作系统 DNS 缓存(/etc/hosts)→ 命中则直接用
3. 本地 DNS 服务器(运营商/公司 DNS,如 8.8.8.8)
│
│(本地 DNS 缓存未命中,开始递归查询)
▼
4. 根域名服务器(.)→ 返回 .com 顶级域名服务器地址
▼
5. .com 顶级域名服务器 → 返回 example.com 权威域名服务器地址
▼
6. example.com 权威域名服务器 → 返回 www.example.com 的 IP
▼
7. 本地 DNS 服务器缓存结果,返回给客户端
DNS 记录类型:
| 类型 | 说明 | 示例 |
|---|---|---|
| A | 域名 → IPv4 地址 | www → 1.2.3.4 |
| AAAA | 域名 → IPv6 地址 | www → ::1 |
| CNAME | 域名 → 另一个域名 | www → lb.example.com |
| MX | 邮件服务器 | example.com → mail.example.com |
| TXT | 文本记录(域名验证/SPF) | --- |
| NS | 该域名的权威 DNS 服务器 | --- |
DNS 使用 UDP 的原因:DNS 查询是简单的一问一答,报文小(通常 < 512 字节),UDP 无需握手,延迟更低。报文超大时(如 DNSSEC)降级使用 TCP。
六、WebSocket
与 HTTP 的区别
HTTP:客户端发请求,服务端响应,单向,连接后关闭(或 Keep-Alive 复用但仍是请求响应模式)
WebSocket:全双工,服务端可以主动推送,长连接
升级过程(HTTP Upgrade):
Client:
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Server:
HTTP/1.1 101 Switching Protocols ← 101 表示协议升级成功
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
之后:TCP 连接不断,WebSocket 帧双向传输
适用场景:IM 聊天、实时通知、在线游戏、股票行情、协同编辑
WebSocket vs SSE vs 轮询:
| 方案 | 方向 | 适用场景 |
|---|---|---|
| 短轮询 | 单向(客户端拉) | 简单,延迟高,浪费资源 |
| 长轮询 | 单向(客户端拉) | 延迟较低,每次响应后重连 |
| SSE | 单向(服务端推) | 消息推送、日志流 |
| WebSocket | 双向 | 需要双向通信的场景 |
七、代理与负载均衡
正向代理 vs 反向代理(面试高频)
正向代理(Forward Proxy):代理客户端
客户端 → [正向代理] → 服务端
服务端不知道真实客户端是谁
场景:VPN、访问被限制的网站、企业内网上网
反向代理(Reverse Proxy):代理服务端
客户端 → [反向代理] → 多个后端服务
客户端不知道真实服务端是谁
场景:Nginx 反向代理、API 网关、CDN、负载均衡
一句话记住:
- 正向代理:客户端知道代理的存在,代理帮客户端访问外部
- 反向代理:客户端不知道后端的存在,代理帮后端承接请求
负载均衡算法
| 算法 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 轮询(Round Robin) | 依次分配 | 简单均匀 | 不考虑服务器差异 | 服务器性能相近 |
| 加权轮询 | 按权重比例分配 | 可区分性能差异 | 不考虑实时负载 | 服务器性能不同 |
| 最少连接 | 分配给当前连接数最少的 | 动态感知负载 | 需维护连接数状态 | 长连接场景 |
| IP Hash | 对客户端 IP 取模 | 同一客户端打到同一服务器 | 负载不均衡 | 有状态服务(Session) |
| 一致性哈希 | 哈希环,节点增减影响范围小 | 扩缩容影响小 | 实现复杂 | 分布式缓存 |
| 随机 | 随机选一个 | 简单 | 可能不均 | 无状态服务 |
一致性哈希详解(面试常问):
普通 Hash:hash(key) % N
问题:N 变化时(增删节点),几乎所有 key 都要重新映射
一致性哈希:
1. 将 [0, 2^32) 排列成哈希环
2. 节点映射到环上某位置(多个虚拟节点分散分布)
3. key 顺时针找到第一个节点
节点增删时,只影响该节点到前一个节点之间的 key
影响范围:1/N(N 为节点数)
虚拟节点:解决节点分布不均的问题(每个真实节点映射多个虚拟节点)
八、IP 与路由
IP 地址与子网
IPv4:32 位,点分十进制,如 192.168.1.100
IPv6:128 位,冒号十六进制,如 fe80::1
子网掩码:区分网络部分和主机部分
192.168.1.100 / 24
网络地址:192.168.1.0(主机位全0)
广播地址:192.168.1.255(主机位全1)
可用主机:192.168.1.1 ~ 192.168.1.254(254个)
私有 IP 地址(不路由到公网):
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
ARP(地址解析协议)
问题:知道 IP 地址,不知道 MAC 地址(局域网通信需要 MAC)
流程:
主机A(192.168.1.1)想发数据给主机B(192.168.1.2)
↓
广播 ARP 请求:"谁是 192.168.1.2?告诉我你的 MAC"
↓
主机B回复(单播):"我是 192.168.1.2,MAC 是 xx:xx:xx:xx:xx:xx"
↓
主机A缓存 IP→MAC 映射(ARP 缓存),直接发送
NAT(网络地址转换)
家庭网络(私有 IP) 路由器(NAT) 互联网(公有 IP)
192.168.1.2:54321 ──────▶ 替换源地址为公有IP ──────▶ 1.2.3.4:54321
维护 NAT 映射表
192.168.1.2:54321 ◀────── 恢复目标地址 ◀─────────── 1.2.3.4:54321
NAT 表项:内部IP:端口 ↔ 外部IP:端口
九、CORS 跨域(后端必懂)
什么是跨域
浏览器的同源策略(Same-Origin Policy):协议 + 域名 + 端口三者全同才算同源,不同源的 JS 脚本不能发送 AJAX 请求。
http://a.com:80 访问 http://a.com:80 ✅ 同源
http://a.com:80 访问 https://a.com:80 ❌ 跨域(协议不同)
http://a.com:80 访问 http://b.com:80 ❌ 跨域(域名不同)
http://a.com:80 访问 http://a.com:8080 ❌ 跨域(端口不同)
简单请求 vs 预检请求
简单请求(直接发送,无预检):
- 方法:GET / POST / HEAD
- 头部:只含标准头(Content-Type 为 form / text / json 之一)
预检请求(Preflight) :不满足简单请求条件时,浏览器先发 OPTIONS 询问服务端是否允许:
浏览器:
OPTIONS /api/user HTTP/1.1
Origin: http://frontend.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization
服务端响应(允许跨域):
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://frontend.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Authorization
Access-Control-Max-Age: 86400 ← 预检结果缓存 1 天
之后浏览器才发真正的 DELETE 请求
Go 中处理 CORS(Gin 示例)
go
// 方式1:手动中间件
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*") // 或指定域名
c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204) // 预检请求直接返回
return
}
c.Next()
}
}
// 方式2:使用 github.com/gin-contrib/cors 库
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://frontend.com"},
AllowMethods: []string{"GET", "POST", "DELETE"},
AllowHeaders: []string{"Authorization"},
MaxAge: 12 * time.Hour,
}))
面试答法 :CORS 是浏览器的安全机制,由浏览器检查响应头决定是否放行,服务端只需在响应中加正确的
Access-Control-Allow-*头。预检请求是浏览器自动发的 OPTIONS,后端要正确处理 204 响应,否则跨域请求永远失败。
十、常见网络攻击与防御
DDoS(分布式拒绝服务)
SYN Flood:大量伪造 SYN 包,耗尽半连接队列
防御:SYN Cookie、增大半连接队列、限制 SYN 包速率
UDP Flood:大量 UDP 包消耗带宽
防御:流量清洗、限速
HTTP Flood(CC攻击):大量合法 HTTP 请求消耗服务器资源
防御:验证码、IP 限速、WAF
SQL 注入
漏洞:SELECT * FROM users WHERE name = '$input'
攻击:input = "' OR '1'='1" → 查出所有用户
防御:
参数化查询(Prepared Statement):? 占位符
ORM 框架自动处理
输入白名单验证
XSS(跨站脚本)
存储型 XSS:攻击者在数据库存入恶意脚本,其他用户访问时执行
反射型 XSS:恶意脚本在 URL 参数中,服务端直接拼入响应
防御:
输出时对 HTML 实体编码(< → <)
Content-Security-Policy 头(限制脚本来源)
HttpOnly Cookie(JS 无法读取 Cookie)
CSRF(跨站请求伪造)
攻击:用户已登录 bank.com,访问恶意页面,恶意页面自动发请求到 bank.com(携带用户 Cookie)
防御:
CSRF Token(随机令牌,恶意站点无法获取)
SameSite Cookie(限制跨站发送 Cookie)
验证 Referer / Origin 头
九、网络编程(Go 视角)
TCP 服务器基础
go
// 服务端
listener, _ := net.Listen("tcp", ":8080")
for {
conn, _ := listener.Accept() // 阻塞等待新连接
go handleConn(conn) // 每个连接一个 goroutine
}
func handleConn(conn net.Conn) {
defer conn.Close()
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf) // 读取数据
if err != nil { return }
conn.Write(buf[:n]) // 回写(Echo)
}
}
// 客户端
conn, _ := net.Dial("tcp", "localhost:8080")
defer conn.Close()
conn.Write([]byte("Hello"))
设置超时
go
// 连接超时
conn, err := net.DialTimeout("tcp", "host:port", 5*time.Second)
// 读写超时(每次读写前设置)
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
// 同时设置读写超时
conn.SetDeadline(time.Now().Add(10 * time.Second))
HTTP 客户端最佳实践
go
// 共享 Transport,复用连接池,不要每次请求创建新 Client
var httpClient = &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
// 不设置这些参数会导致:
// 1. 连接不复用,频繁三次握手
// 2. 连接泄漏,fd 耗尽
},
}
十、面试高频问题速答
Q:TCP 和 UDP 怎么选?
需要可靠传输(数据不能丢、需要顺序)选 TCP,如 HTTP、数据库;对实时性要求高、少量丢包可接受选 UDP,如视频通话、游戏。现代趋势是基于 UDP 自实现可靠传输(QUIC),兼顾可靠性和性能。
Q:TCP 三次握手,为什么不是两次?
两次握手只能让一方确认双方通信正常,另一方无法确认自己的报文被对方收到。第三次 ACK 让服务端确认客户端能正常接收,双方都验证了发送和接收能力。另外,两次握手无法应对历史失效的连接请求,可能导致服务端浪费资源建立无效连接。
Q:HTTP 和 HTTPS 的区别?
HTTP 是明文传输,存在被窃听、篡改、冒充的风险。HTTPS 在 HTTP 和 TCP 之间加了 TLS 层,提供加密(防窃听)、完整性校验(防篡改)和身份认证(防冒充)。握手阶段用非对称加密协商会话密钥,数据传输阶段用对称加密,兼顾安全和性能。HTTPS 有握手延迟和计算开销,但 TLS 1.3 和 Session 复用已大幅降低影响。
Q:HTTP/1.1 的队头阻塞是什么?HTTP/2 如何解决?
HTTP/1.1 管道化虽然可以连续发请求,但响应必须按顺序返回,如果第一个响应慢,后面所有响应都要等。HTTP/2 用二进制分帧和 Stream 实现多路复用,每个请求是独立的 Stream,响应可以乱序,彻底消除了应用层队头阻塞。但 TCP 层的队头阻塞依然存在,HTTP/3 用 QUIC 彻底解决。
Q:HTTPS 加密过程?
TLS 握手分两阶段:① 密钥协商------客户端发 ClientHello,服务端返回证书,双方通过 ECDHE 算法协商出会话密钥(整个过程用非对称加密保护);② 数据传输------用协商好的会话密钥对称加密所有 HTTP 数据。客户端同时验证证书的合法性(CA 签名链、域名匹配、有效期)。
Q:DNS 解析过程?
依次查询:浏览器缓存→OS 缓存(/etc/hosts)→本地 DNS 服务器缓存;缓存都未命中时,本地 DNS 服务器从根域名服务器开始递归查询:根→顶级域(.com)→权威域名服务器,最终得到 IP 并缓存返回。全程使用 UDP(报文小,一问一答)。
Q:浏览器输入 URL 到页面显示,经历了什么?
1. URL 解析(协议、域名、路径)
2. DNS 解析(域名→IP)
3. TCP 三次握手(建立连接)
4. TLS 握手(HTTPS 时)
5. HTTP 请求发送
6. 服务端处理,返回 HTTP 响应
7. 浏览器解析 HTML,构建 DOM 树
8. 下载 CSS/JS/图片等资源(可能并行)
9. 构建 CSSOM,合并成渲染树
10. Layout(计算位置大小)→ Paint(绘制)→ Composite(合成)
11. TCP 四次挥手(或 Keep-Alive 保持连接)
Q:什么是 CDN,原理是什么?
CDN(内容分发网络)在全球部署缓存节点,用户请求被调度到最近的节点,减少延迟。原理:DNS 解析时,权威 DNS 返回 CDN 的 CNAME,CDN 的 GSLB(全局负载均衡)根据用户 IP 返回最近节点的 IP。适合静态资源(图片、JS、CSS、视频)加速。
Q:Cookie、Session、Token(JWT)的区别?
Cookie 是浏览器存储机制,随请求自动发送,大小限制 4KB。Session 是服务端存储用户状态,客户端只持有 SessionID,服务端有状态,横向扩展需要共享 Session(Redis)。JWT 是服务端无状态方案,Token 自包含用户信息并签名,服务端只需验证签名,适合微服务,代价是无法主动让 Token 失效(需要黑名单)。
Q:JWT 的结构和验证原理?
JWT = Header.Payload.Signature(Base64URL 编码,点分隔)
Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "user123", "exp": 1234567890, "role": "admin"}
↑ 不加密!只是 Base64,任何人都能解码看到内容
Signature: HMAC-SHA256(base64(header) + "." + base64(payload), secret)
验证流程:
1. 用同一 secret 重新计算签名,与 Token 中的签名比较
2. 签名一致 → Token 未被篡改
3. 检查 exp 字段是否过期
注意:Payload 内容任何人可见,不要存密码等敏感信息
只能验证"未被篡改",不能验证"是否被注销"
Q:Time_Wait 和 Close_Wait 哪个多说明什么问题?
TIME_WAIT多:说明本机是主动关闭连接的一侧(客户端或主动断开的服务端),连接关闭太频繁(短连接场景),可以开启tcp_tw_reuse或使用长连接复用。CLOSE_WAIT多:说明本机的代码有问题,收到 FIN 后没有调用close()关闭连接,连接长期处于半关闭状态,通常是连接/资源泄漏,需要检查代码。
十一、一张图记住核心
TCP 可靠性保障:
┌──────────────────────────────────────────────────────┐
│ 序列号+确认号 → 知道哪些收到了 │
│ 超时重传+快速重传 → 丢包了重发 │
│ 滑动窗口(流量控制)→ 不压垮接收方 │
│ 拥塞控制(慢启动+拥塞避免)→ 不压垮网络 │
└──────────────────────────────────────────────────────┘
HTTP 演进:
HTTP/1.0(短连接)→ HTTP/1.1(Keep-Alive,管道化但有队头阻塞)
→ HTTP/2(多路复用,二进制分帧,头部压缩)
→ HTTP/3(QUIC+UDP,Stream 级别可靠,0-RTT,连接迁移)
HTTPS = HTTP + TLS(非对称握手 + 对称加密传输 + 证书验证)
面试核心考点:
TCP 三次握手/四次挥手(原因)
拥塞控制 vs 流量控制(目的、机制)
HTTP/1.1 vs HTTP/2 vs HTTP/3
HTTPS 握手流程
DNS 解析过程
浏览器输入 URL 全流程
总结 :计算机网络面试核心是 TCP 可靠传输机制 (握手挥手、拥塞控制、流量控制)、HTTP 协议演进 (1.1→2→3 解决了什么问题)、HTTPS 加密流程 、DNS 解析 、浏览器 URL 全流程。这五条主线吃透,计算机网络面试稳了。