1. 程式人生 > >RTMP協議播放流程的實現及抓包分析

RTMP協議播放流程的實現及抓包分析

     實時流協議(Real-TimeMessaging Protocol,RTMP)是用於網際網路上傳輸視音訊資料的網路協議。本API提供了支援RTMP, RTMPT,RTMPE, RTMP RTMPS以及以上幾種協議的變種(RTMPTE, RTMPTS)協議所需的大部分客戶端功能以及少量的伺服器功能。RTMP是目前各種網路直播應用最核心的傳輸協議,也是互動直播採用最廣泛的協議。
     RTMP協議規定,播放一個流媒體有兩個前提步驟:第一步,建立一個網路連線(NetConnection);第二步,建立一個網路流(NetStream)。其中,網路連線代表伺服器端應用程式和客戶端之間基礎的連通關係。網路流代表了傳送多媒體資料的通道。伺服器和客戶端之間只能建立一個網路連線,但是基於該連線可以建立很多網路流。播放一個RTMP協議的流媒體需要經過以下幾個步驟:握手,建立連線,建立流,播放。RTMP連線都是以握手作為開始的。建立連線階段用於建立客戶端與伺服器之間的“網路連線”;建立流階段用於建立客戶端與伺服器之間的“網路流”;播放階段用於傳輸視音訊資料。
一 RTMP儲存為FLV
使用librtmp接收RTMP流的函式執行流程圖如下圖所示。
 


InitSockets():初始化Socket
RTMP_Alloc():為結構體“RTMP”分配記憶體。
RTMP_Init():初始化結構體“RTMP”中的成員變數。
RTMP_SetupURL():設定輸入的RTMP連線的URL。
RTMP_Connect():建立RTMP連線,建立一個RTMP協議規範中的NetConnection。
RTMP_ConnectStream():建立一個RTMP協議規範中的NetStream。
RTMP_Read():從伺服器讀取資料。
RTMP_Close():關閉RTMP連線。
RTMP_Free():釋放結構體“RTMP”。
CleanupSockets():關閉Socket。
原始碼:
#include <stdio.h>  
#include "librtmp/rtmp_sys.h"  
#include "librtmp/log.h"  
  
int InitSockets()  
{  
    WORD version;  
    WSADATA wsaData;  
    version = MAKEWORD(1, 1);  
    return (WSAStartup(version, &wsaData) == 0);  
}  
  
void CleanupSockets()  
{  
    WSACleanup();  
}  
  
int main(int argc, char* argv[])  
{  
    InitSockets();  
      
    double duration=-1;  
    int nRead;  
    //is live stream ?  
    bool bLiveStream=true;                
      
      
    int bufsize=1024*1024*10;             
    char *buf=(char*)malloc(bufsize);  
    memset(buf,0,bufsize);  
    long countbufsize=0;  
      
    FILE *fp=fopen("receive.flv","wb");  
    if (!fp){  
        RTMP_LogPrintf("Open File Error.\n");  
        CleanupSockets();  
        return -1;  
    }  
      
    /* set log level */  
    //RTMP_LogLevel loglvl=RTMP_LOGDEBUG;  
    //RTMP_LogSetLevel(loglvl);  
  
    RTMP *rtmp=RTMP_Alloc();  
    RTMP_Init(rtmp);  
    //set connection timeout,default 30s  
    rtmp->Link.timeout=10;     
    // HKS's live URL  
    if(!RTMP_SetupURL(rtmp,"rtmp://live.hkstv.hk.lxdns.com/live/hks"))  
    {  
        RTMP_Log(RTMP_LOGERROR,"SetupURL Err\n");  
        RTMP_Free(rtmp);  
        CleanupSockets();  
        return -1;  
    }  
    if (bLiveStream){  
        rtmp->Link.lFlags|=RTMP_LF_LIVE;  
    }  
      
    //1hour  
    RTMP_SetBufferMS(rtmp, 3600*1000);        
      
    if(!RTMP_Connect(rtmp,NULL)){  
        RTMP_Log(RTMP_LOGERROR,"Connect Err\n");  
        RTMP_Free(rtmp);  
        CleanupSockets();  
        return -1;  
    }  
  
    if(!RTMP_ConnectStream(rtmp,0)){  
        RTMP_Log(RTMP_LOGERROR,"ConnectStream Err\n");  
        RTMP_Close(rtmp);  
        RTMP_Free(rtmp);  
        CleanupSockets();  
        return -1;  
    }  
  
    while(nRead=RTMP_Read(rtmp,buf,bufsize)){  
        fwrite(buf,1,nRead,fp);  
  
        countbufsize+=nRead;  
        RTMP_LogPrintf("Receive: %5dByte, Total: %5.2fkB\n",nRead,countbufsize*1.0/1024);  
    }  
  
    if(fp)  
        fclose(fp);  
  
    if(buf){  
        free(buf);  
    }  
  
    if(rtmp){  
        RTMP_Close(rtmp);  
        RTMP_Free(rtmp);  
        CleanupSockets();  
        rtmp=NULL;  
    }     
    return 0;  
}  
二 RTMP協議播放流程抓包分析
1  Wireshark抓RTMP包
用wireshark抓取RTMP包,開啟如下:

2 握手(Handshake)
一個RTMP連線以握手開始,我們先看下圖:

首先我們要明確的是客戶端IP是192.168.1.50(我的電腦),192.168.1.123是RTMP伺服器。
劇本應該是這樣子的:
1.RTMP協議是TCP協議的上層協議,所以必須要先建立TCP連線,所以就看到了1-4這幾個TCP三次握手的包。
2.客戶端向伺服器傳送C0塊(chunks),表示要和伺服器握手,C0中包含版本號。
3.伺服器收到C0後,檢查C0中的版本是否支援,如果支援傳送S0作為響應,否則應該終止連線。
4.客戶端和伺服器都分別等待C1和S1,等待版本確認。
5.客戶端收到S1後傳送C2,伺服器收到C1後傳送S2(確認傳送,測試握手完成。)
然而,協議的實際執行卻不是按照劇本來的(如果按劇本來,延遲就要大大增大了),實際執行是這樣的:
1.RTMP協議是TCP協議的上層協議,所以必須要先建立TCP連線,所以就看到了1-4這幾個TCP三次握手的包。
2.客戶端傳送的是C0+C1塊,直接告訴伺服器我發的版本我自己確認了。
3.伺服器更狠,一個大嘴巴子就抽回來了(傳送S0+S1+S2)。
4.客戶端收到後,傳送C2,握手完成!
附上RTMP協議中的流程圖:

3 建立一個網路連線(NetConnection)
提示:網路連線代表伺服器應用程式和客戶端之間基礎的連通關係
我們接著看抓到的包:

RTMP握手完成後,要建立網路連線。大家都知道一個普通的標準的rtmp流是什麼樣子的?rtmp://IP:PORT/APP/Stream 是不是這樣?
實際劇本是這樣子滴:
1.客戶端在傳送C2的時候,順帶還發了一個請求連線的命令,要求與伺服器應用建立網路連線,這就是RTMP URL中的的Application。soga,是不是恍然大悟?
2.伺服器在收到客戶端傳送的連線請求後傳送如下資訊:

主要是告訴客戶端確認視窗大小,設定節點頻寬,然後伺服器把“連線”連線到指定的應用並返回結果,“網路連線成功”。並且返回流開始的的訊息(Stream Begin 0)。
3.客戶端在收到伺服器發來的訊息後,返回確認視窗大小,此時網路連線建立完成。
協議流程圖:

4 建立一個網路流(NetStream)
提示:網路流代表了傳送多媒體資料的通道。伺服器和客戶端之間只能建立一個網路連線,且多個網路流可以複用這一個網路連線。
接著看抓包:

現在地洞挖好了,就差鋪鐵軌了!
1.客戶端向伺服器傳送請求建立流(createStream)。
2.伺服器收到請求後向客戶端傳送_result(),對建立流的訊息進行響應。此時NetStream建立完成。
協議流程圖:

5 播放
提示:主要功能:傳輸音視訊資料
看抓包:

萬事具備,只欠東風了。
1.客戶端向伺服器傳送播放命令,請求播放stream,並設定Buffer Length 1,3000ms。
2.伺服器收到請求後,向客戶端傳送設定塊大小的協議訊息,並且還附加了一堆其他的訊息一起傳送:

包括 Stream Begin(告知客戶端流ID為0)、NetStream.Play.Start( 告知客戶端播放成功)等。
3. 伺服器向客戶端傳送推流通知,並附帶元資料資訊(解析度、幀率、音訊取樣率、音訊位元速率等等)和視訊、音訊資料。此時客戶端就可以開始正常播放rtmp流了。 協議流程圖:

Reference:
http://blog.csdn.net/leixiaohua1020/article/details/42104893
http://blog.csdn.net/wishfly/article/details/52965787
http://blog.csdn.net/shangmingyang/article/details/50837852
http://blog.chinaunix.net/uid-17102734-id-3986995.html
http://befo.io/306.html
--------------------- 
作者:DaveBobo 
來源:CSDN 
原文:https://blog.csdn.net/davebobo/article/details/76557596 
版權宣告:本文為博主原創文章,轉載請附上博文連結!