RTMP(Real-Time Messaging Protocol)的握手机制是建立客户端与服务器之间可靠连接的第一步。它发生在 TCP 三次握手之后,主要目的是协商协议版本、同步时间戳以及验证连接的合法性。

RTMP 握手主要分为‌简单握手(Simple Handshake)‌和‌复杂握手(Complex Handshake)‌两种模式。目前大多数现代流媒体服务器(如 Nginx-rtmp, SRS, Adobe Media Server)都支持或默认使用复杂握手以增强安全性或兼容性,但简单握手是协议的基础。

1. 握手基本流程概述

无论简单还是复杂握手,RTMP 握手都涉及三个固定大小的数据包交换,每个包大小为 ‌1536 字节‌(除了第一个字节 C0/S0)。

  • C0/C1‌: 客户端发送
  • S0/S1/S2‌: 服务器响应
  • C2‌: 客户端确认

标准握手序列:

  • Client -> Server‌: 发送 C0 和 C1。
  • Server -> Client‌: 收到 C1 后,发送 S0, S1 和 S2。
  • Client -> Server‌: 收到 S1 后,发送 C2。

完成‌: 当 Client 收到 S2 且 Server 收到 C2 后,握手完成,后续可以开始传输 RTMP 消息块(Chunks)。

2. 数据包结构详解

C0 和 S0(协议版本标识)

大小‌: 1 字节
内容‌:

  • 0x03: 表示 RTMP 版本 3(最常用,明文传输)。
  • 0x06: 表示加密的 RTMPE(RTMP Encrypted)。
  • 其他值通常被忽略或视为版本 3。

作用‌: 告知对端使用的 RTMP 协议版本。

C1 和 S1(关键数据交换)

大小‌: 1536 字节
结构‌:

  • Time (4 bytes)‌: 时间戳。通常是发送端的当前时间(毫秒级),用于后续计算网络延迟和同步。在简单握手中,这个值可以是任意值;在复杂握手中,它参与摘要计算。
  • Zero (4 bytes)‌: 全零字段(在简单握手中);在复杂握手中,这部分可能包含版本信息或随机数。
  • Random Bytes (1528 bytes)‌: 随机数据。

简单握手‌: 纯随机数,用于区分不同的连接会话。
复杂握手‌: 包含 ‌Digest‌(摘要)和 ‌Key‌(密钥)信息,用于 HMAC-SHA256 验证,防止中间人攻击或非法客户端连接。

C2 和 S2(回声确认)

大小‌: 1536 字节
结构‌:

  • Time (4 bytes)‌: 必须是对端发来的 S1 或 C1 中的时间戳。
  • C2 中的 Time = S1 中的 Time。
  • S2 中的 Time = C1 中的 Time。

Time2 (4 bytes)‌: 本端之前发送的时间戳(可选,常用于计算往返延迟 RTT)。
Random Echo (1528 bytes)‌: 必须原样返回对端 S1 或 C1 中的随机数据部分。
C2 的 Random Echo = S1 的 Random Bytes。
S2 的 Random Echo = C1 的 Random Bytes。
作用‌: 证明双方都正确接收并处理了对方的 C1/S1 包,确保连接的完整性和同步性。

3. 简单握手 vs 复杂握手

表格

特性 简单握手 (Simple Handshake) 复杂握手 (Complex Handshake)
定义来源‌ Adobe RTMP 规范基础部分 Adobe Flash Media Server 实现细节
C1/S1 内容‌ Time + Zero + 随机数 Time + Version/Random + ‌Digest‌ + ‌Key‌
验证机制‌ 无加密验证,仅靠随机数区分会话 使用 HMAC-SHA256 进行完整性校验
兼容性‌ 所有 RTMP 服务器均支持 大多数现代服务器支持,旧设备可能不支持
用途‌ 测试、内部网络、非安全场景 公共互联网直播、需要防篡改/鉴权的场景

复杂握手的关键点:

  • Digest 位置‌: 在 1528 字节的随机区域中,通过特定算法确定 Digest 的偏移量(Offset)和长度(32字节)。
  • Key 计算‌: 使用预共享密钥(FPKey)和数据进行 HMAC-SHA256 计算,生成签名。
  • 验证‌: 接收方根据收到的数据重新计算 Digest,如果与收到的 Digest 匹配,则握手成功;否则断开连接。

4. 握手过程中的常见问题与 Broken Pipe

你之前提到的 java.net.SocketException: Broken pipe 在 RTMP 握手中可能由以下原因引起:

  • 握手超时‌: 客户端发送 C1 后,服务器未在合理时间内响应 S1,或者客户端发送 C2 后服务器未确认。如果一方设置了 Socket 超时(setSoTimeout),超时后会关闭连接,另一方再写入数据就会报 Broken pipe。
  • 版本不匹配‌: 客户端发送 C0 版本为 0x03,但服务器只支持其他版本(极少见),服务器可能直接关闭连接。
  • 复杂握手验证失败‌: 如果服务器要求复杂握手,而客户端发送的是简单握手格式(或 Digest 计算错误),服务器会在收到 C1 后直接关闭连接,不再发送 S1/S2。客户端若继续尝试发送 C2 或后续数据,就会触发 Broken pipe。
  • 网络中断‌: 在握手过程中网络波动导致 TCP 连接断开。

5. 总结

RTMP 握手是一个严格的三步交换过程:

  • C0+C1‌ (Client Init)
  • S0+S1+S2‌ (Server Response)
  • C2‌ (Client Ack)

开发 RTMP 客户端时,务必注意:

  • 严格遵循 1536 字节的数据包大小。
  • 正确回显 S1 中的时间戳和随机数据到 C2。

如果服务器支持复杂握手,需实现 HMAC-SHA256 摘要计算。
处理好异常,一旦握手失败(如收到 RST 包或超时),应立即关闭 Socket 并重连,避免对已关闭的连接进行写操作。

Logo

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

更多推荐