CS 教程 · 第 07 章

互联网原理

浏览器工作、客户端/服务器模型

从输入 URL 到页面呈现,浏览器背后发生了什么?

当我们每天打开浏览器、输入网址、按下回车的那一刻,一场精密的数字协作悄然展开。本章将带你深入理解互联网通信的核心机制——从浏览器的工作原理到 HTTP 协议,从客户端-服务器模型到实时通信技术。


7.1 浏览器工作原理

浏览器是互联网世界的大门。现代浏览器(Chrome、Firefox、Safari)虽然各有特色,但核心架构高度相似。

7.1.1 浏览器的多进程架构

现代浏览器采用多进程架构,将不同功能隔离到独立进程中:

┌─────────────────────────────────────────┐
│            浏览器主进程                   │
│  (地址栏、书签、前进后退按钮)              │
├─────────────────────────────────────────┤
│  渲染进程(每个标签页一个)                │
│  ┌──────────┐  ┌──────────┐            │
│  │ 渲染引擎  │  │ JS 引擎  │            │
│  └──────────┘  └──────────┘            │
├─────────────────────────────────────────┤
│  GPU 进程 │ 网络进程 │ 插件进程 │ ...     │
└─────────────────────────────────────────┘

为什么要多进程?

7.1.2 渲染引擎的工作流程

以最常见的 Blink(Chrome)和 WebKit(Safari)为例,渲染过程分为五个阶段:

HTML → [解析] → DOM 树
CSS  → [解析] → CSSOM 树
              ↓
         [合并] → Render 树
              ↓
         [布局] → 计算每个元素的位置和大小
              ↓
         [绘制] → 将像素填充到屏幕
              ↓
         [合成] → 分层合并,GPU 加速

关键概念:

- 回流:改变布局(如修改宽高),开销大

- 重绘:只改变外观(如修改颜色),开销较小

// 引发回流的操作(应尽量减少)
element.style.width = '200px';     // 改变尺寸 → 回流
element.style.padding = '10px';    // 改变盒模型 → 回流
element.style.display = 'none';   // 隐藏元素 → 回流

// 只引发重绘的操作
element.style.color = 'red';       // 只改变颜色 → 重绘
element.style.backgroundColor = '#fff'; // 只改变背景 → 重绘

// 优化:批量修改样式
element.style.cssText = 'width: 200px; color: red; padding: 10px;';
// 浏览器会合并为一次回流

7.1.3 JavaScript 引擎

V8 引擎(Chrome/Node.js 使用)将 JavaScript 代码转化为机器码执行,核心流程:

JS 源代码 → [解析器] → AST(抽象语法树)
         → [解释器 Ignition] → 字节码
         → [优化编译器 TurboFan] → 优化后的机器码

JS 引擎是单线程的,这意味着:

console.log('1: 开始');

setTimeout(() => {
  console.log('2: 定时器回调');
}, 0);

Promise.resolve().then(() => {
  console.log('3: Promise 回调');
});

console.log('4: 结束');

// 输出顺序:1 → 4 → 3 → 2
// 原因:微任务(Promise)优先级高于宏任务(setTimeout)

7.2 HTTP 协议详解

HTTP(HyperText Transfer Protocol)是 Web 通信的基石,定义了客户端与服务器之间的数据交换规则。

7.2.1 HTTP 请求与响应

一次完整的 HTTP 通信:

客户端                                    服务器
  │                                        │
  │  GET /api/users HTTP/1.1               │
  │  Host: example.com                     │
  │  Accept: application/json              │
  │────────────────────────────────────────>│
  │                                        │  处理请求
  │                                        │  查询数据库
  │  HTTP/1.1 200 OK                       │
  │  Content-Type: application/json        │
  │  Content-Length: 85                    │
  │                                        │
  │  {"users": [{"id": 1, "name": "张三"}]}│
  │<────────────────────────────────────────│

7.2.2 HTTP 请求方法

方法 含义 幂等性 使用场景
GET 获取资源 查询数据、访问页面
POST 创建资源 表单提交、登录
PUT 完整更新资源 替换整个资源
PATCH 部分更新资源 更新部分字段
DELETE 删除资源 删除数据
HEAD 只获取响应头 检查资源是否存在
OPTIONS 查询支持的方法 CORS 预检请求
幂等性:多次执行同一请求,结果相同。GET 请求无论执行多少次,都不会改变服务器状态。

7.2.3 HTTP 状态码

状态码由三位数字组成,按首位数字分类:

1xx:信息提示
  100 Continue — 请求继续发送

2xx:成功
  200 OK            — 请求成功
  201 Created       — 资源已创建
  204 No Content    — 成功但无返回内容

3xx:重定向
  301 Moved Permanently  — 永久重定向
  302 Found              — 临时重定向
  304 Not Modified       — 缓存有效,不用重新下载

4xx:客户端错误
  400 Bad Request    — 请求格式错误
  401 Unauthorized   — 需要认证
  403 Forbidden      — 禁止访问(认证了也没有权限)
  404 Not Found      — 资源不存在
  429 Too Many Requests — 请求频率过高

5xx:服务器错误
  500 Internal Server Error — 服务器内部错误
  502 Bad Gateway           — 网关错误
  503 Service Unavailable   — 服务暂时不可用

7.2.4 Cookie、Session 与 Token

HTTP 是无状态协议,服务器默认不知道两次请求来自同一用户。状态保持方案:

Cookie 机制

# 服务器在响应头中设置 Cookie
Set-Cookie: session_id=abc123; HttpOnly; Secure; Max-Age=3600

# 浏览器后续请求自动携带
Cookie: session_id=abc123

Cookie 关键属性:

现代趋势:JWT(JSON Web Token)

// JWT 结构:Header.Payload.Signature
const token = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.xqWbC";

// 客户端存储(如 localStorage),请求时放在 Authorization 头
fetch('/api/profile', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});
对比维度 Cookie + Session JWT
存储位置 服务器存储 Session,客户端存 Cookie 客户端存储 Token
扩展性 需要共享 Session(多服务器时有挑战) 无状态,天然支持分布式
安全性 可设为 HttpOnly,较安全 需注意 XSS,可配合 refresh token
适用场景 传统 Web 应用 API 服务、移动端、微服务

7.3 HTTPS:安全的 HTTP

HTTPS = HTTP + SSL/TLS,通过加密保障通信安全。

7.3.1 TLS 握手过程(简化版)

客户端                          服务器
  │                               │
  │  1. ClientHello               │
  │  (支持的加密套件、随机数A)     │
  │──────────────────────────────>│
  │                               │
  │  2. ServerHello               │
  │  (选择的加密套件、随机数B、证书)│
  │<──────────────────────────────│
  │                               │
  │  3. 验证证书,生成预主密钥       │
  │  用服务器公钥加密后发送│
  │──────────────────────────────>│
  │                               │
  │  双方用随机数A+B+预主密钥        │
  │  生成对称加密密钥,开始安全通信   │
  │<══════════════════════════════>│

为什么先用非对称加密,后换对称加密?


7.4 客户端-服务器模型

┌──────────┐   请求    ┌──────────────┐   查询    ┌──────────┐
│  客户端   │ ──────>  │   Web 服务器  │ ──────>  │  数据库   │
│ (浏览器)  │ <────── │  (Nginx/Node) │ <────── │ (MySQL)  │
└──────────┘   响应    └──────────────┘   结果    └──────────┘

演进路径:

1. 单体架构:一台服务器处理所有逻辑

2. 动静分离:静态资源由 Nginx 直接返回,动态请求转发到应用服务器

3. 前后端分离:前端独立部署,通过 API 与后端通信

4. 微服务化:按业务拆分为独立服务,各自独立部署和扩展


7.5 CDN 加速原理

CDN(Content Delivery Network,内容分发网络)通过全球分布的边缘节点,让用户就近获取内容。

