標籤:
2011-12-6閱讀1264 評論1
一直想學習流媒體伺服器的設計,這幾天有點時間,看了一下live555的原始碼。live555是一個開源的跨平台流媒體伺服器,使用程式設計語言是C++。將現階段學習筆記總結如下,其實關鍵是要弄明白幾個類的作用和它們之間的關係:
一.UsageEnvironment類以及其衍生類別的繼承關係
基類UsageEnvironment是一個抽象類別,它主要是定義了一些介面函數(純虛函數)包括錯誤碼/結果訊息系列函數,重載輸出操作符系列函數等;以及定義重要的資料成員fScheduler,它聲明為TaskScheduler的引用;它重要是因為它是整個程式運作的引擎。為了做更好的封裝UsageEnvironment將建構函式和解構函式聲明為protected。後面會看到,UsageEnvironment的衍生類別的構造是通過靜態函數creatNew實現的,而析構都是通過調用的reClain()函數實現。
BasicUsageEnvironment0很簡單,它定義了一個字元數組來儲存結果訊息,只是實現了UsageEnvironment中的結果訊息系列函數,所以它仍然是一個抽象類別。
BasicUsageEnvironment也很簡單,它實現了輸出操作符系列函數的重載以及靜態函數creatNew。
因此只需要記住這三樣東西:結果訊息函數、輸出操作符函數、TaskScheduler引用變數,便可以瞭解UsageEnvironment及其衍生類別的作用。實際上完全可以分開三個類來表示,作者可能是為了方便使用才把它們放到一起。因為當我們要給程式增加功能時,可以在TaskScheduler完成,而想設定和輸出程式運行期間的訊息,可以直接使用輸出操作符和結果訊息處理函數,它給後續程式開發提供一個很好的環境。
二.TaskScheduler類以及其衍生類別的繼承關係
顧名思義,TaskScheduler是一個任務調度器。它的繼承關係圖跟UsageEnvironment類似,呵呵,有了前面的分析我們也應該很容易掌握這個類。這裡的任務是抽象的,可以想象為一段代碼或一個函數,任務調度目的就是要決定程式當前應該運行哪一個任務。
1.TaskScheduler是一個抽象基類,它定義了一系列的介面函數,其中doEventLoop定義為程式迴圈函數。根據任務的類別,作者分成三類的來處理,每一次迴圈都會按照下面順序來完成調用(請參考BasicTaskScheduler中的singleStep函數):
(1)首先處理的是Socket Event,負責I/O複用,使用select函數等待指定的描述字準備好讀、寫或有異常條件處理。若select傳回值大於-1,則轉到相應的處理函數;否則表明發生異常,程式將轉到錯誤處理代碼中去。該類型適合於有I/O操作的任務。
相關函數:setBackgroundHandling/disableBackgroundHandling/moveSocketHandling
(2)接著是處理觸發事件(Trigger-Event)。作者定義了一個32位的位元影像來實現觸發事件,當某一位設定為1則表明要觸發該位對應的事件。若同時有多個(3個或以上)觸發事件,它們觸發的先後還會跟事件建立的先後有關,因此這一類型僅適合於沒有順序依賴關係的任務。
相關函數:createEventTrigger/deleteEventTrigger/triggerEvent
(3)最後一個是延遲任務(Delayed Task),它是一個帶有時間的任務。當剩餘時間不為0,則任務不執行。通過調整任務的剩餘時間,可以靈活地安排任務。
相關函數:scheduleDelayedTask/unscheduleDelayedTask/rescheduleDelayedTask
TaskScheduler為了相容以前的程式,還保留了turnOnBackgroundReadHandling/turnOffBackgroundReadHandling函數 ,實際上它們也是通過調用setBackgroundHandling/disableBackgroundHandling實現的。當然還有一個錯誤處理函數interalError,處理常式錯誤,衍生類別可重載。
2.BasicTaskScheduler0主要是實現了觸發事件和延遲任務。
(1)觸發事件是通一個32位元影像實現的,它利用兩個數組儲存儲存觸發事件的函數指標和函數參數指標。它是從最高為開始存放的,即最高位對應函數指標數組和參數指標數組的第0個元素,最多可使用32個觸發器。它還儲存上一次的觸發的序號和觸發mask,作為下一次起始點,從而保證所有的觸發器都能夠觸發。
(2)延遲任務是通過一個雙向迴圈鏈表實現的。它的節點實際是AlarmHandler,而鏈表則實現為DelayQueue,它們都是從DelayQueueEntry基類繼承得到的,三者間的關係如:
DelayQueueEntry可看作是一個抽象的雙向鏈表中的節點,除了前向指標和後向指標,它附加了一個fDeltaTimeRemaining成員和fToken成員,表示延時時間和標識節點的唯一標誌。此外還定義了有一個TimeOut時調用的虛函數handleTimeOut()。
AlarmHandler是實際的延時任務節點,非常簡單的,它在DelayQueueEntry基礎上定義了一個的函數指標和函數參數指標,並重載了handleTimeOut函數。
DelayQueue是作為迴圈鏈表類,一般來說不用繼承DelayQueueEntry,作者在這裡是把它作為迴圈鏈表的前端節點。其餘的都是迴圈鏈表的常規操作,包括節點的查詢、插入、刪除、更新操作。DelayQueue還實現了timeToNextAlarm()返回前端節點的時延,以及handleAlarm()實際延時任務處理,實際調用的是節點的handleTimeOut()函數;
3.BasicTaskScheduler類實現剩下的I/O操作任務介面和三類任務的實際調度(singleStep函數)。
I/O任務的實現也很簡單的,它也是使用雙向迴圈鏈表來儲存任務。它的節點定義為HandlerDiscriptor,包括前向後向節點指標,函數指標和函數參數指標,以及IO相關的socketNum和conditionSet資料成員。迴圈鏈表類定義為HandlerSet,類似地也定義了查詢、插入、刪除、更新操作。它聲明了一個節點成員作為前端節點。
三.對UsageEnvironment的測試
現在可以測試體驗一下這些類的功能,我寫了一個簡單的程式,設定三種類型任務並進行調度。代碼如下:
// This is a test for basic objects, such as TaskSchduler, UsageEnvironment and// so on. It‘s just for study purpose.#include <BasicUsageEnvironment.hh>#include <iostream>using namespace std;TaskScheduler* scheduler = BasicTaskScheduler::createNew();UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);void taskFunc(void* clientData){cout<<"taskFunc(\""<<(char*)clientData<<"\") called."<<endl;}void handlerFunc(void* clientData, int mask){cout<<"handlerFunc(\""<<(char*)clientData<<"\", "<<mask<<") called."<<endl;scheduler->disableBackgroundHandling(STDOUT_FILENO);}int main(int argc, char* args[]){ // IO event test char handlerClientData[] = "IO Event"; scheduler->setBackgroundHandling(STDOUT_FILENO, SOCKET_WRITABLE,(TaskScheduler::BackgroundHandlerProc*)&handlerFunc, handlerClientData); // trigger event test EventTriggerId id1 = scheduler->createEventTrigger(taskFunc); char triggerClientData1[] = "Trigger Event 1"; EventTriggerId id2 = scheduler->createEventTrigger(taskFunc); char triggerClientData2[] = "Trigger Event 2"; EventTriggerId id3 = scheduler->createEventTrigger(taskFunc); char triggerClientData3[] = "Trigger Event 3";(*env)<<"Setting Event triggers...\n";scheduler->triggerEvent(id2, (void*)triggerClientData2); scheduler->triggerEvent(id1, (void*)triggerClientData1); scheduler->triggerEvent(id3, (void*)triggerClientData3); (*env)<<"Event triggers has been set.\n";// delayed task testchar delayedTaskClientData1[] = "Delayed Task 1s";TaskToken token1 = scheduler->scheduleDelayedTask(1000000,taskFunc, delayedTaskClientData1);char delayedTaskClientData2[] = "Delayed Task 5s"; TaskToken token2 = scheduler->scheduleDelayedTask(5000000, taskFunc, delayedTaskClientData2);// loopscheduler->doEventLoop();return 0;}編譯執行,輸出的結果是:Setting Event triggers...
Event triggers has been set.
handlerFunc("IO Event", 4) called.
taskFunc("Trigger Event 1") called.
taskFunc("Trigger Event 2") called.
taskFunc("Trigger Event 3") called.
taskFunc("Delayed Task 1s") called.
taskFunc("Delayed Task 5s") called. 代碼能夠正常工作。
總結:
live555使用UsageEvironment類和TaskSheduler類以及它們的衍生類別建立一個良好的程式開發基本架構,在此基礎上可以方便構建我們的各種應用。一個任務只需要兩步就可以完成,先根據任務的類型定義任務處理函數,然後將任務添加到迴圈體裡面即可。
值得注意的是這個任務調度器的效能和安全執行緒問題。如果某個任務需要長時間的處理或發生阻塞,那麼主迴圈也將發生阻塞,其他的任務將得不到及時的響應,因此設計任務時要考慮任務花費的時間,若太長則要考慮是否開闢另外一個線程來處理了。另外,從原始碼上來看,該任務調度器並沒有為支援多線程做更多的工作,所以通過多個線程新增工作,可能會發生異常。
轉自:http://m.blog.csdn.net/blog/huangwanzhang/7042843
(轉)live555學習筆記-UsageEnvironment和TaskScheduler