rtmp協議詳解 (一) handshake

語言: CN / TW / HK

本文所有邏輯均從client出發

本文邏輯已通過golang實現,程式碼地址 rtmp handshark

rtmp協議是什麼

RTMP伺服器搭建可參考:Nginx與Nginx-rtmp-module搭建RTMP影片直播和點播伺服器

實時流協議(Real-TimeMessaging Protocol,RTMP)是用於網際網路上傳輸視音訊資料的網路協議。本API提供了支援RTMP, RTMPT,RTMPE, RTMP RTMPS以及以上幾種協議的變種(RTMPTE, RTMPTS)協議所需的大部分客戶端功能以及少量的伺服器功能。

RTMP是目前各種網路直播應用最核心的傳輸協議,也是互動直播採用最廣泛的協議。

RTMP協議規定,播放一個流媒體有兩個前提步驟:

第一步,建立一個網路連線(NetConnection);

第二步,建立一個網路流(NetStream)。

其中,網路連線代表伺服器端應用程式和客戶端之間基礎的連通關係。網路流代表了傳送多媒體資料的通道。伺服器和客戶端之間只能建立一個網路連線,但是基於該連線可以建立很多網路流。播放一個RTMP協議的流媒體需要經過以下幾個步驟:握手,建立連線,建立流,播放。

RTMP連線都是以握手作為開始的。建立連線階段用於建立客戶端與伺服器之間的“網路連線”;建立流階段用於建立客戶端與伺服器之間的“網路流”;播放階段用於傳輸視音訊資料。

rtmp的握手過程

握手順序

rtmp 連線tcp連線後,需要進行握手操作。

它包含三個固定大小的塊:

客戶端傳送的三個塊命名為 C0,C1,C2;
服務端傳送的三個塊命名為S0,S1,S2。

握手示意圖:

    +-------------+                           +-------------+
    |    Client   |       TCP/IP Network      |    Server   |
    +-------------+            |              +-------------+
          |                    |                     |
    Uninitialized              |               Uninitialized
          |          C0        |                     |
          |------------------->|         C0          |
          |                    |-------------------->|
          |          C1        |                     |
          |------------------->|         S0          |
          |                    |<--------------------|
          |                    |         S1          |
     Version sent              |<--------------------|
          |          S0        |                     |
          |<-------------------|                     |
          |          S1        |                     |
          |<-------------------|                Version sent
          |                    |         C1          |
          |                    |-------------------->|
          |          C2        |                     |
          |------------------->|         S2          |
          |                    |<--------------------|
       Ack sent                |                  Ack Sent
          |          S2        |                     |
          |<-------------------|                     |
          |                    |         C2          |
          |                    |-------------------->|
     Handshake Done            |               Handshake Done
          |                    |                     |
              Pictorial Representation of Handshake

從客戶端來看:

是client先發送c0,c1給服務端,一定要等客戶端收到了s1之後,才能傳送c2。

從服務端來看:

當接收到客戶端的c0之後,服務端傳送s0,s1給客戶端。當接收到客戶端的c1之後,傳送s2給客戶端。

整體看來:

客戶端要等收到S1之後才能傳送C2
客戶端要等收到S2之後才能傳送其他資訊(控制資訊和真實音影片等資料)
服務端要等到收到C0之後傳送S1
服務端必須等到收到C1之後才能傳送S2
服務端必須等到收到C2之後才能傳送其他資訊(控制資訊和真實音影片等資料) 
如果每次傳送一個握手chunk的話握手順序會是這樣:

握手包體

那麼,rtmp的握手包是長什麼樣呢?

rtmp設計之初,0、1、2是相對的,保持一樣的結構體。

c0 s0

C0 和 S0 包由一個位元組組成,下面是 C0/S0 包內的欄位:

     0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+
    |   version     |
    +-+-+-+-+-+-+-+-+
     C0 and S0 bits

version(1 byte):RTMP 的版本,一般為 3(一般情況 寫死即可)。

c1 s1

C1和S1包含兩部分資料:

key和digest,如下:

      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                        time (4 bytes)                         |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                     version (4 bytes)                         |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                         key (764 bytes)                       |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                      digest (764 bytes)                       |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                              C1 and S1 bits

