天天看點

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

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);)。

繼續閱讀