抓包使用的服务端是nginx-rtmp模块
流程如下
- 客户端与服务器建立TCP连接。
- 双方通过握手过程确认协议版本及交换随机数等信息。
- 客户端发送连接命令(connect)到服务器。
- 服务器响应连接命令,返回连接结果。
- 客户端与服务器建立流(stream)进行音视频数据传输。
- 在传输过程中,双方可以发送控制命令,如播放(play)、暂停(pause)等。
- 当连接关闭时,双方结束消息传输并断开连接
RTMP握手
为了减少通信的次数,一般的发送顺序是这样的
握手流程
Uninitialized (未初始化):协议的版本号在这个阶段被发送。客户端和服务器都是。
uninitialized (未初始化)状态。之后客户端在数据包 C0 中将协议版本号发出。如果服务器支持这个版本,它将在回应中发送S0 和 S1。如果不支持,服务器会才去适当的行为进行响应。在 RTMP 协议中,这个行为就是终止连接。
Version Sent (版本已发送):在未初始化状态之后,客户端和服务器都进入 Version Sent(版本已发送) 状态。客户端会等待接收数据包 S1 而服务器在等待 C1。一旦拿到期待的包,客户端会发送数据包 C2 而服务器发送数据包 S2。 (客户端和服务器各自的)状态随即变为Ack Sent (确认已发送 )。
Ack Sent (确认已发送):客户端和服务器分别等待 S2 和 C2。
Handshake Done (握手结束):客户端和服务器可以开始交换消息了。
注意握手区分简单握手和复杂握手
客户端主动建立连接,服务端响应
- 客户端发起连接请求
- 服务器设置客户端的应答窗口大小, 服务器设置客户端的发送带宽大小, 服务器设置客户端的接收块大小
- 服务器响应连接结果
- 客户端设置服务器的接收块大小
1.客户端发起连接请求
message切割成chunk,可以一个chunk接一个chunk传输,接收端将chunk stream id相同的chunk组合成完成的message再返回给应用层。
客户端发送命令消息中的“连接” (connect)到服务器, 请求与一个服务应用实例建立连接。
**format :**决定了RTMP header的长度为多少个字节:
Format取值(2bits) | header的长度 | 说明 |
---|---|---|
0(二进制00) | 12字节 | onMetaData流开始的绝对时间戳控制消息(如connect) |
1(二进制01) | 8字节 | 大部分的rtmp header都是8字节的 |
2(二进制10) | 4字节 | 比较少见 |
3(二进制11) | 1字节 | 偶尔出现,低于8字节频率 |
Chunk stream ID:是区分Chunk(块)的关键
该部分暂时无法准确判断,待确认
2 low level
3 high level(像connect,create_stream一类消息)
4 control stream
5 video
6 audio
8 control stream
message type id:消息类型 8音频,9视频
stream id:消息标识,比如connect 是0,public就是1
Type id: 表示消息类型ID,比如此处0x14表示以AMF0编码(还有AMF3编码,Adobe定义的编码方式)。另外还有如0x04表示用户控制消息,0x05表示Window Acknowledgement Size,0x06表示 Set Peer Bandwith等等
AMF0格式:使用一个字节来表示数据类型。在数据类型后面紧跟着的就是对应类型数据的长度,每一种类型长度字段所占用的字节数可能也不尽相同,比如string类型后面紧跟的是后面字符串的长度,长度占用2个字节。在长度后面就是具体的数值。
Body解析
app是application的缩写,代表客户端要链接到的,rtmp服务器的应用程序,这个一般我们在nginx服务器的配置选项中可以看到。
flashVer表示flash播放器的版本号,同样首先通过一个字符串来表示object的类型;然后再用AMF0的编码格式对具体的值进行编码,此处是flash的版本号,采用字符串表示,值为"LNX 9,0,124,2"。
服务器的URL地址,其格式为 protocol://servername:port/appName/appInstance
fpad是一个布尔值,用来表示是否使用代理。如果使用,值为true,否则值为false。
能力
audioCodes表示支持的音频编码格式,支持的每一种格式用一个bit位来表示,所有的比特位进行或运算得到最终的值。
4071对应的二进制为 0000 1111 1110 0111
4071 = 0x0001 | 0x0002 | 0x0004 | 0x0020 | 0x0040 | 0x0080 | 0x0100 | 0x0200 | 0x0400 | 0x0800
表示4071支持第1,2,3,6,7,8,9,10,11,12种格式
252 = 0x0040 | 0x0080 | 0x0010 | 0x0020 | 0x0040 | 0x0080
252表示支持 第7,8,5,6种格式
2.服务器设置客户端的应答窗口大小
Window Acknowledgement Size用来通知对端,如果收到该大小字节的数据,需要回复一个Acknowledgement消息,也就是ACK。本例中,设置的大小为500000,也就是服务端通知客户端如果收到了50000字节的数据,需要向服务端发送一个ACK的消息,而实际上一般情况一个会话中能达到如此大的数据量比较少,所以我们也看到会回复ACK消息的消息比较少,更多的只是看到设置接收窗口大小的消息(Window Acknowledgement Size)
该条消息表示,在序列号为2512218的时候,rtmp客户端已累计收到5000000个字节的数据,此时向服务端发送一个ACK。
3.服务器设置客户端的发送带宽大小
服务器向客户端发送Set Peer Bandwidth消息,客户端第一次收到Set Peer Bandwidth消息,之前没有发送过Window Acknowledgement Size,所以在这里向服务端发送一次消息
Set Peer Bandwidth消息还是按照RTMP Header + RTMP Body的格式组成。
意思是接受端接收到该消息后会通过设置消息中的Window ACK Size来限制已发送但未接受到反馈的消息的大小来限制发送端的发送带宽。如果消息中的Window ACK Size与上一次发送给发送端的size不同的话要回馈一个Window Acknowledgement Size的控制消息。
RTMP Body由两个字段组成,一个是Window acknowledgement size,占用4个字节;一个是limit type,表示限制的类型,可取的值为0(Hard),1(soft), 2(Dynamic)。本例中采用的是Dynamic。
limity type表示了不同的限制策略:
Hard:收到消息的一端需要按照消息中设置的Window size进行限制;
Soft:收到消息的一端按照消息中设置的Window size或者已经生效的限制进行限制,以两者中较小的为准。
Dynamic:如果之前的类型为Hard,则此消息也为Hard类型,否则忽略该类型。
服务端发送Set Peer Bandwidth消息之后,客户端向服务端发送Window Acknowledgement Size消息,服务端再向客户端发送一条用户控制消息StreamBegin。
RTMP服务器发送StreamBegin以通知客户端流已经可以使用并且可以用于通信。默认情况下,从客户端成功接收到connect命令后,将在ID 0上发送StreamBegin。
4.服务器设置客户端的接收块大小
设置chunk中Data字段所能承载的最大字节数,默认为128B,通信过程中可以通过发送该消息来设置chunk Size的大小(不得小于128B),而且通信双方会各自维护一个chunkSize,两端的chunkSize是独立的。比如当A想向B发送一个200B的Message,但默认的chunkSize是128B,因此就要将该消息拆分为Data分别为128B和72B的两个chunk发送。
5.服务器响应连接结果
客户端收到_result,获取连接状态为Connection succeeded。
connect命令包含’app’参数,它告诉客户端连接到的服务器应用程序名称。接收方处理该命令并发送回具有相同事务ID的响应。响应字符串是_result、_error或方法名称。
6.客户端设置接收块大小
连接成功之后由客户端选择publish还是play,这里讲的是publish。
releaseStream:这个命令告诉服务器,客户端希望释放特定流的资源。一旦调用了这个命令,服务器将不再保持与该流的关联,这意味着服务器可以释放相关的资源,例如网络带宽和内存。这在客户端不再需要播放某个流时非常有用。
FCPublish:这个命令告诉服务器,客户端希望发布(或发送)一个名为"test"的流。这个命令通常在客户端希望将自己的视频流发送到服务器时使用。服务器会为该流分配资源,并准备接收来自客户端的视频数据。
createStream:这个命令用于在服务器上创建一个新的流。一旦服务器收到了这个命令,它会为客户端分配一个唯一的流ID,并返回给客户端。客户端可以使用这个流ID来标识自己的流,并在后续的操作中使用它。
客户端与服务器建立流(stream)进行音视频数据传输
publish publish命令用于告诉服务器客户端想要发布(或发送)一个特定的流。
onStatus 服务器使用“onStatus”命令向客户端发送NetStream状态更新
@setDataFrame() 是用于设置元数据(metadata)的AMF0数据格式的一种方法。
元数据是描述流媒体内容的数据,通常包括视频的宽度、高度、帧率、编码方式等信息,以及音频的采样率、声道数、编码方式等信息。