React · 17/84
1. 为什么推荐将静态资源放到 CDN 上 2. Class 组件的局限性 3. Class 组件与函数组件 4. Composition API 与 Hooks 对比 5. Vue 中 computed 和 watch 的区别 6. 爬楼梯问题 7. createElement 执行过程 8. 限制构造函数只能通过 new 调用 9. 判断 React 组件类型 10. 受控与非受控组件 11. 自定义 Hook 开发 12. 什么是 DNS 劫持? 13. 判断对象是否是 React 元素 14. HOC 与 Render Props 15. React 中 Element、Component、Node、Instance 的区别 16. Hooks 使用规则 17. HTTP/2 多路复用原理 18. HTTP 报文结构 19. HTTPS 握手过程 20. Immutable 在 React 中的应用 21. 实现图片懒加载 22. JavaScript == 运算符的机制 23. JavaScript 数组的内存存储方式 24. JSX 本质 25. Immutable 在 React 中的应用 26. 最大子序和 27. React Router 的 Link 和 a 标签的区别 28. JSX语法糖本质 29. 父组件调用子组件方法 30. 移动端样式适配方案 31. Portal 中的事件冒泡机制 32. React 17 新特性 33. React 18 新特性与并发渲染 34. React 组件渲染流程 35. React 是什么 36. React元素$$typeof属性 37. React 组件通信方式 38. React 错误边界处理 39. React 核心概念 40. React 组件设计 41. React Fiber 架构 42. React Hooks 原理与规则 43. React 常用 Hooks 使用指南 44. React.memo 和 memoize 函数的区别 45. React 生命周期演变 46. React 性能优化实践 47. React 性能优化策略 48. React Portals 的使用场景 49. React 中的 ref 使用 50. React 和 React-DOM 的关系 51. React 为什么不直接使用 requestIdleCallback 52. React-Router 原理与工作方式 53. React 合成事件机制 54. React 服务端渲染实现 55. React 事务机制 56. setState 同步异步问题 57. setTimeout 为什么不能保证及时执行 58. Redux 工作流与中间件 59. React 服务端渲染实现 60. 单页应用如何提高加载速度 61. Source Map 的工作原理 62. TypeScript 中的命名空间与模块 63. Taro 多端框架实现原理 64. Taro 2.x 和 Taro 3 的区别 65. TypeScript 与 JavaScript 的区别 66. TCP 三次握手和四次挥手 67. useEffect 支持 async/await 68. useEffect 闭包陷阱 69. useMemo 和 useCallback 的使用场景 70. useContext 的使用方法 71. useReducer 与 Redux 对比 72. useState 连续调用 setState 导致值丢失 73. 实现 useTimeout Hook 74. useRef、ref 和 forwardRef 的区别 75. 虚拟DOM性能分析 76. 实现 useUpdate 强制组件重新渲染 77. Virtual DOM 的意义 78. 虚拟DOM的三个组成部分 79. Virtual DOM 与 Diff 算法 80. Vue 页面渲染流程 81. Vue 与 React 对比 82. Vue 与 React 的 Diff 算法差异 83. Vue2 数组变化检测的限制与解决方案 84. Vue3 实现 Modal 组件

HTTP/2 多路复用原理

HTTP/2 如何通过二进制帧实现多路复用

问题

HTTP/2 中,多路复用的原理是什么?

解答

什么是多路复用

HTTP/1.1 的请求-响应模型是串行的:一个 HTTP 消息必须完成后,才能处理下一个。而 HTTP/2 允许多个消息在同一个连接上交织传输,这就是多路复用——在一个 TCP 连接上,多个 HTTP 消息同时工作。

HTTP/1.1 为什么不能多路复用

HTTP/1.1 是基于文本分割解析的协议。请求消息格式如下:

GET /index.html HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0

服务端需要不断读入字节,直到遇到换行符(\n\r\n)才能解析一行内容。这种解析方式存在两个问题:

一次只能处理一个消息:在遇到分隔符完成解析之前,无法停止或切换到其他消息。

内存分配不可预知:服务端不知道一行内容有多长,无法预先分配合适的缓冲区大小,既要保证解析效率,又要避免内存浪费。

HTTP/2 的帧结构

HTTP/2 是基于二进制帧的协议。帧是数据的基本单元,结构如下:

+-----------------------------------------------+
|                 Length (24)                   |
+---------------+---------------+---------------+
|   Type (8)    |   Flags (8)   |
+-+-------------+---------------+-------------------------------+
|R|                 Stream Identifier (31)                      |
+=+=============================================================+
|                   Frame Payload (0...)                      ...
+---------------------------------------------------------------+

前 9 个字节对每个帧都是固定的:

  • Length (3 字节):帧负载的长度
  • Type (1 字节):帧类型(HTTP/2 定义了 10 种帧类型)
  • Flags (1 字节):帧标志位
  • Stream Identifier (4 字节):流 ID,标识帧所属的流

服务端只需解析前 9 个字节,就能准确知道整个帧的大小和类型,实现了”一切可预知,一切可控”。

多路复用的实现

HTTP/2 引入了”流”的概念:流是连接上独立的、双向的帧序列交换。每个帧的 Stream ID 标识它属于哪个流。

多路复用的工作方式:

  1. 客户端发起多个请求,每个请求分配一个唯一的 Stream ID
  2. 请求被拆分成多个帧,每个帧携带 Stream ID
  3. 这些帧可以交错发送,不需要等待前一个请求完成
  4. 服务端根据 Stream ID 将帧重新组装成完整的请求
  5. 响应同样以帧的形式交错返回

例如,一个 POST 请求可能被拆分为:

  • HEADERS 帧(Stream ID: 3)
  • DATA 帧(Stream ID: 3)

同时另一个 GET 请求的帧也在传输:

  • HEADERS 帧(Stream ID: 5)

这些帧在同一个 TCP 连接上交错传输,实现了真正的并发。

关键点

  • HTTP/2 基于二进制帧,每个帧前 9 字节包含长度、类型和流 ID,使解析可预知
  • HTTP/1.1 基于文本分割,必须串行解析,无法预知内存需求
  • 流(Stream)是帧的逻辑分组,通过 Stream ID 标识不同的请求/响应
  • 多个流的帧可以在同一连接上交错传输,实现真正的并发
  • 这种设计消除了 HTTP/1.1 的队头阻塞问题,显著提升了传输效率