其實按照adobe 官方的,可以不用區分key和digest的步驟,只要能保證 剩下的1528個bt保持唯一就行

Random data (1528 bytes): This field can contain any arbitrary values. Since each endpoint has to distinguish between the response to the handshake it has initiated and the handshake initiated by its peer,this data SHOULD send something sufficiently random. But there is no need for cryptographically-secure randomness, or even dynamic values.

我在adb官網看文件,然後結合redis rtmp實現,一直沒理解協議中加密的東西。

rtmp是adobe在2009公開的,但是rfc裡規定的協議只能播放部分編碼的影片,不能播放h264的影片流,原因就是handshake的原因。handshake分為simple handshake和complex handshake,RFC中只是simple handshake,complex handshake的方式才可以播放h264的流,而且我看到的所有原始碼的實現都是complex handshake的。

c1生成

key與digest順序是不確定的,可能會調整為(time、version、digest、key)

key 結構:

random-data: (offset) bytes
key-data: 128 bytes
random-data: (764 - offset - 128 - 4) bytes
offset: 4 bytes

digest 結構:

offset: 4 bytes
random-data: (offset) bytes
digest-data: 32 bytes
random-data: (764 - 4 - offset - 32) bytes

生成Digest byte邏輯,從文件裡獲取到答案:

  1. 獲取偏移量,預設結構的話,是len(time)+len(version).則為8位
  2. 計算偏移量後4位數的總和,並取餘(728?),最後加上 偏移量和當前的4位.(取餘操作是因為總數可能會超出整體大小),此數用於填充結果(下標起始位)
  3. 進行sha256計算,鹽值為 官方公開的值GenuineFpKey.
  4. 將計算結果填充到原本資料中,用到了步驟2中的起始位

offset: 找到offset那四個位元組分別轉換成數字相加,因為這個是隨機數有可能超過了key的所有長度所有需要取餘數。這個取餘的數就是764位元組的整體長度- 128位元組的Key祕鑰-4位元組的自身長度

key: 這個就是128位元組長度的二進位制串,rtmp伺服器在收到C1的key祕鑰後,會生成S1的祕鑰key。在官方及其各大部落格中介紹的都是OpenSSL中的一個加密演算法傳入C1的key祕鑰從而得到共享祕鑰,把這個共享祕鑰作為S1的key祕鑰

digest:是通過祕鑰算出來的一個32位元組的二進位制串(注意:我這裡寫的是祕鑰,而不是祕鑰key)。

s1校驗

大致計算邏輯如下:

  1. 獲取pos(c1 1 、2)
  2. 生成一個新陣列,將s1 下標pos之前和s1 下標+32之後的資料合併為新陣列
  3. 進行sha256計算,鹽值為 官方公開的值GenuineFmsKey.【此處對應】
  4. 對比結果是否與中間的hash值相等(跳躍的32位)
  5. 不相等的話,將偏移 (1528/2)=> 764+8=>772,重新計算
  6. 第二次失敗後,校驗失敗

s2 c2

先去官網看定義吧:

The C2 and S2 packets are 1536 octets long, and nearly an echo of S1 and C1 (respectively), consisting of the following fields:

0 1 2 3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | time (4 bytes) |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | time2 (4 bytes) |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | random echo |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | random echo |
 | (cont) |
 | .... |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

c2的結構和s1保持一致,客戶端的c2的資料是s1的copy.

s2: 前1504bytes資料隨機生成(前8bytes需要注意),然後對1504bytes資料進行HMACsha256得到digest,將digest放到最後的32bytes即可。

s1校驗需要用到c1的pos位,這裡需要提前儲存。

s2 校驗
  1. 獲取c1的計算的hash
  2. 通過c1計算的hash,結合GenuineFmsKey產生出一個新的digest。
  3. 將s2的前(1536-32)的資料結合2的計算結果,計算出新的hash。
  4. 用新的hash對比s2最後32位(即伺服器返回的hash),匹配則正確

參考資料:

adb rtmp文件

handshark詳解

srs rtmp協議變更

分享到: