十三: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