该篇文章参考于http://blog.csdn.net/haolipengzhanshen/article/details/50859062 表示感谢
在上一节中我们知道在EventThread::Entry中调用select_waitevent函数等待连接事件发生,可以是视频采集端推流和客户端拉取流,均会触发这个监听函数。当有事件发生的时候就调用ProcessEvent方法对事件进行相应的处理。
对于建立RTSP连接的请求:
调用TCPListenerSocket::ProcessEvent
此方法调用RTSPListenerSocket的GetSessionTask方法建立一个RTSPSession,并把相应的套接口加入侦听队列,等待RTSP请求。
然后还需调用this->RequestEvent(EV_RE)把建立RTSP连接的请求加入到侦听队列。
在RequestEvent函数中调用select_modwatch(&fEventReq, theMask)将自己感兴趣的事件加入到侦听队列中,
学习TaskThread主要有三个类要关注:
TaskTreadPool: 任务线程池
TaskThread:任务线程
Task: 任务
1. TaskThreadPool
Darwin运行着一个或者多个任务(Task)线程,并将他们统一在线程池TaskThreadPool中管理。任务线程从事件线程中接收RTSP和RTP请求,然后把请求传递到恰当的服务器模块进行处理,把数据包发送给客户端。
缺省情况下,核心服务器为每一个处理器创建一个任务线程。(numProcessors = OS::GetNumProcessors();)
TaskThreadPool::SetNumShortTaskThreads(numShortTaskThreads);
TaskThreadPool::SetNumBlockingTaskThreads(numBlockingThreads);
TaskThreadPool::AddThreads(numThreads);
sServer->InitNumThreads(numThreads);
2. TaskThread
所有的Task对象都将在TaskThread中运行,TaskThread通过运行Task类型对象的Run方法来完成相应Task的处理。TaskThread的个数是可配置的,缺省情况下TaskThread的个数与处理器的个数一致。等待被TaskThread调用并运行的Task放在队列或者堆中。
if (notAService) { // If we're running off the command-line, don't do the service initiation crap. ::StartServer(sXMLParser, &sMessagesSource, sPort, sStatsUpdateInterval, sInitialState, false,0, kRunServerDebug_Off,sAbsolutePath); // No stats update interval for now ::RunServer(); ::exit(0); }
在RunServer.cpp中主函数的这段代码,完成了server的初始化,在StasrtServer中创建了 TaskThread线程池。来看一看这个线程的成员:
class TaskThread : public OSThread { public: //Implementation detail: all tasks get run on TaskThreads. TaskThread() : OSThread(), fTaskThreadPoolElem() {fTaskThreadPoolElem.SetEnclosingObject(this);} virtual ~TaskThread() { this->StopAndWaitForThread(); } private: enum { kMinWaitTimeInMilSecs = 10 //UInt32 }; virtual void Entry(); Task* WaitForTask(); OSQueueElem fTaskThreadPoolElem; OSHeap fHeap; OSQueue_Blocking fTaskQueue; friend class Task; friend class TaskThreadPool; };
其中的重点是Entry()和WaitForTask()。
Task* TaskThread::WaitForTask() { while (true) { //获取当前时间 SInt64 theCurrentTime = OS::Milliseconds(); /*如果堆中有任务,且任务已经到执行时间,返回该任务。 PeekMin函数见OSHeap.h,窃听堆中第一个元素(但不取出)*/ if ((fHeap.PeekMin() != NULL) && (fHeap.PeekMin()->GetValue() <= theCurrentTime)) { return (Task*)fHeap.ExtractMin()->GetEnclosingObject();//返回指向Task对象的指针 } //如果堆中有任务,但是尚未到执行时间,计算需要等待的时间 //if there is an element waiting for a timeout, figure out how long we should wait. SInt64 theTimeout = 0; if (fHeap.PeekMin() != NULL) theTimeout = fHeap.PeekMin()->GetValue() - theCurrentTime;//计算下超时时间 Assert(theTimeout >= 0); if (theTimeout < 10) theTimeout = 10; //wait... //如果任务队列为空,就等待theTimeout时间后从堆中取出任务返回; //如果任务队列不为空,就不等待,直接取队列中的任务; //从fTaskQueue任务队列中取出OSQueueElem队列元素 OSQueueElem* theElem = fTaskQueue.DeQueueBlocking(this, (SInt32) theTimeout); if (theElem != NULL) { return (Task*)theElem->GetEnclosingObject();//返回Task指针,从堆中取出第一个任务返回 } if (OSThread::GetCurrent()->IsStopRequested()) return NULL; } }
3. Task
每个Task对象有两个主要的方法:Signal和Run。
3.1 Run()
Run()方法是在Task对象获得处理该事件的时间片后运行的,Darwin中的大部分工作都是在不同Task对象的Run()函数中进行的。
比如常见的Task类型是RTSPSession和RTPSession。
在这里程序的返回值是一个重要的信息:
当返回-1时,表明任务已经完全结束,程序会直接删除该Task指针;
当返回0时,线程接着执行下一个Task,表明任务希望在下次传信时被再次立即执行;
当返回一个正数n时,线程会在 n毫秒后再次调用这个Task的Run函数。将该任务加入到Heap中,并且纪录它希望等待的时间。Entry()函数将通过waitfortask()函数进行检测,如果等待的时间到了,就再次运行该任务
(注意; 在我们继承Task类,而重写Run虚函数时,我们第一件要作的事情是运行Task类的GetEvent()函数。)
TaskThread::Entry调用TaskThread::WaitForTask()方法获得下一个需要处理的Task。TaskThread::WaitForTask()首先从TaskThread::fHeap中获得Task,如果TaskThread::fHeap中没有满足条件的Task,则从TaskThread::fTaskQueue中获得Task。
继续分析TaskThread::Entry()函数
TaskThread::Entry调用TaskThread::WaitForTask()方法获得下一个需要处理的Task。
根据theTask->fWriteLock判断是否有读写锁
然后调用theTimeout = theTask->Run(),转到Task::Run函数
if(theTimeout < 0) { //删除task } else if (theTimeout == 0) { //不做处理 } else { // 返回值theTimeout代表了下次处理这个Task需等待的时间 //fHeap.Insert(&theTask->fTimerHeapElem)把Task插入到堆里,并设定等待时间
}
对于EasyDarwin的核心任务线程管理处理,还是比较难懂的,我写文章的时候也是模模糊糊的,多看几遍吧,对于整体框架的应用有好处,我写这篇文章的目的是完成一个事实视频采集的功能。对于线程池的管理,我也是需要学习的。
更多内容请看;http://www.amovauto.com 阿木技术社区,玩也要玩的专业。