live555学习1(转)

来源:互联网 发布:大数据时代与个人隐私 编辑:程序博客网 时间:2024/05/22 00:49

摘自:http://blog.csdn.net/nkmnkm/article/details/6906055(对live555感兴趣的friends可以加群:3744371)

一直想研究live555,没有时间,终于因为项目的原因可以深入无间地研究一下了.所以在此著文以记之.


一 如何编译live555

利用mingw环境很容易:在live555文件夹下,

genMakefiles mingw

make

即可.


可以用genWindowsMakefiles.cmd生成VC可用的makefile,但是对比较新的vc版本支持不好,需要自己改很多东西.

用VC编译有一种更好的办法:

手动为每个库都生成一个lib项目,为mediaServer生成exe项目,设置好各库之间的依赖关系,就可以用VC编译了.由于live555代码中没有单独支持gcc的东西,所以编译是相当的容易.这样就可以用VC编译和调试了.


我现在怕麻烦,只用eclipse+mingw.eclipse的调试也很好用了.

 

二 基础类

讲几个重要的基础类:

BasicUsageEnvironment和UsageEnvironment中的类都是用于整个系统的基础功能类.比如UsageEnvironment代表了整个系统运行的环境,它提供了错误记录和错误报告的功能,无论哪一个类要输出错误,就需要保存UsageEnvironment的指针.而TaskScheduler则提供了任务调度功能.整个程序的运行发动机就是它,它调度任务,执行任务(任务就是一个函数).TaskScheduler由于在全局中只有一个,所以保存在了UsageEnvironment中.而所有的类又都保存了UsageEnvironment的指针,所以谁想把自己的任务加入调度中,那是很容易的.在此还看到一个结论:整个live555(服务端)只有一个线程.


类HashTable:不用多说,实现了哈稀表.

类DelayQueue:译为"延迟队列",它是一个队列,每一项代表了一个要调度的任务(在它的fToken变量中保存).同时保存了这个任务离执行时间点的剩余时间.可以预见,它就是在TaskScheduler中用于管理调度任务的东西.注意,此队列中的任务只被执行一次!执行完后这一项即被无情抛弃!


类HandlerSet:Handler集合.Handler是什么呢?它是一种专门用于执行socket操作的任务(函数),HandlerSet被TaskScheduler用来管理所有的socket任务(增删改查).所以TaskScheduler中现在已调度两种任务了:socket任务(handlerSet)和延迟任务(DelayQueue).其实TaskScheduler还调度第三种任务:Event,介个后面再说.


类Groupsock:这个是放在单独的库Groupsock中。它封装了socket操作,增加了多播放支持和一对多单播的功能.但我只看到它对UDP的支持,好像不支持TCP。它管理着一个本地socket和多个目的地址,因为是UDP,所以只需知道对方地址和端口即可发送数据。Groupsock的构造函数有一个参数是struct in_addr const& groupAddr,在构造函数中首先会调用父类构造函数创建socket对象,然后判断这个地址,若是多播地址,则加入多播组。Groupsock的两个成员变量destRecord* fDests和DirectedNetInterfaceSet fMembers都表示目的地址集和,但我始终看不出DirectedNetInterfaceSet fMembers有什么用,且DirectedNetInterfaceSet是一个没有被继承的虚类,看起来fMembers没有什么用。仅fDesk也够用了,在addDestination()和removeDestination()函数中就是操作fDesk,添加或删除目的地址。

解释一下Groupsock::changeDestinationParameters()函数:

[cpp] view plaincopyprint?
  1. //改变目的地址的参数   
  2. //newDestAddr是新的目的地址   
  3. //newDestPort是新的目的端口   
  4. //newDestTTL是新的TTL   
  5. void Groupsock::changeDestinationParameters(  
  6.         struct in_addr const& newDestAddr,  
  7.         Port newDestPort,  
  8.         int newDestTTL)  
  9. {  
  10.     if (fDests == NULL)  
  11.         return;  
  12.   
  13.     //获取第一个目的地址(此处不是很明白:fDest是一个单向链表,每次添加一个目的地址,  
  14.     //都会把它插入到最前目,难道这个函数仅改变最后一个添加的目的地址?)   
  15.     struct in_addr destAddr = fDests->fGroupEId.groupAddress();  
  16.     if (newDestAddr.s_addr != 0) {  
  17.         if (newDestAddr.s_addr != destAddr.s_addr  
  18.                 && IsMulticastAddress(newDestAddr.s_addr))  
  19.         {  
  20.             //如果目的地址是一个多播地址,则离开老的多播组,加入新的多播组。  
  21.             socketLeaveGroup(env(), socketNum(), destAddr.s_addr);  
  22.             socketJoinGroup(env(), socketNum(), newDestAddr.s_addr);  
  23.         }  
  24.         destAddr.s_addr = newDestAddr.s_addr;  
  25.     }  
  26.   
  27.     portNumBits destPortNum = fDests->fGroupEId.portNum();  
  28.     if (newDestPort.num() != 0) {  
  29.         if (newDestPort.num() != destPortNum &&  
  30.                 IsMulticastAddress(destAddr.s_addr))  
  31.         {  
  32.             //如果端口也不一样,则先更改本身socket的端口   
  33.             //(其实是关掉原先的socket的,再以新端口打开一个socket)。  
  34.             changePort(newDestPort);  
  35.             //然后把新的socket加入到新的多播组。  
  36.             // And rejoin the multicast group:  
  37.             socketJoinGroup(env(), socketNum(), destAddr.s_addr);  
  38.         }  
  39.         destPortNum = newDestPort.num();  
  40.         fDests->fPort = newDestPort;  
  41.     }  
  42.   
  43.     u_int8_t destTTL = ttl();  
  44.     if (newDestTTL != ~0)  
  45.         destTTL = (u_int8_t) newDestTTL;  
  46.   
  47.     //目标地址的所有信息都在fGroupEId中,所以改变成员fGroupEId。  
  48.     fDests->fGroupEId = GroupEId(destAddr, destPortNum, destTTL);  
  49.       
  50.     //(看起来这个函数好像只用于改变多播时的地址参数,   
  51.     //以上分析是否合理,肯请高人指点)   
  52. }  

 

 

三 消息循环

看服端的主体:live555MediaServer.cpp中的main()函数,可见其创建一个RTSPServer类实例后,即进入一个函数env->taskScheduler().doEventLoop()中,看名字很明显是一个消息循坏,执行到里面后不停地转圈,生名不息,转圈不止。那么在这个人生的圈圈中如何实现RTSP服务和RTP传输呢?别想那么远了,还是先看这个圈圈中实现了什么功能吧。

[cpp] view plaincopyprint?
  1. void BasicTaskScheduler0::doEventLoop(char* watchVariable) {  
  2.     // Repeatedly loop, handling readble sockets and timed events:  
  3.     while (1) {  
  4.         if (watchVariable != NULL && *watchVariable != 0)  
  5.             break;  
  6.         SingleStep();  
  7.     }  
  8. }  

BasicTaskScheduler0从TaskScheduler派生,所以还是一个任务调度对象,所以依然说明任务调度对象是整个程序的发动机。

循环中每次走一步:SingleStep()。这走一步中都做些什么呢?

总结为以下四步:

1为所有需要操作的socket执行select。

2找出第一个应执行的socket任务(handler)并执行之。

3找到第一个应响应的事件,并执行之。

4找到第一个应执行的延迟任务并执行之。

可见,每一步中只执行三个任务队列中的一项。下面详细分析函数SingleStep():

[cpp] view plaincopyprint?
  1. //循坏中主要执行的函数   
  2. void BasicTaskScheduler::SingleStep(unsigned maxDelayTime) {  
  3.     fd_set readSet = fReadSet; // make a copy for this select() call  
  4.     fd_set writeSet = fWriteSet; // ditto   
  5.     fd_set exceptionSet = fExceptionSet; // ditto  
  6.   
  7.     //计算select socket们时的超时时间。   
  8.     DelayInterval const& timeToDelay = fDelayQueue.timeToNextAlarm();  
  9.     struct timeval tv_timeToDelay;  
  10.     tv_timeToDelay.tv_sec = timeToDelay.seconds();  
  11.     tv_timeToDelay.tv_usec = timeToDelay.useconds();  
  12.     // Very large "tv_sec" values cause select() to fail.  
  13.     // Don't make it any larger than 1 million seconds (11.5 days)  
  14.     const long MAX_TV_SEC = MILLION;  
  15.     if (tv_timeToDelay.tv_sec > MAX_TV_SEC) {  
  16.         tv_timeToDelay.tv_sec = MAX_TV_SEC;  
  17.     }  
  18.     // Also check our "maxDelayTime" parameter (if it's > 0):  
  19.     if (maxDelayTime > 0  
  20.             && (tv_timeToDelay.tv_sec > (long) maxDelayTime / MILLION  
  21.                     || (tv_timeToDelay.tv_sec == (long) maxDelayTime / MILLION  
  22.                             && tv_timeToDelay.tv_usec  
  23.                                     > (long) maxDelayTime % MILLION))) {  
  24.         tv_timeToDelay.tv_sec = maxDelayTime / MILLION;  
  25.         tv_timeToDelay.tv_usec = maxDelayTime % MILLION;  
  26.     }  
  27.   
  28.     //先执行socket的select操作,以确定哪些socket任务(handler)需要执行。  
  29.     int selectResult = select(fMaxNumSockets,  
  30.             &readSet, &writeSet,&exceptionSet,  
  31.             &tv_timeToDelay);  
  32.   
  33.     if (selectResult < 0) {  
  34. //#if defined(__WIN32__) || defined(_WIN32)  
  35.         int err = WSAGetLastError();  
  36.         // For some unknown reason, select() in Windoze sometimes fails with WSAEINVAL if  
  37.         // it was called with no entries set in "readSet".  If this happens, ignore it:  
  38.         if (err == WSAEINVAL && readSet.fd_count == 0) {  
  39.             err = EINTR;  
  40.             // To stop this from happening again, create a dummy socket:  
  41.             int dummySocketNum = socket(AF_INET, SOCK_DGRAM, 0);  
  42.             FD_SET((unsigned) dummySocketNum, &fReadSet);  
  43.         }  
  44.         if (err != EINTR) {  
  45. //#else   
  46. //      if (errno != EINTR && errno != EAGAIN) {  
  47. //#endif   
  48.             // Unexpected error - treat this as fatal:  
  49. //#if !defined(_WIN32_WCE)   
  50. //          perror("BasicTaskScheduler::SingleStep(): select() fails");  
  51. //#endif   
  52.             internalError();  
  53.         }  
  54.     }  
  55.   
  56.     // Call the handler function for one readable socket:  
  57.     HandlerIterator iter(*fHandlers);  
  58.     HandlerDescriptor* handler;  
  59.     // To ensure forward progress through the handlers, begin past the last  
  60.     // socket number that we handled:   
  61.     if (fLastHandledSocketNum >= 0) {  
  62.         //找到上次执行的socket handler的下一个   
  63.         while ((handler = iter.next()) != NULL) {  
  64.             if (handler->socketNum == fLastHandledSocketNum)  
  65.                 break;  
  66.         }  
  67.         if (handler == NULL) {  
  68.             fLastHandledSocketNum = -1;  
  69.             iter.reset(); // start from the beginning instead  
  70.         }  
  71.     }  
  72.   
  73.     //从找到的handler开始,找一个可以执行的handler,不论其状态是可读,可写,还是出错,执行之。  
  74.     while ((handler = iter.next()) != NULL) {  
  75.         int sock = handler->socketNum; // alias  
  76.         int resultConditionSet = 0;  
  77.         if (FD_ISSET(sock, &readSet)  
  78.                 && FD_ISSET(sock, &fReadSet)/*sanity check*/)  
  79.             resultConditionSet |= SOCKET_READABLE;  
  80.         if (FD_ISSET(sock, &writeSet)  
  81.                 && FD_ISSET(sock, &fWriteSet)/*sanity check*/)  
  82.             resultConditionSet |= SOCKET_WRITABLE;  
  83.         if (FD_ISSET(sock, &exceptionSet)  
  84.                 && FD_ISSET(sock, &fExceptionSet)/*sanity check*/)  
  85.             resultConditionSet |= SOCKET_EXCEPTION;  
  86.         if ((resultConditionSet & handler->conditionSet)  
  87.                 != 0 && handler->handlerProc != NULL) {  
  88.             fLastHandledSocketNum = sock;  
  89.             // Note: we set "fLastHandledSocketNum" before calling the handler,  
  90.             // in case the handler calls "doEventLoop()" reentrantly.  
  91.             (*handler->handlerProc)(handler->clientData, resultConditionSet);  
  92.             break;  
  93.         }  
  94.     }  
  95.   
  96.     //如果寻找完了依然没有执行任何handle,则从头再找。   
  97.     if (handler == NULL && fLastHandledSocketNum >= 0) {  
  98.         // We didn't call a handler, but we didn't get to check all of them,  
  99.         // so try again from the beginning:  
  100.         iter.reset();  
  101.         while ((handler = iter.next()) != NULL) {  
  102.             int sock = handler->socketNum; // alias  
  103.             int resultConditionSet = 0;  
  104.             if (FD_ISSET(sock, &readSet)&& FD_ISSET(sock, &fReadSet)/*sanity check*/)  
  105.                 resultConditionSet |= SOCKET_READABLE;  
  106.             if (FD_ISSET(sock, &writeSet)&& FD_ISSET(sock, &fWriteSet)/*sanity check*/)  
  107.                 resultConditionSet |= SOCKET_WRITABLE;  
  108.             if (FD_ISSET(sock, &exceptionSet)   && FD_ISSET(sock, &fExceptionSet)/*sanity check*/)  
  109.                 resultConditionSet |= SOCKET_EXCEPTION;  
  110.             if ((resultConditionSet & handler->conditionSet)  
  111.                     != 0 && handler->handlerProc != NULL) {  
  112.                 fLastHandledSocketNum = sock;  
  113.                 // Note: we set "fLastHandledSocketNum" before calling the handler,  
  114.                 // in case the handler calls "doEventLoop()" reentrantly.  
  115.                 (*handler->handlerProc)(handler->clientData, resultConditionSet);  
  116.                 break;  
  117.             }  
  118.         }  
  119.   
  120.         //依然没有找到可执行的handler。   
  121.         if (handler == NULL)  
  122.             fLastHandledSocketNum = -1; //because we didn't call a handler  
  123.     }  
  124.   
  125.     //响应事件   
  126.     // Also handle any newly-triggered event  
  127.     // (Note that we do this *after* calling a socket handler,  
  128.     // in case the triggered event handler modifies The set of readable sockets.)  
  129.     if (fTriggersAwaitingHandling != 0) {  
  130.         if (fTriggersAwaitingHandling == fLastUsedTriggerMask) {  
  131.             // Common-case optimization for a single event trigger:  
  132.             fTriggersAwaitingHandling = 0;  
  133.             if (fTriggeredEventHandlers[fLastUsedTriggerNum] != NULL) {  
  134.                 //执行一个事件处理函数   
  135.                 (*fTriggeredEventHandlers[fLastUsedTriggerNum])(fTriggeredEventClientDatas[fLastUsedTriggerNum]);  
  136.             }  
  137.         } else {  
  138.             // Look for an event trigger that needs handling  
  139.             // (making sure that we make forward progress through all possible triggers):  
  140.             unsigned i = fLastUsedTriggerNum;  
  141.             EventTriggerId mask = fLastUsedTriggerMask;  
  142.   
  143.             do {  
  144.                 i = (i + 1) % MAX_NUM_EVENT_TRIGGERS;  
  145.                 mask >>= 1;  
  146.                 if (mask == 0)  
  147.                     mask = 0x80000000;  
  148.   
  149.                 if ((fTriggersAwaitingHandling & mask) != 0) {  
  150.                     //执行一个事件响应   
  151.                     fTriggersAwaitingHandling &= ~mask;  
  152.                     if (fTriggeredEventHandlers[i] != NULL) {  
  153.                         (*fTriggeredEventHandlers[i])(fTriggeredEventClientDatas[i]);  
  154.                     }  
  155.   
  156.                     fLastUsedTriggerMask = mask;  
  157.                     fLastUsedTriggerNum = i;  
  158.                     break;  
  159.                 }  
  160.             } while (i != fLastUsedTriggerNum);  
  161.         }  
  162.     }  
  163.   
  164.     //执行一个最迫切的延迟任务。   
  165.     // Also handle any delayed event that may have come due.  
  166.     fDelayQueue.handleAlarm();  
  167. }  

 

 

四 计划任务(TaskScheduler)深入探讨

我们且把三种任务命名为:socket handler,event handler,delay task

这三种任务的特点是,前两个加入执行队列后会一直存在,而delay task在执行完一次后会立即弃掉。

socket handler保存在队列BasicTaskScheduler0::HandlerSet* fHandlers中;

event handler保存在数组BasicTaskScheduler0::TaskFunc * fTriggeredEventHandlers[MAX_NUM_EVENT_TRIGGERS]中;

delay task保存在队列BasicTaskScheduler0::DelayQueue fDelayQueue中。


下面看一下三种任务的执行函数的定义:
socket handler为
typedef void BackgroundHandlerProc(void* clientData, int mask);
event handler为
typedef void TaskFunc(void* clientData);
delay task 为
typedef void TaskFunc(void* clientData);//跟event handler一样。

再看一下向任务调度对象添加三种任务的函数的样子:
delay task为:
void setBackgroundHandling(int socketNum, int conditionSet ,BackgroundHandlerProc* handlerProc, void* clientData)
event handler为:
EventTriggerId createEventTrigger(TaskFunc* eventHandlerProc)
delay task为:
TaskToken scheduleDelayedTask(int64_t microseconds, TaskFunc* proc,void* clientData)

socket handler添加时为什么需要那些参数呢?socketNum是需要的,因为要select socket(socketNum即是socket()返回的那个socket对象)。conditionSet也是需要的,它用于表明socket在select时查看哪种装态,是可读?可写?还是出错?proc和clientData这两个参数就不必说了(真有不明白的吗?)。再看BackgroundHandlerProc的参数,socketNum不必解释,mask是什么呢?它正是对应着conditionSet,但它表明的是select之后的结果,比如一个socket可能需要检查其读/写状态,而当前只能读,不能写,那么mask中就只有表明读的位被设置。

event handler是被存在数组中。数组大小固定,是32项,用EventTriggerId来表示数组中的项,EventTriggerId是一个32位整数,因为数组是32项,所以用EventTriggerId中的第n位置1表明对应数组中的第n项。成员变量fTriggersAwaitingHandling也是EventTriggerId类型,它里面置1的那些位对应了数组中所有需要处理的项。这样做节省了内存和计算,但降低了可读性,呵呵,而且也不够灵活,只能支持32项或64项,其它数量不被支持。以下是函数体