没有 CDN:
  北京用户 ──────────────── 🌍 源站(美国)
  延迟:200ms ─────────────────────>│

有 CDN:
  北京用户 ──── 🏢 CDN 边缘节点(北京)
  延迟:10ms ──>│
                 │(缓存未命中时回源)
                 └──── 🏢 源站(美国)

CDN 工作流程:

1. DNS 解析返回最近的 CDN 节点 IP

2. 用户请求到达边缘节点

3. 节点有缓存 → 直接返回

4. 节点无缓存 → 回源拉取,缓存后返回

CDN 适用场景:


7.6 RESTful API 设计

REST(Representational State Transfer)是目前最主流的 API 设计风格。

核心原则

1. 资源导向:URL 表示资源,用 HTTP 方法表示操作

2. 无状态:每个请求包含所有必要信息

3. 统一接口:一致的资源操作方式

设计实例

# 用户资源
GET    /api/users          # 获取用户列表
GET    /api/users/1        # 获取 ID 为 1 的用户
POST   /api/users          # 创建新用户
PUT    /api/users/1        # 完整更新用户
PATCH  /api/users/1        # 部分更新用户
DELETE /api/users/1        # 删除用户

# 嵌套资源
GET    /api/users/1/posts  # 获取用户 1 的所有文章
POST   /api/users/1/posts  # 为用户 1 创建文章

最佳实践

// 统一响应格式
{
  "code": 200,
  "message": "success",
  "data": {
    "id": 1,
    "name": "张三",
    "email": "zhangsan@example.com"
  }
}

// 分页参数
GET /api/users?page=1&page_size=20

// 版本管理
GET /api/v1/users      // URL 路径版本
// 或
GET /api/users
Accept: application/vnd.company.v1+json    // Header 版本

7.7 WebSocket 实时通信

HTTP 是"请求-响应"模式,服务器不能主动推送。WebSocket 解决了这个问题。

// 客户端(浏览器)
const ws = new WebSocket('wss://example.com/chat');

ws.onopen = () => {
  console.log('连接已建立');
  ws.send(JSON.stringify({ type: 'join', room: 'general' }));
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  console.log('收到消息:', message);
};

ws.onclose = () => {
  console.log('连接已关闭,尝试重连...');
  setTimeout(() => { /* 重连逻辑 */ }, 3000);
};

// 关闭连接
ws.close();
// 服务端(Node.js + ws 库示例)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  console.log('新客户端已连接');

  ws.on('message', (data) => {
    // 广播给所有客户端
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(data);
      }
    });
  });

  ws.on('close', () => {
    console.log('客户端已断开');
  });
});

WebSocket vs HTTP 对比:

特性 HTTP WebSocket
通信模式 请求-响应 全双工
连接方式 短连接(1.1 可复用) 长连接
头部开销 大(每次请求携带头) 小(仅握手时有完整头)
适用场景 REST API、网页加载 聊天、股票行情、游戏、协作编辑

替代方案:SSE(Server-Sent Events)

如果只需要服务器向客户端的单向推送,SSE 更简单:

// 客户端
const evtSource = new EventSource('/api/events');
evtSource.onmessage = (event) => {
  console.log('收到推送:', event.data);
};

综合示例:从输入 URL 到页面渲染

让我们以一个实际例子串联本章所有知识点:

1. 用户在地址栏输入 https://www.example.com

2. DNS 解析(可能经过 CDN)
   浏览器缓存 → OS 缓存 → 路由器缓存 → DNS 服务器
   → 返回 CDN 边缘节点 IP

3. TCP 三次握手 + TLS 握手
   → 建立安全的 TCP 连接(HTTPS)

4. 发送 HTTP 请求
   GET / HTTP/1.1
   Host: www.example.com

5. 服务器处理(可能经过 Nginx → 应用服务器 → 数据库)
   → 返回 200 OK + HTML 内容

6. 浏览器解析 HTML,构建 DOM 树
   遇到  标签 → 下载 CSS → 构建 CSSOM
   遇到