live555_RTSP連接建立以及請求消息處理過程

轉自:https://www.cnblogs.com/graveliang/p/5688234.html

1,RTSP連接的建立過程
    RTSPServer類用於構建一個RTSP服務器,該類同時在其內部定義了一個RTSPClientSession類,用於處理單獨的客戶會話。
    首先創建RTSP服務器(具體實現類是DynamicRTSPServer),在創建過程中,先建立Socket(ourSocket)在TCP的554端口進行監聽,然後把連接處理函數句柄 

(RTSPServer:: incomingConnectionHandler)和socket句柄傳給任務調度器(taskScheduler)。


    任務調度器把socket句柄放入後面select調用中用到的socket句柄集(fReadSet)中,同時將socket句柄和incomingConnectionHandler句柄關聯起來。接着,主程序開始進入任務調度器的主循環(doEventLoop),在主循環中調用系統函數select阻塞,等待網絡連接。
    當RTSP客戶端輸入(rtsp://192.168.1.109/1.mpg)連接服務器時,select返回對應的scoket,進而根據前面保存的對應關係,可找到對應處理函數句柄,這裏就是前面提到的incomingConnectionHandler了。在incomingConnectionHandler中創建了RTSPClientSession,開始對這個客戶端的會話進行處理。

具體分析如下:

DynamicRTSPServer::creatnew():
   1.調用繼承自RTPSever::setUpOurSocket:
       1.調用GroupsockHelper 的setupStreamSocket創建一個socket連接,並綁定,
       2.設置socket的發送緩存大小,
       3.調用listen開始監聽端口,設置同時最大能處理連接數LISTEN_BACKLOG_SIZE=20,如果達到這個上限則client端將收到ECONNERREFUSED的錯誤
       4.測試綁定端口是否爲0,爲0的話重新綁定斷口,並返回系統自己選擇的新的端口。
       5.返回建立的socket文件描述符

   2.調用自己和RTPSever的構造函數:
   RTPSever構造函數:
       1.用一個UsageEnvironment對象的引用構造其父類Medium
       2.設置最大等待回收連接時間reclamationTestSeconds,超過這個時間從客戶端沒有RTSP命令或者RTSP的RR包則收回其RTSPClientSession
       3.建立一個HashTable(實際上是一個BasicHashTable),fServerMediaSessions指向這個表。
       4.調用參數UsageEnvironment對象env的成員,一個TaskScheduler指針所指對象(實際就是一個BasicTaskScheduler對象)的成員函數
           turnOnBackgroundReadHandling():
               1.調用一個HandlerSet::assignHandler()創建一個Handler,把socketNum【此處爲服務器監聽的socket描述符】和處理函數RTSPServer::incomingConnectionHandler(),還有指向RTSPSever的指針綁定在一起。
                   incomingConnectionHandler作用:
                       1.調用accept返回服務器與客戶端連接的socket描述符
                       2.設置客戶端描述符爲非阻塞
                       3.增加客戶端socket描述符的發送緩存爲50*1024
                       4.爲此客戶端隨機分配一個sessionId
                       5.用客戶端socket描述符clientSocket,sessionId,和客戶端地址clientAddr調用creatNewClientSession創建一個clientSession。


2,請求消息處理過程
    上節我們談到RTSP服務器收到客戶端的連接請求,建立了RTSPClientSession類,處理單獨的客戶會話。在建立 RTSPClientSession的過程中,將新建立的socket句柄(clientSocket)和RTSP請求處理函數句柄RTSPClientSession::incomingRequestHandler傳給任務調度器,由任務調度器對兩者進行一對一關聯。當客戶端發出 RTSP請求後,服務器主循環中的select調用返回,根據socket句柄找到對應的incomingRequestHandler,開始消息處理。先進行消息的解析。

RTSPClientSession::RTSPClientSession()構造函數:
   1.重置請求緩存

   2.調用envir().taskScheduler().turnOnBackgroundReadHandling(),這次socketnumber爲客戶端socket描述符這次的處理函數是RTSPServer::RTSPClientSession::incomingRequestHandler()

       RTSPServer::RTSPClientSession::incomingRequestHandler():
           調用handleAlternativeRequestByte1(uint8_t requestByte):
               1.fRequestBuffer[fRequestBytesAlreadySeen] =requestByte;把請求字符放入請求緩存fRequestBuffer
               2.調用handleRequestBytes(1) 處理請求緩存
                   handleRequestBytes(int newBytesRead):
                       1.調用noteLiveness()查看請求是否到期,如果服務器的reclamationTestSeconds> 0,調用taskScheduler對象的rescheduleDelayedTask函數: 參數爲

( fLivenessCheckTask,fOurServer.fReclamationTestSeconds*1000000,(TaskFunc*)livenessTimeoutTask, this )

其中livenessTimeoutTask()函數作用是刪除new出來的clientSession.
                           1.調用unscheduleDelayedTask(TaskToken&prevTask):
                               從DelayQueue中刪除prevTask項, prevTask置空.
                           2.調用scheduleDelayedTask(int64_t microseconds, 

                                                     TaskFunc* proc, void*clientData): 

                               1.創建一個DelayInterval對象timeToDelay,用microseconds初始化。

                               2.創建一個AlarmHandler對象,用proc, clientData, timeToDelay初始化

                               3.調用fDelayQueue.addEntry(),把這個AlarmHandler對象加入到延遲隊列中

                               4.返回AlarmHandler對象的token[long類型]的指針

                     2.如果請求的的長度超過請求緩存可讀長度fRequestBufferBytesLeft,結束這個連接。

               3.找到請求消息的結尾:。

               4.如果找到消息結尾,調用RTSPServer::RTSPClientSession::handleRequestBytes()[值得關注此函數]把請求字符串轉換成命令各部分包括:cmdName[方法],urlPreSuffix[url地址],urlSuffix[要讀取的文件名],sceq[消息的Cseq],否則函數返回需要繼續從連接中讀取請求。分別存入對應的數組。

               5.如果轉換成功,調用handleCmd_xxx()處理對應的cmdName: xxx[此處實現了:OPTIONS,DESCRIBE,SETUP,TEARDOWN,PLAY,PAUSE,GET_PARAMETER,SET_PARAMETER]
               其中PLAY,PAUSE,GET_PARAMETER,SET_PARAMETER調用handleCmd_withinSession (cmdName,urlPreSuffix, urlSuffix,cseq,(char const*)fRequestBuffer);

               6.清空 RequestBuffer

 

比如:消息解析後,如果發現客戶端的請求是DESCRIBE則進入handleCmd_DESCRIBE函數。RTSP服務器收到客戶端的DESCRIBE請求後,根據請求URL(rtsp://192.168.1.109/1.mpg),找到對應的流媒體資源,返回響應消息。live555中的ServerMediaSession類用來處理會話中描述,它包含多個(音頻或視頻)的子會話描述(ServerMediaSubsession)。根據客戶端請求URL的後綴(例如是1.mpg), 調用成員函數                  DynamicRTSPServer::lookupServerMediaSession查找對應的流媒體信息 ServerMediaSession。(根據urlSuffix查找)。

如果ServerMediaSession不存在,查找文件是否存在,若文件不存在,則判斷ServerMediaSession         (即smsExists)是否存在,如果存在則將其remove(調用removeServerMediaSession方法)。但是如果本地存在1.mpg文件,則根據文件名創建一個新的 ServerMediaSession(調用createNewSMS方法,若在該方法中找不到對應的文件擴展名,則將返回NULL)。

如果通過lookupServerMediaSession返回的是NULL,則向客戶端發送響應消息並將fSessionIsActive置爲FALSE;否則,爲該session組裝一個SDP描述信息(調用generateSDPDescription方法,該方法在ServerMediaSession類中),組裝完成後,生成一個RTSP URL(調用rtspURL方法,該方法在RTSPServer類中)。

創建ServerMediaSession過程中,根據文件後綴.mpg,創建媒體MPEG-1or2的解複用器                   (MPEG1or2FileServerDemux)。再由MPEG1or2FileServerDemux創建一個子會話描述 MPEG1or2DemuxedServerMediaSubsession。最後由ServerMediaSession完成組裝響應消息中的SDP信息(SDP組裝過程見下面的描述),然後將響應消息發給客戶端,完成一次消息交互。

 

===================================================================================================================================

 

RTSP服務器處理客戶端點播的基本流程

 

  處理連接請求的基本流程:

l  Step 1:與客戶端建立RTSP連接(調用incomingConnectionHandler方法),創建ClientSession並關聯fClientSocket與incomingRequestHandler(調用incomingConnectionHandler1)。

 

l  Step 2:接收客戶端請求(調用incomingRequestHandler方法)。

 

l  Step 3:從客戶端Socket讀取數據,並對請求數據(即the request string)進行轉換(調用parseRTSPRequestString方法,該方法在RTSPCommon類中)。

 

l  Step 4:根據分離出來的指令進行分別處理:

n  OPTIONS→handleCmd_OPTIONS 

n  DESCRIBE→handleCmd_DESCRIBE

handleCmd_DESCRIBE這一個方法比較重要,首先根據urlSuffix查找ServerMediaSession是否存在(調用lookupServerMediaSession方法,該方法中通過HashTable來查找)。

在testOnDemandRTSPServer項目工程中,僅僅是通過streamName來確認session是否爲NULL。而在完整的live555MediaServer項目工程中,則是通過DynamicRTSPServer類來處理,其首先是查找文件是否存在,若文件不存在,則判斷ServerMediaSession(即smsExists)是否存在,如果存在則將其remove(調用removeServerMediaSession方法);若文件存在,則根據文件名創建一個ServerMediaSession(調用createNewSMS方法,若在該方法中找不到對應的文件擴展名,則將返回NULL)。

如果通過lookupServerMediaSession返回的是NULL,則向客戶端發送響應消息並將fSessionIsActive置爲FALSE;否則,爲該session組裝一個SDP描述信息(調用generateSDPDescription方法,該方法在ServerMediaSession類中),組裝完成後,生成一個RTSP URL(調用rtspURL方法,該方法在RTSPServer類中)。

 

n  SETUP→handleCmd_SETUP

handleCmd_SETUP方法中,有兩個關鍵的名詞,一個是urlPreSuffix,代表了session name(即stream name);一個是urlSuffix,代表了subsession name(即track name),後面經常用到的streamName和trackId分別與這兩個名詞有關。

接下來會創建session's state,包括incrementReferenceCount等。緊接着,會針對確定的subsession(track)查找相應的信息。接着,在request string查找一個"Transport:" header,目的是爲了從中提取客戶端請求的一些參數(調用parseTransportHeader方法,該方法在RTSPServer類中),如clientsDestinationAddressStr、ClientRTPPortNum等。

再接着是getStreamParameters(該方法在ServerMediaSession類中被定義爲純虛函數並在OnDemandServerMediaSubsession類中被重定義),然後通過fIsMulticast和streamingMode來組裝不同的響應消息。

 

n  PLAY→handleCmd_PLAY:處理播放請求,具體的實現流程請參見後面的步驟。

n  PAUSE→handleCmd_PAUSE:處理暫停請求,在執行了該請求後,最終會調用StopPlaying方法,並將fAreCurrentlyPlaying置爲FALSE。

 

n  TEARDOWN→handleCmd_TEARDOWN:處理停止請求,將fSessionIsActive置爲FALSE。

 

n  GET_PARAMETER→handleCmd_GET_PARAMETER:該方法主要是維持客戶端與服務器通信的生存狀態,just for keep alive。

 

n  SET_PARAMETER→handleCmd_SET_PARAMETER:該方法未針對SET_PARAMETER作實現,使用該方法會調用handleCmd_notSupported方法,並將最終引發與客戶端斷開連接。

 

l  Step 5:根據Step 4的不同指令進行消息響應(調用send方法),該消息響應是即時的。

 

l  Step 6:處理客戶端發送“SETUP”指令後即開始播放的特殊情況。

 

l  Step 7:將RequestBuffer進行重置,以便於爲之後到來的請求做好準備。

 

l  Step 8:檢查fSessionIsActive是否爲FALSE,如果是則刪除當前的ClientSession。

 

 

  處理PLAY的基本流程:

l  Step 1:對rtspURL及相關header的處理,涉及較多的細節。

 

l  Step 2:根據不同的header對流進行縮放比例或定位的處理。

如果爲sawScaleHeader,則進行縮放比例的處理(調用setStreamScale方法,該方法在OnDemandServerMediaSubsession類中實現)。

如果爲sawRangeHeader,則進行尋找流的處理(即是對流進行定位,調用seekStream方法,該方法在OnDemandServerMediaSubsession類中實現;同時,該方法的調用是在初始播放前及播放過程中由於用戶拖動播放進度條而產生的系列請求)。

在OnDemandServerMediaSubsession類中,seekStream方法中調用了seekStreamSource方法,該方法在對應的媒體格式文件的FileServerMediaSubsession類中實現(如針對WAV格式,則在WAVAudioFileServerMediaSubsession類中實現;針對MP3格式,則在MP3AudioFileServerMediaSubsession類中實現)。

同理,OnDemandServerMediaSubsession類中的setStreamScale方法中所調用的setStreamSourceScale方法亦是類似的實現機制。

 

l  Step 3:開始進行流式播放(調用startStream方法,該方法在OnDemandServerMediaSubsession類中實現)。

n  Step 3.1:根據clientSessionId從fDestinationsHashTable中查找到destinations(包括了客戶端的IP地址、RTP端口號、RTCP端口號等信息)。

 

n  Step 3.2:調用startPlaying方法,在該方法中根據RTPSink或UDPSink分別調用startPlaying方法。

如果是調用RTPSink的startPlaying方法,則接着會調用MediaSink類中的startPlaying方法,並在該方法中調用MultiFramedRTPSink類中的continuePlaying方法,之後便是buildAndSendPacket了。這裏已經來到重點了,即是關於不斷讀取Frame並Send的要點。在MultiFramedRTPSink類中,通過buildAndSendPacket、packFrame、afterGettingFrame、afterGettingFrame1、sendPacketIfNecessary和sendNext構成了一個循環圈,數據包的讀取和發送在這裏循環進行着。特別注意的是sendPacketIfNecessary方法中的後面代碼(nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*)sendNext, this);),通過Delay amount of time後,繼續下一個Task,並回過來繼續調用buildAndSendPacket方法。

在packFrame方法中,正常情況下,需要調用getNextFrame方法(該方法在FramedSource類中,並且對不同媒體格式的Frame的獲取出現在FramedSource類的getNextFrame方法中,通過調用doGetNextFrame方法來實現)來獲取新的Frame。

如果是調用UDPSink的startPlaying方法,則接着會調用MediaSink類中的startPlaying方法,並在該方法中調用BasicUDPSink類中的continuePlaying方法。在這之後由若干個方法構成了一個循環圈:continuePlaying1、afterGettingFrame、afterGettingFrame1、sendNext。並在afterGettingFrame1方法中實現了packet的發送(fGS->output(envir(), fGS->ttl(),fOutputBuffer, frameSize);)。

 

Step 3.3:針對RTPSink創建RTCP instance(RTP與RTCP的配套使用決定了其必須這麼做,否則可能就跟直接使用UDP發送數據包沒什麼兩樣了^_^),創建RTCP instance時,將incomingReportHandler句柄作爲BackgroundHandlerProc,以便於處理RTCP的報告,並開始startNetworkReading。這裏RTP/RTCP的使用方式有兩種,一種建立在TCP之上,一種建立在UDP之上。

 

發佈了195 篇原創文章 · 獲贊 53 · 訪問量 53萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章