[cpp] view plaincopyprint?
  1. EventTriggerId BasicTaskScheduler0::createEventTrigger( TaskFunc* eventHandlerProc)  
  2. {  
  3. unsigned i = fLastUsedTriggerNum;  
  4. EventTriggerId mask = fLastUsedTriggerMask;  
  5.   
  6. //在数组中寻找一个未使用的项,把eventHandlerProc分配到这一项。   
  7. do {  
  8. i = (i + 1) % MAX_NUM_EVENT_TRIGGERS;  
  9. mask >>= 1;  
  10. if (mask == 0)  
  11. mask = 0x80000000;  
  12.   
  13. if (fTriggeredEventHandlers[i] == NULL) {  
  14. // This trigger number is free; use it:   
  15. fTriggeredEventHandlers[i] = eventHandlerProc;  
  16. fTriggeredEventClientDatas[i] = NULL; // sanity  
  17.   
  18. fLastUsedTriggerMask = mask;  
  19. fLastUsedTriggerNum = i;  
  20.   
  21. return mask; //分配成功,返回值表面了第几项  
  22. }  
  23. while (i != fLastUsedTriggerNum);//表明在数组中循环一圈  
  24.   
  25. //数组中的所有项都被占用,返回表明失败。   
  26. // All available event triggers are allocated; return 0 instead:  
  27. return 0;  
  28. }  


可以看到最多添加32个事件,且添加事件时没有传入clientData参数。这个参数在触发事件时传入,见以下函数:

[cpp] view plaincopyprint?
  1. void BasicTaskScheduler0::triggerEvent(EventTriggerId eventTriggerId,void* clientData)   
  2. {  
  3. // First, record the "clientData":  
  4. if (eventTriggerId == fLastUsedTriggerMask) {   
  5. // common-case optimization:直接保存下clientData  
  6. fTriggeredEventClientDatas[fLastUsedTriggerNum] = clientData;  
  7. else {  
  8. //从头到尾查找eventTriggerId对应的项,保存下clientData   
  9. EventTriggerId mask = 0x80000000;  
  10. for (unsigned i = 0; i < MAX_NUM_EVENT_TRIGGERS; ++i) {  
  11. if ((eventTriggerId & mask) != 0) {  
  12. fTriggeredEventClientDatas[i] = clientData;  
  13.   
  14. fLastUsedTriggerMask = mask;  
  15. fLastUsedTriggerNum = i;  
  16. }  
  17. mask >>= 1;  
  18. }  
  19. }  
  20.   
  21. // Then, note this event as being ready to be handled.  
  22. // (Note that because this function (unlike others in the library)   
  23. // can be called from an external thread, we do this last, to  
  24. // reduce the risk of a race condition.)   
  25. //利用fTriggersAwaitingHandling以bit mask的方式记录需要响应的事件handler们。  
  26. fTriggersAwaitingHandling |= eventTriggerId;  
  27. }  

看,clientData被传入了,这表明clientData在每次触发事件时是可以变的。

此时再回去看SingleStep()是不是更明了了?

delay task添加时,需要传入task延迟等待的微秒(百万分之一秒)数(第一个参数),这个弱智也可以理解吧?嘿嘿。分析一下介个函数:

[cpp] view plaincopyprint?
  1. TaskToken BasicTaskScheduler0::scheduleDelayedTask(int64_t microseconds,TaskFunc* proc, void* clientData)   
  2. {  
  3. if (microseconds < 0)  
  4. microseconds = 0;  
  5. //DelayInterval 是表示时间差的结构   
  6. DelayInterval timeToDelay((long) (microseconds / 1000000),(long) (microseconds % 1000000));  
  7. //创建delayQueue中的一项   
  8. AlarmHandler* alarmHandler = new AlarmHandler(proc, clientData,timeToDelay);  
  9. //加入DelayQueue   
  10. fDelayQueue.addEntry(alarmHandler);  
  11. //返回delay task的唯一标志   
  12. return (void*) (alarmHandler->token());  
  13. }  
  14.   
  15. delay task的执行都在函数fDelayQueue.handleAlarm()中,handleAlarm()在类DelayQueue中实现。看一下handleAlarm():  
  16. void DelayQueue::handleAlarm()   
  17. {  
  18. //如果第一个任务的执行时间未到,则同步一下(重新计算各任务的等待时间)。   
  19. if (head()->fDeltaTimeRemaining != DELAY_ZERO)  
  20. synchronize();  
  21. //如果第一个任务的执行时间到了,则执行第一个,并把它从队列中删掉。  
  22. if (head()->fDeltaTimeRemaining == DELAY_ZERO) {  
  23. // This event is due to be handled:  
  24. DelayQueueEntry* toRemove = head();  
  25. removeEntry(toRemove); // do this first, in case handler accesses queue  
  26. //执行任务,执行完后会把这一项销毁。   
  27. toRemove->handleTimeout();  
  28. }  
  29. }  

可能感觉奇怪,其它的任务队列都是先搜索第一个应该执行的项,然后再执行,这里干脆,直接执行第一个完事。那就说明第一个就是最应该执行的一个吧?也就是等待时间最短的一个吧?那么应该在添加任务时,将新任务跟据其等待时间插入到适当的位置而不是追加到尾巴上吧?猜得对不对还得看fDelayQueue.addEntry(alarmHandler)这个函数是怎么执行的。

[cpp] view plaincopyprint?
  1. void DelayQueue::addEntry(DelayQueueEntry* newEntry)   
  2. {  
  3. //重新计算各项的等待时间   
  4. synchronize();  
  5.   
  6. //取得第一项   
  7. DelayQueueEntry* cur = head();  
  8. //从头至尾循环中将新项与各项的等待时间进行比较   
  9. while (newEntry->fDeltaTimeRemaining >= cur->fDeltaTimeRemaining) {  
  10. //如果新项等待时间长于当前项的等待时间,则减掉当前项的等待时间。   
  11. //也就是后面的等待时几只是与前面项等待时间的差,这样省掉了记录插入时的时间的变量。  
  12. newEntry->fDeltaTimeRemaining -= cur->fDeltaTimeRemaining;  
  13. //下一项   
  14. cur = cur->fNext;  
  15. }  
  16.   
  17. //循环完毕,cur就是找到的应插它前面的项,那就插它前面吧  
  18. cur->fDeltaTimeRemaining -= newEntry->fDeltaTimeRemaining;  
  19.   
  20. // Add "newEntry" to the queue, just before "cur":  
  21. newEntry->fNext = cur;  
  22. newEntry->fPrev = cur->fPrev;  
  23. cur->fPrev = newEntry->fPrev->fNext = newEntry;  
  24. }  

有个问题,while循环中为什么没有判断是否到达最后一下的代码呢?难道肯定能找到大于新项的等待时间的项吗?是的!第一个加入项的等待时间是无穷大的,而且这一项永远存在于队列中。

 

 

 

 

五 RTSP服务运作


基础基本搞明白了,那么RTSP,RTP等这些协议又是如何利用这些基础机制运作的呢?
首先来看RTSP.


RTSP首先需建立TCP侦听socket。可见于此函数:

[cpp] view plaincopyprint?
  1. DynamicRTSPServer* DynamicRTSPServer::createNew(UsageEnvironment& env, Port ourPort,  
  2. UserAuthenticationDatabase* authDatabase,  
  3. unsigned reclamationTestSeconds) {  
  4. int ourSocket = setUpOurSocket(env, ourPort); //建立TCP socket  
  5. if (ourSocket == -1)  
  6. return NULL;  
  7.   
  8.   
  9. return new DynamicRTSPServer(env, ourSocket, ourPort, authDatabase,  
  10. reclamationTestSeconds);  
  11. }  


要帧听客户端的连接,就需要利用任务调度机制了,所以需添加一个socket handler。可见于此函数:

[cpp] view plaincopyprint?
  1. RTSPServer::RTSPServer(UsageEnvironment& env,   
  2.         int ourSocket,   
  3.         Port ourPort,  
  4.         UserAuthenticationDatabase* authDatabase,  
  5.         unsigned reclamationTestSeconds) :  
  6.         Medium(env),   
  7.         fRTSPServerSocket(ourSocket),  
  8.         fRTSPServerPort(ourPort),  
  9.         fHTTPServerSocket(-1),  
  10.         fHTTPServerPort(0),  
  11.         fClientSessionsForHTTPTunneling(NULL),   
  12.         fAuthDB(authDatabase),  
  13.         fReclamationTestSeconds(reclamationTestSeconds),  
  14.         fServerMediaSessions(HashTable::create(STRING_HASH_KEYS))   
  15. {  
  16. #ifdef USE_SIGNALS   
  17.     // Ignore the SIGPIPE signal, so that clients on the same host that are killed  
  18.     // don't also kill us:   
  19.     signal(SIGPIPE, SIG_IGN);  
  20. #endif   
  21.   
  22.   
  23.     // Arrange to handle connections from others:  
  24.     env.taskScheduler().turnOnBackgroundReadHandling(  
  25.             fRTSPServerSocket,  
  26.             (TaskScheduler::BackgroundHandlerProc*) &incomingConnectionHandlerRTSP,  
  27.             this);  
  28. }  



 

当收到客户的连接时需保存下代表客户端的新socket,以后用这个socket与这个客户通讯。每个客户将来会对应一个rtp会话,而且各客户的RTSP请求只控制自己的rtp会话,那么最好建立一个会话类,代表各客户的rtsp会话。于是类RTSPServer::RTSPClientSession产生,它保存的代表客户的socket。下为RTSPClientSession的创建过程

[cpp] view plaincopyprint?
  1. void RTSPServer::incomingConnectionHandler(int serverSocket)   
  2. {  
  3.     struct sockaddr_in clientAddr;  
  4.     SOCKLEN_T clientAddrLen = sizeof clientAddr;  
  5.       
  6.     //接受连接   
  7.     int clientSocket = accept(serverSocket,  
  8.             (struct sockaddr*) &clientAddr,  
  9.             &clientAddrLen);  
  10.       
  11.     if (clientSocket < 0) {  
  12.         int err = envir().getErrno();  
  13.         if (err != EWOULDBLOCK) {  
  14.             envir().setResultErrMsg("accept() failed: ");  
  15.         }  
  16.         return;  
  17.     }  
  18.       
  19.     //设置socket的参数   
  20.     makeSocketNonBlocking(clientSocket);  
  21.     increaseSendBufferTo(envir(), clientSocket, 50 * 1024);  
  22.   
  23. #ifdef DEBUG   
  24.     envir() << "accept()ed connection from " << our_inet_ntoa(clientAddr.sin_addr) << "\n";  
  25. #endif   
  26.   
  27.     //产生一个sesson id   
  28.       
  29.     // Create a new object for this RTSP session.  
  30.     // (Choose a random 32-bit integer for the session id (it will be encoded as a 8-digit hex number).  We don't bother checking for  
  31.     //  a collision; the probability of two concurrent sessions getting the same session id is very low.)  
  32.     // (We do, however, avoid choosing session id 0, because that has a special use (by "OnDemandServerMediaSubsession").)  
  33.     unsigned sessionId;  
  34.     do {  
  35.         sessionId = (unsigned) our_random();  
  36.     } while (sessionId == 0);  
  37.       
  38.     //创建RTSPClientSession,注意传入的参数   
  39.     (void) createNewClientSession(sessionId, clientSocket, clientAddr);  
  40. }  

 RTSPClientSession要提供什么功能呢?可以想象:需要监听客户端的rtsp请求并回应它,需要在DESCRIBE请求中返回所请求的流的信息,需要在SETUP请求中建立起RTP会话,需要在TEARDOWN请求中关闭RTP会话,等等...

RTSPClientSession要侦听客户端的请求,就需把自己的socket handler加入计划任务。证据如下:

[cpp] view plaincopyprint?
  1. RTSPServer::RTSPClientSession::RTSPClientSession(  
  2.             RTSPServer& ourServer,  
  3.             unsigned sessionId,  
  4.             int clientSocket,  
  5.             struct sockaddr_in clientAddr) :  
  6.         fOurServer(ourServer),  
  7.         fOurSessionId(sessionId),  
  8.         fOurServerMediaSession(NULL),  
  9.         fClientInputSocket(clientSocket),  
  10.         fClientOutputSocket(clientSocket),  
  11.         fClientAddr(clientAddr),  
  12.         fSessionCookie(NULL),  
  13.         fLivenessCheckTask(NULL),  
  14.         fIsMulticast(False),  
  15.         fSessionIsActive(True),  
  16.         fStreamAfterSETUP(False),  
  17.         fTCPStreamIdCount(0),  
  18.         fNumStreamStates(0),  
  19.         fStreamStates(NULL),  
  20.         fRecursionCount(0)  
  21. {  
  22.     // Arrange to handle incoming requests:  
  23.     resetRequestBuffer();  
  24.     envir().taskScheduler().turnOnBackgroundReadHandling(fClientInputSocket,  
  25.             (TaskScheduler::BackgroundHandlerProc*) &incomingRequestHandler,  
  26.             this);  
  27.     noteLiveness();  
  28. }  



下面重点讲一下下RTSPClientSession响应DESCRIBE请求的过程:

[cpp] view plaincopyprint?
  1. void RTSPServer::RTSPClientSession::handleCmd_DESCRIBE(  
  2.         char const* cseq,  
  3.         char const* urlPreSuffix,  
  4.         char const* urlSuffix,  
  5.         char const* fullRequestStr)  
  6. {  
  7.     char* sdpDescription = NULL;  
  8.     char* rtspURL = NULL;  
  9.     do {  
  10.         //整理一下下RTSP地址   
  11.         char urlTotalSuffix[RTSP_PARAM_STRING_MAX];  
  12.         if (strlen(urlPreSuffix) + strlen(urlSuffix) + 2  
  13.                 > sizeof urlTotalSuffix) {  
  14.             handleCmd_bad(cseq);  
  15.             break;  
  16.         }  
  17.         urlTotalSuffix[0] = '\0';  
  18.         if (urlPreSuffix[0] != '\0') {  
  19.             strcat(urlTotalSuffix, urlPreSuffix);  
  20.             strcat(urlTotalSuffix, "/");  
  21.         }  
  22.         strcat(urlTotalSuffix, urlSuffix);  
  23.   
  24.   
  25.         //验证帐户和密码   
  26.         if (!authenticationOK("DESCRIBE", cseq, urlTotalSuffix, fullRequestStr))  
  27.             break;  
  28.   
  29.   
  30.         // We should really check that the request contains an "Accept:" #####  
  31.         // for "application/sdp", because that's what we're sending back #####  
  32.   
  33.   
  34.         // Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix":  
  35.         //跟据流的名字查找ServerMediaSession,如果找不到,会创建一个。每个ServerMediaSession中至少要包含一个  
  36.         //ServerMediaSubsession。一个ServerMediaSession对应一个媒体,可以认为是Server上的一个文件,或一个实时获取设备。其包含的每个ServerMediaSubSession代表媒体中的一个Track。所以一个ServerMediaSession对应一个媒体,如果客户请求的媒体名相同,就使用已存在的ServerMediaSession,如果不同,就创建一个新的。一个流对应一个StreamState,StreamState与ServerMediaSubsession相关,但代表的是动态的,而ServerMediaSubsession代表静态的。  
  37.         ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlTotalSuffix);  
  38.         if (session == NULL) {  
  39.             handleCmd_notFound(cseq);  
  40.             break;  
  41.         }  
  42.   
  43.   
  44.         // Then, assemble a SDP description for this session:  
  45.         //获取SDP字符串,在函数内会依次获取每个ServerMediaSubSession的字符串然连接起来。  
  46.         sdpDescription = session->generateSDPDescription();  
  47.         if (sdpDescription == NULL) {  
  48.             // This usually means that a file name that was specified for a  
  49.             // "ServerMediaSubsession" does not exist.  
  50.             snprintf((char*) fResponseBuffer, sizeof fResponseBuffer,  
  51.                     "RTSP/1.0 404 File Not Found, Or In Incorrect Format\r\n"  
  52.                     "CSeq: %s\r\n"  
  53.                     "%s\r\n", cseq, dateHeader());  
  54.             break;  
  55.         }  
  56.         unsigned sdpDescriptionSize = strlen(sdpDescription);  
  57.   
  58.   
  59.         // Also, generate our RTSP URL, for the "Content-Base:" header  
  60.         // (which is necessary to ensure that the correct URL gets used in  
  61.         // subsequent "SETUP" requests).  
  62.         rtspURL = fOurServer.rtspURL(session, fClientInputSocket);  
  63.   
  64.   
  65.         //形成响应DESCRIBE请求的RTSP字符串。  
  66.         snprintf((char*) fResponseBuffer, sizeof fResponseBuffer,  
  67.                 "RTSP/1.0 200 OK\r\nCSeq: %s\r\n"  
  68.                 "%s"  
  69.                 "Content-Base: %s/\r\n"  
  70.                 "Content-Type: application/sdp\r\n"  
  71.                 "Content-Length: %d\r\n\r\n"  
  72.                 "%s", cseq, dateHeader(), rtspURL, sdpDescriptionSize,  
  73.                 sdpDescription);  
  74.     } while (0);  
  75.   
  76.   
  77.     delete[] sdpDescription;  
  78.     delete[] rtspURL;  
  79.   
  80.   
  81.     //返回后会被立即发送(没有把socket write操作放入计划任务中)。  
  82. }  




fOurServer.lookupServerMediaSession(urlTotalSuffix)中会在找不到同名ServerMediaSession时新建一个,代表一个RTP流的ServerMediaSession们是被RTSPServer管理的,而不是被RTSPClientSession拥有。为什么呢?因为ServerMediaSession代表的是一个静态的流,也就是可以从它里面获取一个流的各种信息,但不能获取传输状态。不同客户可能连接到同一个流,所以ServerMediaSession应被RTSPServer所拥有。创建一个ServerMediaSession过程值得一观:

[cpp] view plaincopyprint?
  1. static ServerMediaSession* createNewSMS(UsageEnvironment& env,char const* fileName, FILE/*fid*/)   
  2. {  
  3.     // Use the file name extension to determine the type of "ServerMediaSession":  
  4.     char const* extension = strrchr(fileName, '.');  
  5.     if (extension == NULL)  
  6.         return NULL;  
  7.   
  8.   
  9.     ServerMediaSession* sms = NULL;  
  10.     Boolean const reuseSource = False;  
  11.     if (strcmp(extension, ".aac") == 0) {  
  12.         // Assumed to be an AAC Audio (ADTS format) file:  
  13.         NEW_SMS("AAC Audio");  
  14.         sms->addSubsession(  
  15.                 ADTSAudioFileServerMediaSubsession::createNew(env, fileName,  
  16.                         reuseSource));  
  17.     } else if (strcmp(extension, ".amr") == 0) {  
  18.         // Assumed to be an AMR Audio file:  
  19.         NEW_SMS("AMR Audio");  
  20.         sms->addSubsession(  
  21.                 AMRAudioFileServerMediaSubsession::createNew(env, fileName,  
  22.                         reuseSource));  
  23.     } else if (strcmp(extension, ".ac3") == 0) {  
  24.         // Assumed to be an AC-3 Audio file:  
  25.         NEW_SMS("AC-3 Audio");  
  26.         sms->addSubsession(  
  27.                 AC3AudioFileServerMediaSubsession::createNew(env, fileName,  
  28.                         reuseSource));  
  29.     } else if (strcmp(extension, ".m4e") == 0) {  
  30.         // Assumed to be a MPEG-4 Video Elementary Stream file:  
  31.         NEW_SMS("MPEG-4 Video");  
  32.         sms->addSubsession(  
  33.                 MPEG4VideoFileServerMediaSubsession::createNew(env, fileName,  
  34.                         reuseSource));  
  35.     } else if (strcmp(extension, ".264") == 0) {  
  36.         // Assumed to be a H.264 Video Elementary Stream file:  
  37.         NEW_SMS("H.264 Video");  
  38.         OutPacketBuffer::maxSize = 100000; // allow for some possibly large H.264 frames  
  39.         sms->addSubsession(  
  40.                 H264VideoFileServerMediaSubsession::createNew(env, fileName,  
  41.                         reuseSource));  
  42.     } else if (strcmp(extension, ".mp3") == 0) {  
  43.         // Assumed to be a MPEG-1 or 2 Audio file:  
  44.         NEW_SMS("MPEG-1 or 2 Audio");  
  45.         // To stream using 'ADUs' rather than raw MP3 frames, uncomment the following:  
  46. //#define STREAM_USING_ADUS 1   
  47.         // To also reorder ADUs before streaming, uncomment the following:  
  48. //#define INTERLEAVE_ADUS 1   
  49.         // (For more information about ADUs and interleaving,  
  50.         //  see <http://www.live555.com/rtp-mp3/>)  
  51.         Boolean useADUs = False;  
  52.         Interleaving* interleaving = NULL;  
  53. #ifdef STREAM_USING_ADUS   
  54.         useADUs = True;  
  55. #ifdef INTERLEAVE_ADUS   
  56.         unsigned char interleaveCycle[] = {0,2,1,3}; // or choose your own...  
  57.         unsigned const interleaveCycleSize  
  58.         = (sizeof interleaveCycle)/(sizeof (unsigned char));  
  59.         interleaving = new Interleaving(interleaveCycleSize, interleaveCycle);  
  60. #endif   
  61. #endif   
  62.         sms->addSubsession(  
  63.                 MP3AudioFileServerMediaSubsession::createNew(env, fileName,  
  64.                         reuseSource, useADUs, interleaving));  
  65.     } else if (strcmp(extension, ".mpg") == 0) {  
  66.         // Assumed to be a MPEG-1 or 2 Program Stream (audio+video) file:  
  67.         NEW_SMS("MPEG-1 or 2 Program Stream");  
  68.         MPEG1or2FileServerDemux* demux = MPEG1or2FileServerDemux::createNew(env,  
  69.                 fileName, reuseSource);  
  70.         sms->addSubsession(demux->newVideoServerMediaSubsession());  
  71.         sms->addSubsession(demux->newAudioServerMediaSubsession());  
  72.     } else if (strcmp(extension, ".ts") == 0) {  
  73.         // Assumed to be a MPEG Transport Stream file:  
  74.         // Use an index file name that's the same as the TS file name, except with ".tsx":  
  75.         unsigned indexFileNameLen = strlen(fileName) + 2; // allow for trailing "x\0"  
  76.         char* indexFileName = new char[indexFileNameLen];  
  77.         sprintf(indexFileName, "%sx", fileName);  
  78.         NEW_SMS("MPEG Transport Stream");  
  79.         sms->addSubsession(  
  80.                 MPEG2TransportFileServerMediaSubsession::createNew(env,  
  81.                         fileName, indexFileName, reuseSource));  
  82.         delete[] indexFileName;  
  83.     } else if (strcmp(extension, ".wav") == 0) {  
  84.         // Assumed to be a WAV Audio file:  
  85.         NEW_SMS("WAV Audio Stream");  
  86.         // To convert 16-bit PCM data to 8-bit u-law, prior to streaming,  
  87.         // change the following to True:  
  88.         Boolean convertToULaw = False;  
  89.         sms->addSubsession(  
  90.                 WAVAudioFileServerMediaSubsession::createNew(env, fileName,  
  91.                         reuseSource, convertToULaw));  
  92.     } else if (strcmp(extension, ".dv") == 0) {  
  93.         // Assumed to be a DV Video file  
  94.         // First, make sure that the RTPSinks' buffers will be large enough to handle the huge size of DV frames (as big as 288000).  
  95.         OutPacketBuffer::maxSize = 300000;  
  96.   
  97.   
  98.         NEW_SMS("DV Video");  
  99.         sms->addSubsession(  
  100.                 DVVideoFileServerMediaSubsession::createNew(env, fileName,  
  101.                         reuseSource));  
  102.     } else if (strcmp(extension, ".mkv") == 0) {  
  103.         // Assumed to be a Matroska file  
  104.         NEW_SMS("Matroska video+audio+(optional)subtitles");  
  105.   
  106.   
  107.         // Create a Matroska file server demultiplexor for the specified file.  (We enter the event loop to wait for this to complete.)  
  108.         newMatroskaDemuxWatchVariable = 0;  
  109.         MatroskaFileServerDemux::createNew(env, fileName,  
  110.                 onMatroskaDemuxCreation, NULL);  
  111.         env.taskScheduler().doEventLoop(&newMatroskaDemuxWatchVariable);  
  112.   
  113.   
  114.         ServerMediaSubsession* smss;  
  115.         while ((smss = demux->newServerMediaSubsession()) != NULL) {  
  116.             sms->addSubsession(smss);  
  117.         }  
  118.     }  
  119.   
  120.   
  121.     return sms;  
  122. }  


可以看到NEW_SMS("AMR Audio")会创建新的ServerMediaSession,之后马上调用sms->addSubsession()为这个ServerMediaSession添加一个 ServerMediaSubSession 。看起来ServerMediaSession应该可以添加多个ServerMediaSubSession,但这里并没有这样做。如果可以添加多个 ServerMediaSubsession 那么ServerMediaSession与流名字所指定与文件是没有关系的,也就是说它不会操作文件,而文件的操作是放在 ServerMediaSubsession中的。具体应改是在ServerMediaSubsession的sdpLines()函数中打开。