在上一篇网络流媒体协议之——RTMP协议(Part I)中,介绍了RMP协议的基本message结构与chunk格式,本篇我们来更深入地来了解一下RTMP中的消息交互。分成三个部分来介绍,分别是Handshake、控制消息与交互、命令消息与交互。
1. Handshake
RTMP 连接的建立首先通过握手(handshake)开始。Handshake消息不同于协议的其他消息,并非由可变大小的chunk和chunk header组成,而是由固定大小的三个chunks组成。客户端和服务器依次发送这三个同样的chunks,为了方便表述,客户端发送的三个chunks记为C0,C1和C2,服务器端发送的三个chunks记为S0,S1和S2。
1.1 Handshake Sequence
Handshake始于客户端发送C0和C1,RTMP协议并没有严格规定6个Handshake message发送顺序,但有如下几个限制:
- 客户端必须收到S1后,才能发送C2;
- 客户端必须收到S2后,才能发送其他数据;
- 服务器端必须收到C0后,才能发送S0和S1;
- 服务器端必须收到C1后,才能发送S2;
- 服务器端必须收到C2后才能发送其他数据。
Handshake sequence如下图所示,
在实际使用中,一般的Handshake顺序是这样的:
| client | Server |
|---C0+C1—->|
|<--S0+S1+S2–|
|---C2---->|
下面是通过PC播放香港电视台直播,并通过wireshark抓包得到的RTMP握手过程,
1.2 C0、S0 Format
C0和S0为单字节chunk,格式如下:
Version表示RTMP协议的版本号,目前使用的是版本3。例如,前面抓取的香港电视台直播的handshake message,将,C0和S0的包展开如下:
1.3 C1、S1 Format
C1和S1 message长1536字节,包含如下组成:
Time:4 bytes的时间戳;
Zero:4 bytes的全零域;
Random bytes:1528 bytes的随机数。
1.4 C2、S2 Format
C2和S2 的包也是1536字节长,几乎分别是对S1和C1的回声,其组成如下:
Time:对于C2来说,该域的值与S1的time域相同;对于S2来说,该域的值与C1的time域相同。
Time2:该域存储的是对端送来的上一个packet(S1或C1)被读取的时间。
Random echo:该域的数据必须是S1(对于C2来说)和C1(对于S2来说)域中的数据。为了验证这一点,我们再来看wireshark抓取得handshake包,展开S1和C2,如下所示,发现S1的random data和C2的random echo数据是完全相同的。
再来看C1和S2,不一样?Why?
2. Protocol Control Messages
RTMP Chunk Stream将message type ID 1,2,3,5和6用于协议控制消息,这些协议控制消息必须使用message stream ID 0(即控制流ID),并在chunk stream ID (CSID) 2上发送。协议控制消息在被接收到的瞬间生效,其时间戳被接收端忽略。下面我们按照不同的message type ID来分别介绍几种不同的协议控制消息。
2.1 Set Chunk Size (message type ID = 1)
Set Chunk Size消息使用协议控制消息ID 1,用于通知对端新的最大chunk size。最大chunk size默认为128字节,但客户端和服务器都可以修改该值,并通过Set Chunk Size命令通知对端更新。例如,假如客户端要发送131 bytes大小的音频数据至服务器端,而当前的chunk size为128 bytes,那么客户端就可以通过Set Chunk Size消息告知服务器端最新的chunk size是131 bytes,从而,客户端即可在1个chunk上发送131 bytes的音频数据了。最大chunk size至少为1 byte,客户端和服务器端两方均可维护自己的最大chunk size。
下面为Set Chunk Size的message payload:
位必须为0。Chunk Size占31位,最大值为2147483647(0x7FFFFFFF),但实际上所有大于16777215(0xFFFFFF)的值都认为等于
16777215,因为Message的最大长度为0xFFFFFF,而chunk size不可能大于Message的长度。
2.2 Abort Message (message type ID = 2)
当一个message被切分成多个chunks,接收端只接收到了部分chunks时,发送该控制消息通知对端不再传输该message的chunks,接收端在收到该消息后,会丢弃已接收到的不能组成完整message的chunks。该控制消息的payload中只有一个CSID,该CSID表示的当前消息的chunks都被丢弃。Abort Message的payload如下:
32 bits的CSID:该CSID的当前message不再传输后续的chunks,已接收到的chunks将被丢弃。
2.3 Acknowledgement (message type ID = 3)
当收到对端的消息大小等于窗口大小(window size)时,接收端必须返回一个ACK给发送端。窗口大小就是指收到接收端反馈的ACK前最多可以发送的字节数量。ACK中携带一个4字节的sequence number,表明目前已经接收到的字节总数。
2.4 Window Acknowledgement Size (message type ID = 5)
客户端和服务器端发送该消息通知对端在发送acknowledgements之间使用的window size。发送端在发送等于window size大小的数据后,会期待对端有一个确认消息(上一节的acknowledgement)返回;而接收端自从发送完上一个acknowledgement(或从会话的开始)后,若接收到指定大小的数据,则必须向发送端反馈一个acknowledgement消息。
2.5 Set Peer Bandwidth (message type ID = 6)
客户端或服务器通过发送该消息来限制对端的输出带宽。接收端在收到该消息后,会限制已发送但未收到ACK的数据大小至该消息指定的window size大小。接收端在收到该消息后,如果该消息指定的window size与上一次反馈给发送端的window size大小不一致,应该再次反馈一个Window Acknowledgement Size消息给发送端。
该消息的Payload包含5个字节,其中4字节的Acknowledgement Window Size和1字节的Limit Type,Limit Type有如下取值:
0 – Hard:对端应该限制其输出带宽至该消息指定带宽;
1 – Soft:对端应将带宽限制在该消息指定的值以内,或者按照已生效的带宽限制,以两者中较小的值为准;
2 – Dynamic:若上一个Limit Type为Hard,将当前message的Limit Type也标为Hard,否则忽略该消息。
3. RTMP Command Messages
上一篇我们提到RTMP中的message类型,包括:audio message、video message、data message、shared object message、command message。Command message是客户端和服务器用来交换AMF编码的命令的消息。发送端发送的消息中包含:命令名称(command name)、事务ID(transaction ID)、以及包含相关参数的命令对象(command object)。接收端处理接收到的command message后,会回馈给发送端一个包含相同transaction ID的回复消息。回复的消息可能是:_result、_error或method name,其中_result表示接受该命令,对端可以继续往下执行相关流程;_error表示拒绝该命令;method name表示要在对端执行的函数或方法名称,比如verifyClient或contactExternalServer。
Command消息主要分为两类:NetConnection、NetStream。NetConnection为服务器和客户端的高层表示,用于管理两端之间的连接状态;NetStream是信息流的传输通道,也会传输一些诸如play、pause等控制信息流的命令。
3.1 NetConnection Commands
NetConnection管理服务器和客户端的双向连接,此外,它还提供异步远程方法调用(RPC)。NetConnection上可传输下列命令:
- connect
- call
- close
- createStream
3.1.1 connect
客户端向服务器端发送connect请求来建立连接。请求和回复的消息结构如下面两图:
其中,command information中的name-value键值对有很多,不一一列举,感兴趣的朋友可以去翻看原始协议查询。Connect命令的消息流如下:
3.1.2 call
Call方法用于接收端的远程过程调用(RPC),被调用的RPC名称作为参数在Call命令中传输。Call请求和反馈的消息结构如下:
3.1.3 createStream
客户端向服务器端发送该消息以创建message通信的逻辑通道。音频、视频和元数据的发布都是通过该命令创建的流通道来传输的。该命令的请求和回复消息结构如下:
3.2 NetStream Commands
NetStream定义了NetConnection连接的客户端和服务器之间用于传输音频、视频和数据的通道。一个netConnection可支持不同数据流的多个netStreams。netStream上可传输的从client到server方向的命令有:play,play2,deleteStream,closeStream,receiveAudio,receiveVideo,publish,seek,pause;在server到client方向,有”onStatus”命令,用于更新netStream的状态。
onStatus命令消息结构如下:
实例:
3.2.1 play
客户端向服务器发送该命令来实现一个流的播放。也可以通过多次执行该命令来形成一个播放列表。命令结构如下:
客户端与服务器之间的play命令交互流程如下:
3.2.2 play2
与play命令不同,play2可以将正在播放的流切换到相同内容的不同比特率的流上。服务器端维护了不同比特率的文件供客户端发送play2请求切换。命令结构如下:
客户端和服务器之间play2命令的交互流程如下:
3.2.3 deleteStream
客户端发送该命令告知服务器本地的某个流被销毁,不需要再传输此路流;服务器不需要回复该命令。该命令的结构如下:
3.2.4 receiveAudio
客户端发送该消息通知服务器是否接收Audio数据。
如果Bool Flag设为false,表示客户端不接收audio数据,服务器端无需回复该消息;如果Bool Flag为true,表示客户端要接收audio数据,则服务器端会回复NetStream.Seek.Notify和NetStream.Play.Start的状态消息。
3.2.5 receiveVideo
与receiveAudio类似,该消息用于通知服务器端是否接收视频数据,除了Command Name字段为“receiveVideo”,其他字段都与receiveAudio相同,不再赘述。
3.2.6 publish
客户端通过该命令向服务器发布某个名称的流
(推流),发布成功后,其他客户端都可通过该名称来访问服务器上已发布的音频、视频或数据流。服务器会向客户端回复一个表示发布开始的onStatus命令。
3.2.7 seek
定位到视频或音频的某个位置,以毫秒为单位。若seek成功,服务器回复一个NetStream.Seek.Notify状态消息;若seek失败,回复_error消息。
3.2.8 pause
暂停命令。若暂停,服务器回复NetStream.Pause.Notify状态;若取消暂停,回复NetStream.Unpause.Notify;若失败,返回_error消息。
内容太多,总算是整理完了,希望能帮到需要的小伙伴~