live555學習筆記13-RTPInterface詳解

十三:RTPInterface詳解

好幾天沒寫blog了。看源碼真累啊,還要把理解的寫到紙上,還要組織混亂的思想,令人頭痛,所以這需要激情。不過,今天激情又來了。

大家應該已理解了GroupSocket這個類。理論上講那些需要操作udp socket 的類應保存GroupSocket的實例。但事實並不是這樣,可以看一下RTPSink,RTPSource,RTCPInstance等,它們都沒有保存GroupSocket型的變量。那它們通過哪個類進行socket操作呢?是RTPInterface!!
這些類接收的GroupSocket指針最後都傳給了 RTPInterface 。爲什麼用RTPInterface而不直接用GroupSocket呢?這裏面有個故事…扯遠了。

要解答這個問題,讓我們先提出問題吧。
首先請問,Live555即支持rtp over udp,又支持rtp over tcp。那麼在rtp over tcp情況下,用 GroupSocket 怎麼實現呢?GroupSocket可是僅僅代表UDP啊!
那麼RTPInterface既然用於網絡讀寫,它就應該既支持tcp收發,也支持udp收發。而且它還要像GroupSocket那樣支持一對多。因爲服務端是一對多個客戶端哦。我們看一下RTPInterface的成員:
Groupsock* fGS;
tcpStreamRecord* fTCPStreams; // optional, for RTP-over-TCP streaming/receiving
嘿嘿,這兩個緊靠着,說明它們關係不一般啊(難道他們有一腿?)。fGS--代表了一個udp socket和它對應的多個目的端,fTCPStreams–代表了多個TCP socket,當然這些socket都是從一個socket accept()出來的客戶端socket(tcpStreamRecord是一個鏈表哦)。
看到這個架式,我想大家都要得出結論了:RTPInterface還真是男女通吃啊!不論你客戶端與我建立的是tcp連接,還是udp連接,我RTPInterface一律能接收你們的數據,並向你們發出數據!
證據一:向所有客戶端發出數據:

Boolean RTPInterface::sendPacket(unsigned char* packet, unsigned packetSize)  
{  
    Boolean success = True; // we'll return False instead if any of the sends fail  
  
    // Normal case: Send as a UDP packet:  
    if (!fGS->output(envir(), fGS->ttl(), packet, packetSize))  
        success = False;  
  
    // Also, send over each of our TCP sockets:  
    for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;  
            streams = streams->fNext) {  
        if (!sendRTPOverTCP(packet, packetSize, streams->fStreamSocketNum,  
                streams->fStreamChannelId)) {  
            success = False;  
        }  
    }  
  
    return success;  
}  

很明顯啊,先發送udp數據,一對多的問題在GroupSocket中解決。再發送tcp數據,一對多的問題本地解決。
證據二:從所有客戶端讀取數據:
我現在找不到直接的證據,所以我就憶想一下吧:當udp端口或tcp端口收到數據時,分析後,是哪個客戶端的數據就發給對應這個客戶端的RTPSink或RTCPInstance。
好像已經把最開始的問題解答完了。下面讓我們來分析一下RTPInterface吧。


