一直想學習流媒體伺服器的設計,這幾天有點時間,看了一下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 test
char delayedTaskClientData1[] = "Delayed Task 1s";
TaskToken token1 = scheduler->scheduleDelayedTask(1000000,
taskFunc, delayedTaskClientData1);
char delayedTaskClientData2[] = "Delayed Task 5s";
TaskToken token2 = scheduler->scheduleDelayedTask(5000000,
taskFunc, delayedTaskClientData2);
// loop
scheduler->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.