RTMP协议(二)握手流程
2020-07-03 本文已影响0人
Seacen_Liu
若要建立一个有效的 RTMPConnection
链接,首先需要“握手”:客户端要向服务器按序发送C0
,C1
,C2
三个chunk
,服务器向客户端按序发送S0
,S1
,S2
三个chunk
,然后才能进行有效的信息传输。RTMP 协议本身并没有规定这6个信息的具体传输顺序,但 RTMP 协议的实现者需要保证这几点如下:
- 对于 RTMP 客户端
- 握手以客户端发送
C0
和C1
开始 - 客户端必须接收
S1
之后才能发送C2
- 客户端必须接收
S2
之后才能发送其他数据
- 握手以客户端发送
- 对于 RTMP 服务端
- 服务端必须接收到
C0
才能发送S0
和S1
,也可以等待接收到C1
再发送S0
和S1
- 服务端必须接收
C1
才能发送S2
- 服务端必须接收
C2
才能发送其他数据
- 服务端必须接收到
握手示意图
+-------------+ +-------------+
| Client | TCP/IP Network | Server |
+-------------+ | +-------------+
| | |
Uninitialized | Uninitialized
| C0 | |
|------------------->| C0 |
| |-------------------->|
| C1 | |
|------------------->| S0 |
| |<--------------------|
| | S1 |
Version Sent |<--------------------|
| S0 | |
|<-------------------| |
| S1 | |
|<-------------------| Version Sent
| | C1 |
| |-------------------->|
| C2 | |
|------------------->| S2 |
| |<--------------------|
AckSSent | Ack Sent
| S2 | |
|<-------------------| |
| | C2 |
| |-------------------->|
Handshake Done | Handshake Done
| | |
Pictorial Representation Of Handshake
握手状态
-
Uninitialized (未初始化):协议的版本在这个状态中被发送。客户端在数据包
C0
中将协议版本发出,如果服务端支持这个版本,将在回应中发送S0
和S1
,并进入Version Sent
状态。如果不支持,服务端会终止连接。 -
Version Sent (版本已发送):客户端和服务端分别等待接收
S1
和C1
。接收完成后,将会进入Ack Sent
状态。 -
Ack Sent (确认已发送):客户端和服务端分别等待接收
S2
和C2
。接收完成后,进入Handshake Done
状态。 - Handshake Done (握手完成):客户端和服务端可以开始交换消息了。
握手数据格式
在 Flash10.1 之后,Adobe 对 RTMP 握手进行了一轮修改,握手的步骤和上文记述的没有不同,而是对握手的数据进行了修改,采用了加密数据。因此,现在存在两种握手数据,一般通过C1[4:8]
是否为0
来区分。我们将使用0
值称为简单握手(simple handshake),将非0
值的称为复杂握手(complex handshake)。
简单握手 - simple handshake
C0 和 S0(1 byte)
+-+-+-+-+-+-+-+-+-+-+-+
| version (1 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+
-
version(1 byte)
:版本号,表示客户端请求的 RTMP 版本号或服务端支持的 RTMP 版本号。当前使用的版本是3
。
C1 和 S1(1536 bytes)
+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+
| zero (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+
| random bytes |
+-+-+-+-+-+-+-+-+-+-+
| random bytes |
| (cont) |
| .... |
+-+-+-+-+-+-+-+-+-+-+
-
time(4 bytes)
:本字段包含一个发送时间戳(取值可以为零或其他任意值)。客户端应该使用此字段来标识所有流块的时间戳。为了同步多个块流,客户端可能希望多个块流使用相同的时间戳。 -
zero(4 bytes)
:本字段必须全为零。 -
random (1528 bytes)
:本字段可以包含任何值。由于握手的双方需要区分另一端,此字段填充的数据必须足够随机(以防止与其他握手端混淆)。不过没有必要为此使用加密数据或动态数据。
C2 和 S2(1536 bytes)
+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+
| time2 (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+
| random bytes |
+-+-+-+-+-+-+-+-+-+-+
| random bytes |
| (cont) |
| .... |
+-+-+-+-+-+-+-+-+-+-+
-
time(4 bytes)
:本字段表示对端发送的时间戳(对C2
来说是S1
,对S2
来说是C1
)。 -
time2(4 bytes)
:本字段表示接收对端发送过来的握手包的时间戳。 -
random(1528 bytes)
:本字段包含对端发送过来的随机数据(对C2
来说是S1
,对S2
来说是C1
)。
握手的双方可以使用``time
和
time2` 字段来估算网络连接的带宽和或延迟,但是不一定有用。
复杂握手 - complex handshake
C0 和 S0(1 byte)
+-+-+-+-+-+-+-+-+-+-+-+
| version (1 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+
-
version(1 byte)
:版本号,表示客户端请求的 RTMP 版本号或服务端支持的 RTMP 版本号。当前使用的版本是3
。
C1 和 S1(1536 bytes)
+-+-+-+-+-+-+-+-+-+-+
| time (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+
| version (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+
| key (764 bytes) |
+-+-+-+-+-+-+-+-+-+-+
| digest (764 bytes)|
+-+-+-+-+-+-+-+-+-+-+
-
time(4 bytes)
:发送的时间戳 -
version (4 bytes)
:版本号- 客户端的
C1
一般是0x80000702
- 服务端的
S1
一般是0x04050001
、0x0d0e0a0d
(livego
中数值)
- 客户端的
-
key (764 bytes)
:结构如下-
random-data
: (offset) bytes -
key-data
: 128 bytes -
random-data
: (764 - offset - 128 - 4) bytes -
offset
: 4 bytes
-
-
digest (764 bytes)
:结构如下-
offset
: 4 bytes -
random-data
: (offset) bytes -
digest-data
: 32 bytes -
random-data
: (764 - 4 - offset - 32) bytes
-
在不同的包里,
key
和diest
顺序可能会颠倒,比如nginx-rtmp
C2 和 S2(1536 bytes)
+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random-data (1504 bytes)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+
| digest-data (32 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
random-data
和digest-data
都应来自对应的数据(对C2
来说是S1
,对S2
来说是C1
)
digest
的计算方式较为复杂,后续再补全
握手流程设计的理解
-
C0
和S0
是版本数据包 -
C1
和S1
是带有验证数据的包 -
S2
和C2
是验证C1
和S1
的数据包
实现选用的流程
为了方便开发,在实现上,我们选用以下握手流程,这样服务端可以连续发送S0
,S1
和S2
:
-
Client
-->Server
: 发送一个创建流的请求(C0
、C1
) -
Server
-->Client
: 返回一个流的索引号(S0
、S1
、S2
)。 -
Client
-->Server
: 开始发送 (C2
) -
Client
-->Server
: 发送音视频数据(这些包用流的索引号来唯一标识)