```cpp
void RTPInterface::setStreamSocket(int sockNum, unsigned char streamChannelId)  
{  
    fGS->removeAllDestinations();  
    addStreamSocket(sockNum, streamChannelId);  
}  
  
void RTPInterface::addStreamSocket(int sockNum, unsigned char streamChannelId)  
{  
    if (sockNum < 0)  
        return;  
  
    for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;  
            streams = streams->fNext) {  
        if (streams->fStreamSocketNum == sockNum  
                && streams->fStreamChannelId == streamChannelId) {  
            return; // we already have it  
        }  
    }  
  
    fTCPStreams = new tcpStreamRecord(sockNum, streamChannelId, fTCPStreams);  
}  

setStreamSocket()沒必要說了吧,看一下addStreamSocke()。從字面意思應能瞭解,添加一個流式Socket,也就是添加tcp 
socket了。循環中查找是否已經存在了,最後如果不存在,就創建之,在tcpStreamRecord的構造函數中己經把自己加入了鏈表。對於參數,sockNum很易理解,就是socket()返回的那個SOCKET型
數據唄,streamChannelId是什麼呢?我們不防再猜測一下(很奇怪,我每次都能猜對,嘿嘿...):rtp over tcp時,這個tcp連接是直接利用了RTSP所用的那個tcp連接,如果同時有很多rtp 
session,再加上rtsp session,大家都用這一個socket通信,怎麼區分你的還是我的?我想這個channel 
id就是用於解決這個問題。給每個session分配一個唯一的id,在發送自己的包時爲包再加上個頭部,頭部中需要有session的標記--也就是這個channel id,包的長度等等字段。這樣大家就可以穿一條褲子了,術語叫多路複用,但要注意只有tcp才進行多路複用,udp是不用的,因爲udp是一個session對應一個socket(加上RTCP是兩個)。
想像一下,服務端要從這個tcp socket讀寫數據,必須把一個handler加入TaskScheduler中,這個handler在可讀數據時進行讀,在可寫數據時進行寫。在讀數據時,對讀出的數據進行分析,取得數據包的長度,以及其channel id,跟據channel id找到相應的處handler和對象,交給它們去處理自己的數據。
試想兩個建立在tcp上的rtp session,這個兩個tcp socket既擔負着rtsp通訊,又擔負着rtp通訊。如果這兩個rtp session共用一個stream,那麼最終負責這兩個session通信的就只有一個RTPInterface,那麼這個RTPInterface中的fTCPStreams這個鏈表中就會有兩項,分別對應這兩個session。tcpStreamRecord主要用於socket number與channel id的對應。這些tcpStreamRecord是通過addStreamSocket()添加的。處理數據的handler是通過startNetworkReading()添加的,看一下下:

```cpp
void RTPInterface::startNetworkReading(TaskScheduler::BackgroundHandlerProc* handlerProc)  
{  
    // Normal case: Arrange to read UDP packets:  
    envir().taskScheduler().turnOnBackgroundReadHandling(fGS->socketNum(),handlerProc,  
        fOwner);  
  
    // Also, receive RTP over TCP, on each of our TCP connections:  
    fReadHandlerProc = handlerProc;  
    for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;  
            streams = streams->fNext) {  
        // Get a socket descriptor for "streams->fStreamSocketNum":  
        SocketDescriptor* socketDescriptor = lookupSocketDescriptor(envir(),  
                streams->fStreamSocketNum);  
  
        // Tell it about our subChannel:  
        socketDescriptor->registerRTPInterface(streams->fStreamChannelId, this);  
    }  
}  

用UDP時很簡單,直接把處理函數做爲handler加入taskScheduler即可。而TCP時,需向所有的session的socket都註冊自己。可以想像,socketDescriptor代表一個tcp socket,並且它有一個鏈表之類的東西,其中保存了所有的對這個socket感興趣的RTPInterface,同時也記錄了RTPInterface對應的channal id。只有向socketDescriptor註冊了自己,socketDescriptor在讀取數據時,才能跟據分析出的channel id找到對應的RTPInterface,才能調用RTPInterface中的數據處理handler,當然,這個函數也不是RTPInteface自己的,而是從startNetworkReading()這個函數接收到的調用者的。
上述主要講的是一個RTPInterface對應多個客戶端tcp socket的情形。現在又發現一個問題:SocketDescriptor爲什麼需要對應多個RTPInterface呢?上面已經講了,是爲了多路複用,因爲這個socket即負擔rtsp通信又負擔rtp通信還負擔RTCP通信。SocketDescriptor記錄多路複用數據(也就是RTPInterface與channel id)用了一個Hash table:HashTable* fSubChannelHashTable。SocketDescriptor讀數據使用函數:static void tcpReadHandler(SocketDescriptor*, int mask)。證據如下:

void SocketDescriptor::registerRTPInterface(  
unsigned char streamChannelId,  
        RTPInterface* rtpInterface)  
{  
    Boolean isFirstRegistration = fSubChannelHashTable->IsEmpty();  
    fSubChannelHashTable->Add((char const*) (long) streamChannelId,  
            rtpInterface);  
  
    if (isFirstRegistration) {  
        // Arrange to handle reads on this TCP socket:  
        TaskScheduler::BackgroundHandlerProc* handler =   
            (TaskScheduler::BackgroundHandlerProc*) &tcpReadHandler;  
        fEnv.taskScheduler().turnOnBackgroundReadHandling(fOurSocketNum,  
                handler, this);  
    }  
}  

可見在註冊第一個多路複用對象時啓動reand handler。看一下函數主體:

void SocketDescriptor::tcpReadHandler1(int mask)  
{  
    // We expect the following data over the TCP channel:  
    //   optional RTSP command or response bytes (before the first '$' character)  
    //   a '$' character  
    //   a 1-byte channel id  
    //   a 2-byte packet size (in network byte order)  
    //   the packet data.  
    // However, because the socket is being read asynchronously, this data might arrive in pieces.  
  
    u_int8_t c;  
    struct sockaddr_in fromAddress;  
    if (fTCPReadingState != AWAITING_PACKET_DATA) {  
        int result = readSocket(fEnv, fOurSocketNum, &c, 1, fromAddress);  
        if (result != 1) { // error reading TCP socket, or no more data available  
            if (result < 0) { // error  
                fEnv.taskScheduler().turnOffBackgroundReadHandling(  
                        fOurSocketNum); // stops further calls to us  
            }  
            return;  
        }  
    }  
  
    switch (fTCPReadingState) {  
    case AWAITING_DOLLAR: {  
        if (c == '$') {  
            fTCPReadingState = AWAITING_STREAM_CHANNEL_ID;  
        } else {  
            // This character is part of a RTSP request or command, which is handled separately:  
            if (fServerRequestAlternativeByteHandler != NULL) {  
                (*fServerRequestAlternativeByteHandler)(  
                        fServerRequestAlternativeByteHandlerClientData, c);  
            }  
        }  
        break;  
    }  
    case AWAITING_STREAM_CHANNEL_ID: {  
        // The byte that we read is the stream channel id.  
        if (lookupRTPInterface(c) != NULL) { // sanity check  
            fStreamChannelId = c;  
            fTCPReadingState = AWAITING_SIZE1;  
        } else {  
            // This wasn't a stream channel id that we expected.  We're (somehow) in a strange state.  Try to recover:  
            fTCPReadingState = AWAITING_DOLLAR;  
        }  
        break;  
    }  
    case AWAITING_SIZE1: {  
        // The byte that we read is the first (high) byte of the 16-bit RTP or RTCP packet 'size'.  
        fSizeByte1 = c;  
        fTCPReadingState = AWAITING_SIZE2;  
        break;  
    }  
    case AWAITING_SIZE2: {  
        // The byte that we read is the second (low) byte of the 16-bit RTP or RTCP packet 'size'.  
        unsigned short size = (fSizeByte1 << 8) | c;  
  
        // Record the information about the packet data that will be read next:  
        RTPInterface* rtpInterface = lookupRTPInterface(fStreamChannelId);  
        if (rtpInterface != NULL) {  
            rtpInterface->fNextTCPReadSize = size;  
            rtpInterface->fNextTCPReadStreamSocketNum = fOurSocketNum;  
            rtpInterface->fNextTCPReadStreamChannelId = fStreamChannelId;  
        }  
        fTCPReadingState = AWAITING_PACKET_DATA;  
        break;  
    }  
    case AWAITING_PACKET_DATA: {  
        // Call the appropriate read handler to get the packet data from the TCP stream:  
        RTPInterface* rtpInterface = lookupRTPInterface(fStreamChannelId);  
        if (rtpInterface != NULL) {  
            if (rtpInterface->fNextTCPReadSize == 0) {  
                // We've already read all the data for this packet.  
                fTCPReadingState = AWAITING_DOLLAR;  
                break;  
            }  
            if (rtpInterface->fReadHandlerProc != NULL) {  
                rtpInterface->fReadHandlerProc(rtpInterface->fOwner, mask);  
            }  
        }  
        return;  
    }  
    }  
}  

最開始的註釋中解釋了多路複用頭的格式。這一段引起了我的興趣:

case AWAITING_DOLLAR: {  
        if (c == $) {  
            fTCPReadingState = AWAITING_STREAM_CHANNEL_ID;  
        } else {  
            // This character is part of a RTSP request or command, which is handled separately:  
            if (fServerRequestAlternativeByteHandler != NULL) {  
                (*fServerRequestAlternativeByteHandler)(  
                        fServerRequestAlternativeByteHandlerClientData, c);  
            }  
        }  
        break;  
    }  

啊!原來ServerRequestAlternativeByteHandler是用於處理RTSP數據的。也就是從這個socket收到RTSP數據時,調用ServerRequestAlternativeByteHandler。如果收到RTP/RTCP數據時,先查看其channel id,跟據id找到RTPInterface(RTCP也是用了RTPIterface進行通信),設置RTPInterface中與讀緩衝有關的變量,然後當讀到包數據的開始位置時,調用rtpInterface中保存的數據處理handler。還記得吧,rtpInterface中的這個數據處理handler在UDP時也被使用,在這個函數中要做的是讀取一個包的數據,然後處理這個包。而SocketDescriptor把讀取位置置於包數據開始的位置再交給數據處理handler,正好可以使用與UDP相同的數據處理handler!
還有,socketDescriptor們並不屬於任何RTPInterface,而是單獨保存在一個Hash table中,這樣多個RTPInterface都可以註冊到一個socketDescriptor中,以實現多路複用。
總結一下通過RTPInterface,live555不僅實現了rtp over udp,還實現了rtp over tcp,而且還實現了同時即有rtp over tcp,又有rtp over udp!
最後,channel id是從哪裏來的呢?是在RTSP請求中指定的。在哪個請求中呢?自己找去吧。
————————————————
版權聲明:本文爲CSDN博主「sunxiaopengsun」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/sunxiaopengsun/article/details/55272356

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章