初学者:ACE学习
ACE的配置(window)
(使用VC++)安裝:
1. 從網上下載相應源碼――――根據提示編輯config.h文件,并放置在ACE_ROOT\ace?目錄下。
2. 用VC打開ACE_ROOT\ace\ace.dsw?,并編譯,編譯后會在ACE_ROOT\lib?目錄下生成兩個庫:ACEd.dll(動態庫)和ACEd.lib(靜態庫)。
3. 鏈接:把ACEd.dll(動態庫)和ACEd.lib(靜態庫)復制到目錄ACE_ROOT\ace下,因為這是默認的靜態庫鏈接路徑。或者可以修改靜態庫鏈接路徑:project->setting->link->object/library?填入ACE_ROOT\lib.
4.執行:If?you?use?the?dynamic?libraries,?make?sure?you?include?ACE_ROOT\bin?in?your?PATH?whenever?you?run?programs?that
uses?ACE.?Otherwise?you?may?experience?problems?finding?ace.dll?or?aced.dll.
就是在程序執行時,如果使用ACE的動態庫,必須修改系統環境變量PATH的值,把包含ACEd.dll?的路徑添加到PATH中。修改如下:電腦屬性――高級――環境變量――系統變量――Path(修改)。或者把ACEd.dll?添加到系統默認的搜索路徑之中,如添加到c:/window/system32?中。
在VC++下使用ACE
新建工程:使用new菜單,選中project。選取win32?console?Applitation(控制臺應用程序),建立一個空項目。同時新建一個workspace。
一個workspace可以對應幾個項目,一個項目對應一個程序。當然如何在一個workspace管理多個工程現在還沒搞清楚。Workspace保存空間中的相應配置,而一個項目保存,自己項目下的項目配置。
添加頭文件的搜索路徑:tools->option->directories,添加ace頭文件路徑。當然,按照同樣的辦法,也可以修改庫文件、執行文件、源文件的搜索路徑,只需做相應選擇就可以了。
修改項目的setting:通過project->setting進入,或是直接在項目名字上點擊右鍵,選擇setting即可。把C/C++中的MLd修改為MDd(多線程);把link中input中的庫改為aced.lib,同時additional?path?中?http://www.cnblogs.com/../ace。這樣設置就基本完成。
注:以下摘抄自《ACE 程序員教程》
ACE簡介
ACE自適配通信環境?(Adaptive?Communication?Environment)是面向對象的構架和工具包,它為通信軟件實現了核心的并發和分布式模式。ACE中的組件可用于以下幾種目的:
·?并發和同步?
·?進程間通信(IPC)?
·?內存管理?
·?定時器?
·?信號?
·?文件系統管理?
·?線程管理?
·?事件多路分離和處理器分派?
·?連接建立和服務初始化?
·?軟件的靜態和動態配置、重配置?
·?分層協議構建和流式構架?
·?分布式通信服務:名字、日志、時間同步、事件路由和網絡鎖定,等等。
目前ACE適用的OS平臺包括:實時OS(VxWorks、Chorus、LynxOS和pSoS)、大多數版本的UNIX(SunOS?4.x和5.x;?SGI?IRIX?5.x和6.x;?HP-UX?9.x,?10.x和11.x;?DEC?UNIX?3.x和4.x;?AIX?3.x和4.x;?DG/UX;?Linux;?SCO;?UnixWare;?NetBSD和FreeBSD)、Win32(使用MSVC++和Borland?C++的WinNT?3.5.x、4.x、Win95和WinCE)以及MVS?OpenEdition。
在ACE構架中有三個基本層次:
·?操作系統(OS)適配層?
·?C++包裝層?
·?構架和模式層?
第2章 IPC?SAP:進程間通信服務訪問點包裝
ACE_IPC_SAP類提供的一些函數是所有IPC接口公有的。有四個不同的類由此類派生而出,每個類各自代表ACE包含的一種IPC?SAP包裝類屬。這些類封裝適用于特定IPC接口的功能。例如,ACE_SOCK類包含的功能適用于BSD?socket編程接口,而ACE_TLI包裝TLI編程接口。ACE_FIFO類和???ACE_SPIPE類。
socket類屬(ACE_SOCK)
| 類名 | 職責 |
| ACE_SOCK_Acceptor | 用于被動的連接建立,基于BSD?accept()和listen()調用。 |
| ACE_SOCK_Connector | 用于主動的連接建立,基于BSD?connect()調用。 |
| ACE_SOCK_Dgram | 用于提供基于UDP(用戶數據報協議)的無連接消息傳遞服務。封裝了sendto()和receivefrom()等調用,并提供了簡單的send()和recv()接口。 |
| ACE_SOCK_IO | 用于提供面向連接的消息傳遞服務。封裝了send()、recv()和write()等調用。該類是ACE_SOCK_Stream和ACE_SOCK_CODgram類的基類。 |
| ACE_SOCK_Stream | 用于提供基于TCP(傳輸控制協議)的面向連接的消息傳遞服務。派生自ACE_SOCK_IO,并提供了更多的包裝方法。 |
| ACE_SOCK_CODgram | 用于提供有連接數據報(connected?datagram)抽象。派生自ACE_SOCK_IO;它包含的open()方法使用bind()來綁定到指定的本地地址,并使用UDP連接到遠地地址。 |
| ACE_SOCK_Dgram_Mcast | 用于提供基于數據報的多點傳送(multicast)抽象。包括預訂多點傳送組,以及發送和接收消息的方法 |
| ACE_SOCK_Dgram_Bcast | 用于提供基于數據報的廣播(broadcast)抽象。包括在子網中向所有接口廣播數據報消息的方法 |
表2-1?ACE_SOCK中的類及其職責
第3章?ACE的內存管理
ACE含有兩組不同的類用于內存管理。
第一組是那些基于ACE_Allocator的類。這組類使用動態綁定和策略模式來提供靈活性和可擴展性。它們只能用于局部的動態內存分配。
第二組類基于ACE_Malloc模板類。這組類使用C++模板和外部多態性?(External?Polymorphism)來為內存分配機制提供靈活性。在這組類中的類不僅包括了用于局部動態內存管理的類,也包括了管理進程間共享內存的類。這些共享內存類使用底層OS(OS)共享內存接口。
3.1?分配器(Allocator)
分配器用于在ACE中提供一種動態內存管理機制。在ACE中有若干使用不同策略的分配器可用。這些不同策略提供相同的功能,但是具有不同的特性。所有的分配器都支持ACE_Allocator接口,因此無論是在運行時還是在編譯時,它們都可以很容易地相互替換。這也正是靈活性之所在。
| 分配器 | 描述 |
| ACE_Allocator | ACE中的分配器類的接口類。這些類使用繼承和動態綁定來提供靈活性。 |
| ACE_Static_Allocator | 該分配器管理固定大小的內存。每當收到分配內存的請求時,它就移動內部指針、以返回內存chunk(“大塊”)。它還假定內存一旦被分配,就再也不會被釋放。 |
| ACE_Cached_Allocator | 該分配器預先分配內存池,其中含有特定數目和大小的內存chunk。這些chunk在內部空閑表(free?list)中進行維護,并在收到內存請求(malloc())時被返回。當應用調用free()時,chunk被歸還到內部空閑表、而不是OS中。 |
| ACE_New_Allocator | 為C++?new和delete操作符提供包裝的分配器,也就是,它在內部使用new和delete操作符,以滿足動態內存請求。 |
表3-1?ACE中的分配器
使用如下:
typedef?ACE_Cached_Allocator<MEMORY_BLOCK,ACE_SYNCH_MUTEX>?Allocator;
3.2?ACE_Malloc
Malloc類集使用模板類ACE_Malloc來提供內存管理。ACE_Malloc模板需要兩個參數(一個是內存池,一個是池鎖),以產生我們的分配器類。當應用發出free()調用時,ACE_Malloc不會把所釋放的內存返還給內存池,而是由它自己的空閑表進行管理。當ACE_Malloc收到后續的內存請求時,它會使用空閑表來查找可返回的空block。因而,在使用ACE_Malloc時,如果只發出簡單的malloc()和free()調用,從OS分配的內存數量將只會增加,不會減少。ACE_Malloc類還含有一個remove()方法,用于發出請求給內存池,將內存返還給OS。該方法還將鎖也返還給OS。
3.2.2?使用ACE_Malloc
ACE_Malloc類的使用很簡單。首先,用你選擇的內存池和鎖定機制實例化ACE_Malloc,以創建分配器類。隨后用該分配器類實例化一個對象,這也就是你的應用將要使用的分配器。當你實例化分配器對象時,傳給構造器的第一個參數是一個字符串,它是你想要分配器對象使用的底層內存池的“名字”。將正確的名字傳遞給構造器非常重要,特別是如果你在使用共享內存的話。否則,分配器將會為你創建一個新的內存池。如果你在使用共享內存池,這當然不是你想要的,因為你根本沒有獲得共享。
為了方便底層內存池的共享(重復一次,如果你在使用共享內存的話,這是很重要的),ACE_Malloc類還擁有一個映射(map)類型接口:可被給每個被分配的內存block一個名字,從而使它們可以很容易地被在內存池中查找的另一個進程找到。該接口含有bind()和find()調用。bind()調用用于給由malloc()調用返回給ACE_Malloc的block命名。find()調用,如你可能想到的那樣,用于查找與某個名字相關聯的內存。
表3-2列出了各種可用的內存池:
| 池名 | 宏 | 描述 |
| ACE_MMAP_Memory_Pool | ACE_MMAP_MEMORY_POOL | 使用<mmap(2)>創建內存池。這樣內存就可在進程間共享了。每次更新時,內存都被更新到后備存儲(backing?store)。 |
| ACE_Lite_MMAP_Memory_Pool | ACE_LITE_MMAP_MEMORY_POOL | 使用<mmap(2)>創建內存池。不像前面的映射,它不做后備存儲更新。代價是較低可靠性。 |
| ACE_Sbrk_Memory_Pool | ACE_SBRK_MEMORY_POOL | 使用<sbrk(2)>調用創建內存池。 |
| ACE_Shared_Memory_Pool | ACE_SHARED_MEMORY_POOL | 使用系統V?<shmget(2)>調用創建內存池。 |
| Memory_Pool | ? | 內存可在進程間共享。 |
| ACE_Local_Memory_Pool | ACE_LOCAL_MEMORY_POOL | 通過C++的new和delete操作符創建局部內存池。該池不能在進程間共享。 |
第4章?線程管理:ACE的同步和線程管理機制
ACE_Thread提供了對OS的線程調用的簡單包裝,這些調用處理線程創建、掛起、取消和刪除等問題。它提供給應用程序員一個簡單易用的接口,可以在不同的線程API間移植。ACE_Thread是非常“瘦”的包裝,有著很少的開銷。其大多數方法都是內聯的,因而等價于對底層OS專有線程接口的直接調用。ACE_Thread中的所有方法都是靜態的,而且該類一般不進行實例化。
線程是通過使用ACE_Thread::spawn_n()調用創建的。要作為線程的執行啟動點調用的函數的指針(在此例中為worker()函數)被作為參數傳入該調用中。要注意的重點是ACE_Thread::spawn_n()要求所有的線程啟動函數(方法)必須是靜態的或全局的(就如同直接使用OS線程API時所要求的一樣)。
等待是通過使用ACE_Thread::join()調用來完成的。該方法的參數是你想要主線程與之聯接的線程的句柄(ACE_hthread_t)。
4.2?ACE同步原語
ACE有若干可用于同步目的的類。這些類可劃分為以下范疇:
·?ACE?Lock類屬?
·?ACE?Guard類屬?
·?ACE?Condition類屬?
·?雜項ACE?Synchronization類?
| 名字 | 描述 |
| ACE_Mutex | 封裝互斥機制(根據平臺,可以是mutex_t、pthread_mutex_t等等)的包裝類,用于提供簡單而有效的機制來使對共享資源的訪問序列化。它與二元信號量(binary?semaphore)的功能相類似。可被用于線程和進程間的互斥。 |
| ACE_Thread_Mutex | 可用于替換ACE_Mutex,專用于線程同步。 |
| ACE_Process_Mutex | 可用于替換ACE_Mutex,專用于進程同步。 |
| ACE_NULL_Mutex | 提供了ACE_Mutex接口的“無為”(do-nothing)實現,可在不需要同步時用作替換(單線程使用)。 |
| ACE_RW_Mutex | 封裝讀者/作者鎖的包裝類。它們是分別為讀和寫進行獲取的鎖,在沒有作者在寫的時候,多個讀者可以同時進行讀取。 |
| ACE_RW_Thread_Mutex | 可用于替換ACE_RW_Mutex,專用于線程同步。 |
| ACE_RW_Process_Mutex | 可用于替換ACE_RW_Mutex,專用于進程同步。 |
| ACE_Semaphore | 這些類實現計數信號量,在有固定數量的線程可以同時訪問一個資源時很有用。在OS不提供這種同步機制的情況下,可通過互斥體來進行模擬。 |
| ACE_Thread_Semaphore | 應被用于替換ACE_Semaphore,專用于線程同步。 |
| ACE_Process_Semaphore | 應被用于替換ACE_Semaphore,專用于進程同步。 |
| ACE_Token | 提供“遞歸互斥體”(recursive?mutex),也就是,當前持有某令牌的線程可以多次重新獲取它,而不會阻塞。而且,當令牌被釋放時,它確保下一個正阻塞并等待此令牌的線程就是下一個被放行的線程。 |
| ACE_Null_Token | 令牌接口的“無為”(do-nothing)實現,在你知道不會出現多個線程時使用。 |
| ACE_Lock | 定義鎖定接口的接口類。一個純虛類,如果使用的話,必須承受虛函數調用開銷。 |
| ACE_Lock_Adapter | 基于模板的適配器,允許將前面提到的任意一種鎖定機制適配到ACE_Lock接口。 |
表4-1?ACE鎖類屬中的類
表4-1中描述的類都支持同樣的接口。但是,在任何繼承層次中,這些類都是互不關聯的。在ACE中,鎖通常用模板來參數化,因為,在大多數情況下,使用虛函數調用的開銷都是不可接受的。使用模板使得程序員可獲得相當程度的靈活性。他可以在編譯時(但不是在運行時)選擇他想要使用的的鎖定機制的類型。然而,在某些情形中,程序員仍可能需要使用動態綁定和替換(substitution);對于這些情況,ACE提供了ACE_Lock和ACE_Lock_Adapter類。
在臨界區內完成的工作使用ACE_Thread_Mutex互斥體對象進行保護。該對象由主線程作為參數傳給工作者線程。臨界區控制是通過在ACE_Thread_Mutex對象上發出acquire()調用,從而獲取互斥體的所有權來完成的。一旦互斥體被獲取,沒有其他線程能夠再進入這一代碼區。臨界區控制是通過使用release()調用來釋放的。一旦互斥體的所有權被放棄,就會喚醒所有其他在等待的線程。這些線程隨即相互競爭,以獲得互斥體的所有權。第一個試圖獲取所有權的線程會進入臨界區。
使用示例:
struct?Args
{
public:Args(int?iterations):?mutex_(),iterations_(iterations){}
ACE_Thread_Mutex?mutex_;
int?iterations_;
};
4.2.1.3?使用令牌(Token)
如表4-1中所提到的,ACE_Token類提供所謂的“遞歸互斥體”,它可以被最初獲得它的同一線程進行多次重新獲取。ACE_Token類還確保所有試圖獲取它的線程按嚴格的FIFO(先進先出)順序排序。
遞歸鎖允許同一線程多次獲取同一個鎖。線程不會因為試圖獲取它已經擁有的鎖而死鎖。這些類型的鎖能在各種不同的情況下派上用場。例如,如果你用一個鎖來維護跟蹤流的一致性,你可能希望這個鎖是遞歸的,因為某個方法可以調用一個跟蹤例程,獲取鎖,被信號中斷,然后再嘗試獲取這個跟蹤鎖。如果鎖是非遞歸的,線程將會在這里鎖住它自己。你會發現很多其他需要遞歸鎖的有趣應用。重要的是要記住,你獲取遞歸鎖多少次,就必須釋放它多少次。
在SunOS?5.x上運行例4-3,釋放鎖的線程常常也是重新獲得它的線程(大約90%的情況是這樣)。但是如果你采用ACE_Token類作為鎖定機制來運行這個例子,每個線程都會輪流獲得令牌,然后有序地把機會讓給下一個線程。
盡管ACE_Token作為所謂的遞歸鎖非常有用,它們實際上是更大的“令牌管理”構架的一部分。該構架允許你維護數據存儲中數據的一致性。
4.2.2?ACE守衛(Guard)類屬
ACE中的守衛用于自動獲取和釋放鎖。守衛類的對象定義一個代碼塊,在其上獲取一個鎖。在退出此代碼塊時,鎖被自動釋放。
ACE中的守衛類是一種模板,它通過所需鎖定機制的類型來參數化。底層的鎖可以是ACE?Lock類屬中的任何類,也就是,任何互斥體或鎖類。它是這樣工作的:對象的構造器獲取鎖,析構器釋放鎖。表4-2列出了ACE中可用的守衛:
?
| 名字 | 描述 |
| ACE_Guard | 自動在底層鎖上調用acquire()和release()。任何ACE?Lock類屬中的鎖都可以作為它的模板參數傳入。 |
| ACE_Read_Guard | 自動在底層鎖上調用acquire()和release()。 |
| ACE_Write_Guard | 自動在底層鎖上調用acquire()和release()。 |
表4-2?ACE中的守衛
4.2.3?ACE條件(Condition)類屬
ACE_Condition類是針對OS條件變量原語的包裝類。線程常常需要特定條件被滿足才能繼續它的操作。條件變量不是被用作互斥原語,而是用作特定條件已經滿足的指示器。在使用條件變量時,你的程序應該完成以下步驟:
·?獲取全局資源(例如,消息隊列)的鎖(互斥體)。?
·?檢查條件(例如,消息隊列里有空間嗎?)。?
·?如果條件失敗,調用條件變量的wait()方法。等待在未來條件變為真。?
·?當另一線程在全局資源上執行操作時,它發信號(signal())給所有其他在此資源上測試條件的線程(例如,另一線程從消息隊列中取出一個消息,然后通過條件變量發送信號,以使阻塞在wait()上的線程能夠再嘗試將它們的消息插入隊列)。?
·?在醒來之后,重新檢查條件現在是否為真。如為真,則在全局資源上執行操作(例如,將消息插入全局消息隊列)?
需要特別注意的是,在阻塞wait調用中之前,條件變量機制(也就是ACE_Cond)負責釋放全局資源上的互斥體。如果沒有進行此操作,將沒有其他的線程能夠在此資源上工作(該資源是條件改變的原因)。同樣,一旦阻塞線程收到信號、重又醒來,它在檢查條件之前會在內部重新獲取鎖。
注意主線程首先獲取一個互斥體,然后對條件進行測試。如果條件不為真,主線程就等待在此條件變量上。條件變量隨即自動釋放互斥體,并使主線程進入睡眠。條件變量總是像這樣與互斥體一起使用。這是一種可如下描述的一般模式[1]:
while(?expression?NOT?TRUE?)?wait?on?condition?variable;
記住條件變量不是用于互斥,而是用于我們所描述的發送信號功能。
4.2.4?雜項同步類
除了上面描述的同步類,ACE還包括其他一些同步類,比如ACE_Barrier和ACE_Atomic_Op。
4.2.4.1?ACE中的柵欄(Barrier)
柵欄有一個好名字,因為它恰切地描述了柵欄應做的事情。一組線程可以使用柵欄來進行共同的相互同步。組中的每個線程各自執行,直到到達柵欄,就阻塞在那里。在所有相關線程到達柵欄后,它們就全部繼續它們的執行。就是說,它們一個接一個地阻塞,等待其他的線程到達柵欄;一旦所有線程都到達了它們的執行路徑中的“柵欄點”,它們就一起重新啟動。
在ACE中,柵欄在ACE_Barrier類中實現。在柵欄對象被實例化時,它將要等待的線程的數目會作為參數傳入。一旦到達執行路徑中的“柵欄點”,每個線程都在柵欄對象上發出wait()調用。它們在這里阻塞,直到其他線程到達它們各自的“柵欄點”,然后再一起繼續執行。當柵欄從相關線程那里接收了適當數目的wait()調用時,它就同時喚醒所有阻塞的線程。
4.2.4.2?原子操作(Atomic?Op)
ACE_Atomic_Op類用于將同步透明地參數化進基本的算術運算中。ACE_Atomic_Op是一種模板類,鎖定機制和需要參數化的類型被作為參數傳入其中。ACE是這樣來實現此機制的:重載所有算術操作符,并確保在操作前獲取鎖,在操作后釋放它。運算本身被委托給通過模板傳入的的類。
4.3?使用ACE_THREAD_MANAGER進行線程管理
我們可以使用ACE_Thread包裝類來創建和銷毀線程。但是,該包裝類的功能比較有限。ACE_Thread_Manager提供了ACE_Thread中的功能的超集。特別地,它增加了管理功能,以使啟動、取消、掛起和恢復一組相關線程變得更為容易。它用于創建和銷毀成組的線程和任務(ACE_Task是一種比線程更高級的構造,可在ACE中用于進行多線程編程。我們將在后面再來討論任務)。它還提供了這樣的功能:發送信號給一組線程,或是在一組線程上等待,而不是像我們在前面的例子中所看到的那樣,以一種不可移植的方式來調用join()。
4.4?線程專有存儲(Thread?Specific?Storage)
對于各個線程來說,可能需要不同的全局或靜態數據。可使用線程專有存儲來滿足此需求。像輸入端口這樣的結構可放在線程專有存儲中,并可像邏輯上的靜態或全局變量一樣被訪問;而實際上它對線程來說是私有的。
傳統上,線程專有存儲通過讓人迷惑的底層操作系統API來實現。在ACE中,TSS通過使用ACE_TSS模板類來實現。需要成為線程專有的類被傳入ACE_TSS模板,然后可以使用C++的->操作符來調用它的全部公共方法。
第5章?任務和主動對象(Active?Object):并發編程模式(多線程)
5.1?主動對象
那么到底什么是主動對象呢?傳統上,所有的對象都是被動的代碼段,對象中的代碼是在對它發出方法調用的線程中執行的。也就是,調用線程(calling?threads)被“借出”,以執行被動對象的方法。
而主動對象卻不一樣。這些對象持有它們自己的線程(甚或多個線程),并將這個線程用于執行對它們的任何方法的調用。因而,如果你想象一個傳統對象,在里面封裝了一個線程(或多個線程),你就得到了一個主動對象。
例如,設想對象“A”已在你的程序的main()函數中被實例化。當你的程序啟動時,OS創建一個線程,以從main()函數開始執行。如果你調用對象A的任何方法,該線程將“流過”那個方法,并執行其中的代碼。一旦執行完成,該線程返回調用該方法的點并繼續它的執行。但是,如果”A”是主動對象,事情就不是這樣了。在這種情況下,主線程不會被主動對象借用。相反,當”A”的方法被調用時,方法的執行發生在主動對象持有的線程中。另一種思考方法:如果調用的是被動對象的方法(常規對象),調用會阻塞(同步的);而另一方面,如果調用的是主動對象的方法,調用不會阻塞(異步的)。
5.2?ACE_Task
ACE_Task是ACE中的任務或主動對象“處理結構”的基類。在ACE中使用了此類來實現主動對象模式。所有希望成為“主動對象”的對象都必須從此類派生。你也可以把ACE_TASK看作是更高級的、更為面向對象的線程類。
當我們在前一章中使用ACE_Thread包裝時,你一定已經注意到了一些“不好”之處。那一章中的大多數程序都被分解為函數、而不是對象。這是因為ACE_Thread包裝需要一個全局函數名、或是靜態方法作為參數。隨后該函數(靜態方法)就被用作所派生的線程的“啟動點”。這自然就使得程序員要為每個線程寫一個函數。如我們已經看到的,這可能會導致非面向對象的程序分解。
相反,ACE_Task處理的是對象,因而在構造OO程序時更便于思考。因此,在大多數情況下,當你需要構建多線程程序時,較好的選擇是使用ACE_Task的子類。這樣做有若干好處。首要的是剛剛所提到的,這可以產生更好的OO軟件。其次,你不必操心你的線程入口是否是靜態的,因為ACE_Task的入口是一個常規的成員函數。而且,我們會看到ACE_Task還包括了一種用于與其他任務進行通信的易于使用的機制。
重申剛才所說的,ACE_Task可用作:
·?更高級的線程(我們稱之為任務)。?
·?主動對象模式中的主動對象。?
ACE_Task的結構:每個任務都含有一或多個線程,以及一個底層消息隊列。各個任務通過這些消息隊列進行通信。但是,消息隊列并非是程序員需要關注的對象。發送任務可以使用putq()調用來將消息插入到另一任務的消息隊列中。隨后接收任務就可以通過使用getq()調用來從它自己的消息隊列里將消息提取出來。
5.2.2?創建和使用任務
要創建任務或主動對象,必須從ACE_Task類派生子類。在子類派生之后,必須采取以下步驟:
·?實現服務初始化和終止方法:open()方法應該包含所有專屬于任務的初始化代碼。其中可能包括諸如連接控制塊、鎖和內存這樣的資源。close()方法是相應的終止方法。?
·?調用啟用(Activation)方法:在主動對象實例化后,你必須通過調用activate()啟用它。要在主動對象中創建的線程的數目,以及其他一些參數,被傳遞給activate()方法。activate()方法會使svc()方法成為所有它生成的線程的啟動點。?
·?實現服務專有的處理方法:如上面所提到的,在主動對象被啟用后,各個新線程在svc()方法中啟動(如何區分并調用不同線程)。應用開發者必須在子類中定義此方法。?
5.2.3?任務間通信
如前面所提到的,ACE中的每個任務都有一個底層消息隊列。這個消息隊列被用作任務間通信的一種方法。當一個任務想要與另一任務“談話”時,它創建一個消息,并將此消息放入(putq())它想要與之談話的任務的消息隊列。接收任務通常用getq()從消息隊列里獲取消息。如果隊列中沒有數據可用,它就進入休眠狀態。如果有其他任務將消息插入它的隊列,它就會蘇醒過來,從隊列中拾取數據并處理它。因而,在這種情況下,接收任務將從發送任務那里接收消息,并以應用特定的方式作出反饋。
5.3?主動對象模式(Active?Object?Pattern)
主動對象模式用于降低方法執行和方法調用之間的耦合。該模式描述了另外一種更為透明的任務間通信方法。
該模式使用ACE_Task類作為主動對象。在這個對象上調用方法時,它就像是常規對象一樣。就是說,方法調用是通過同樣的->操作符來完成的,其不同在于這些方法的執行發生于封裝在ACE_Task中的線程內。在使用被動或主動對象進行編程時,客戶程序看不到什么區別,或僅僅是很小的區別。對于構架開發者來說,這是非常有用的,因為開發者需要使構架客戶與構架的內部結構屏蔽開來。這樣構架用戶就不必去擔心線程、同步、會合點(rendezvous),等等。
5.3.1?主動對象模式工作原理
主動對象模式是ACE實現的較為復雜的模式中的一個。該模式有如下參與者:
1.?主動對象(基于ACE_Task)。?
2.?ACE_Activation_Queue。?
3.?若干ACE_Method_Object(主動對象的每個方法都需要有一個方法對象)。?
4.?若干ACE_Future對象(每個要返回結果的方法都需要這樣一個對象)。?
我們已經看到,ACE_Task是怎樣創建和封裝線程的。要使ACE_Task成為主動對象,需要完成一些額外的工作:
必須為所有要從客戶異步調用的方法編寫方法對象。每個方法對象都派生自ACE_Method_Object,并會實現它的call()方法。每個方法對象還維護上下文信息(比如執行方法所需的參數,以及用于獲取返回值的ACE_Future對象。這些值作為私有屬性維護)。你可以把方法對象看作是方法調用的“罩子”(closure)。客戶發出方法調用,使得相應的方法對象被實例化,并被放入啟用隊列(activation?queue)中。方法對象是命令(Command)模式的一種形式(參見有關設計模式的參考文獻)。
ACE_Activation_Queue是一個隊列,方法對象在等待執行時被放入其中。因而啟用隊列中含有所有等待調用的方法(以方法對象的形式)。封裝在ACE_Task中的線程保持阻塞,等待任何方法對象被放入啟用隊列。一旦有方法對象被放入,任務就將該方法對象取出,并調用它的call()方法。call()方法應該隨即調用該方法在ACE_Task中的相應實現。在方法實現返回后,call()方法在ACE_Future對象中設置(set())所獲得的結果。
客戶使用ACE_Future對象獲取它在主動對象上發出的任何異步操作的結果。一旦客戶發出異步調用,立即就會返回一個ACE_Future對象。于是客戶就可以在任何它喜歡的時候去嘗試從ACE_Future對象中獲取結果。如果客戶試圖在結果被設置之前從ACE_Future對象中提取結果,客戶將會阻塞。如果客戶不希望阻塞,它可以通過使用ready()調用來輪詢(poll)ACE_Future對象。如果結果已被設置,該方法返回1;否則就返回0。ACE_Future對象基于“多態期貨”(polymorphic?futures)的概念。
call()方法的實現應該將返回的ACE_Future對象的內部值設置為從調用實際的方法實現所獲得的結果(這個實際的方法實現在ACE_Task中編寫)。
第6章?反應堆(Reactor):用于事件多路分離和分派的體系結構模式(事件驅動-異步事件)
反應堆本質上提供一組更高級的編程抽象,簡化了事件驅動的分布式應用的設計和實現。除此而外,反應堆還將若干不同種類的事件的多路分離集成到易于使用的API中。特別地,反應堆對基于定時器的事件、信號事件、基于I/O端口監控的事件和用戶定義的通知進行統一地處理。
ACE中的反應堆與若干內部和外部組件協同工作。其基本概念是反應堆構架檢測事件的發生(通過在OS事件多路分離接口上進行偵聽),并發出對預登記事件處理器(event?handler)對象中的方法的“回調”(callback)。該方法由應用開發者實現,其中含有應用處理此事件的特定代碼。于是用戶(也就是,應用開發者)必須:
1.?創建事件處理器,以處理他所感興趣的某事件。?
2.?在反應堆上登記,通知說他有興趣處理某事件,同時傳遞他想要用以處理此事件的事件處理器的指針給反應堆。?
隨后反應堆構架將自動地:
1.?在內部維護一些表,將不同的事件類型與事件處理器對象關聯起來。?
2.?在用戶已登記的某個事件發生時,反應堆發出對處理器中相應方法的回調。?
6.2?事件處理器
反應堆模式在ACE中被實現為ACE_Reactor類,它提供反應堆構架的功能接口。
如上面所提到的,反應堆將事件處理器對象作為服務提供者使用。一旦反應堆成功地多路分離和分派了某事件,事件處理器對象就對它進行處理。因此,反應堆會在內部記住當特定類型的事件發生時,應該回調哪一個事件處理器對象。當應用在反應堆上登記它的處理器對象,以處理特定類型的事件時,反應堆會創建這種事件和相應的事件處理器的關聯。
因為反應堆需要記錄哪一個事件處理器將被回調,它需要知道所有事件處理器對象的類型。這是通過替換模式(Substitution?Pattern)的幫助來實現的(或者換句話說,通過“是……類型”(is?a?type?of)變種繼承)。該構架提供名為ACE_Event_Handler的抽象接口類,所有應用特有的事件處理器都必須由此派生(這使得應用特有的處理器都具有相同的類型,即ACE_Event_Handler,所以它們可以相互替換)。
ACE_Event_Handler類擁有若干不同的“handle”(處理)方法,每個處理方法被用于處理不同種類的事件。當應用程序員對特定事件感興趣時,他就對ACE_Event_Handler類進行子類化,并實現他感興趣的處理方法。如上面所提到的,隨后他就在反應堆上為特定事件“登記”他的事件處理器類。于是反應堆就會保證在此事件發生時,自動回調在適當的事件處理器對象中的適當的”handle”方法。
使用ACE_Reactor基本上有三個步驟:
·?創建ACE_Event_Handler的子類,并在其中實現適當的“handle_”方法,以處理你想要此事件處理器為之服務的事件類型。(參看表6-1來確定你需要實現哪一個“handle_”方法。注意你可以使用同一個事件處理器對象處理多種類型的事件,因而可以重載多于一個的“handle_”方法。)?
·?通過調用反應堆對象的register_handler(),將你的事件處理器登記到反應堆。?
·?在事件發生時,反應堆將自動回調相應的事件處理器對象的適當的“handle_”方法。
| ACE_Event_Handler中的處理方法 | 在子類中重載,所處理事件的類型: ? |
| handle_signal() | 信號。當任何在反應堆上登記的信號發生時,反應堆自動回調該方法。 |
| handle_input() | 來自I/O設備的輸入。當I/O句柄(比如UNIX中的文件描述符)上的輸入可用時,反應堆自動回調該方法。 |
| handle_exception() | 異常事件。當已在反應堆上登記的異常事件發生時(例如,如果收到SIGURG(緊急信號)),反應堆自動回調該方法。 |
| handle_timeout() | 定時器。當任何已登記的定時器超時的時候,反應堆自動回調該方法。 |
| handle_output() | I/O設備輸出。當I/O設備的輸出隊列有可用空間時,反應堆自動回調該方法。 |
表6-1?ACE_Event_Handler中的處理方法及其對應事件
6.2.1?事件處理器登記
登記事件處理器、以處理特定事件,是在反應堆上調用register_handler()方法來完成的。register_handler()方法是重載方法,就是說,實際上有若干方法可用于登記不同的事件類型,每個方法都叫做register_handler()。但是它們有著不同的特征:它們的參數各不相同。基本上,register_handler()方法采用handle/event_handle元組或signal/event_handler元組作為參數,并將它們加入反應堆的內部分派表。當有事件在handle上發生時,反應堆在它的內部分派表中查找相應的event_handler,并自動在它找到的event_handler上回調適當的方法。
6.2.2?事件處理器的拆除和生存期管理
一旦所需的事件被處理后,可能就無需再讓事件處理器登記在反應堆上。因而,反應堆提供了從它的內部分派表中拆除事件處理器的技術。一旦事件處理器被拆除,它就不再會被反應堆回調。把這樣的死掉的句柄從反應堆里拆除是很重要的,因為,如果不這樣做,反應堆將會把此句柄標記為“讀就緒”,并會持續不斷地回調此事件處理器的handle_方法。
6.2.2.1?從反應堆內部分派表中隱式拆除事件處理器
隱式拆除是更為常用的從反應堆中拆除事件處理器的技術。事件處理器的每個“handle_”方法都會返回一個整數給反應堆。如果此整數為0,在處理器方法完成后、事件處理器將保持在反應堆上的登記。但是,如果“handle_”方法返回的整數<0,反應堆將自動回調此事件處理器的handle_close()方法,并將它從自己的內部分派表中拆除。handle_close()方法用于執行處理器特有的任何清除工作,它們需要在事件處理器被拆除前完成;其中可以包括像刪除處理器申請的動態內存、或關閉日志文件這樣的工作
6.2.2.2從反應堆內部分派表中顯式拆除事件處理器
另一種從反應堆的內部表中拆除事件處理器的方法是顯式地調用反應堆的remove_handler()方法集。該方法也是重載方法,就像register_handler()一樣。它采用需要拆除的處理器的句柄或信號號碼作為參數,并將該處理器從反應堆的內部分派表中拆除。在remove_handler()被調用時,反應堆還自動調用事件處理器的handle_close()方法。可以這樣來對其進行控制:將ACE_Event_Handler::DONT_CALL掩碼傳給remove_handler(),從而使得handle_close()方法不會被調用。
6.3?通過反應堆進行事件處理
6.3.1?I/O事件多路分離
通過在具體的事件處理器類中重載handle_input()方法,反應堆可用于處理基于I/O設備的輸入事件。這樣的I/O可以發生在磁盤文件、管道、FIFO或網絡socket上。為了進行基于I/O設備的事件處理,反應堆在內部使用從操作系統獲取的設備句柄(在基于UNIX的系統中,該句柄是在文件或socket打開時,OS返回的文件描述符。在Windows中該局柄是由Windows返回的設備句柄)。網絡應用顯然是最適于這樣的多路分離的應用之一。下面的例子演示反應堆是怎樣與具體接受器一起使用來構造一個服務器的。
?
#include "ace/Reactor.h"#include "ace/SOCK_Acceptor.h"#define PORT_NO 1024typedef ACE_SOCK_Acceptor Acceptor;//forward declarationclass My_Accept_Handler;class My_Input_Handler: public ACE_Event_Handler{public://ConstructorMy_Input_Handler(){ACE_DEBUG((LM_DEBUG,"Constructor\n"));}//Called back to handle any input receivedint handle_input(ACE_HANDLE){//receive the datapeer_.recv_n(data,12);ACE_DEBUG((LM_DEBUG,"%s\n",data));// do something with the input received.// ...//keep yourself registered with the reactorreturn 0;}//Used by the reactor to determine the underlying handleACE_HANDLE get_handle() const{return this->peer_.get_handle();//return this->peer_i().get_handle();}//Returns a reference to the underlying stream.ACE_SOCK_Stream &peer_i(){return this->peer_;}ACE_SOCK_Stream peer_; //publicprivate://ACE_SOCK_Stream peer_;char data [12];};class My_Accept_Handler: public ACE_Event_Handler{public://ConstructorMy_Accept_Handler(ACE_Addr &addr){this->open(addr);}//Open the peer_acceptor so it starts to ”listen”//for incoming clients.int open(ACE_Addr &addr){peer_acceptor.open(addr);return 0;}//Overload the handle input methodint handle_input(ACE_HANDLE handle){//Client has requested connection to server.//Create a handler to handle the connectionMy_Input_Handler *eh = new My_Input_Handler();//Accept the connection "into" the Event Handlerif (this->peer_acceptor.accept (eh->peer_, // stream0, // remote address0, // timeout1) ==-1) //restart if interrupted{ACE_DEBUG((LM_ERROR,"Error in connection\n"));}ACE_DEBUG((LM_DEBUG,"Connection established\n"));//Register the input event handler for readingACE_Reactor::instance()->register_handler(eh,ACE_Event_Handler::READ_MASK);//Unregister as the acceptor is not expecting new clientsreturn -1;}//Used by the reactor to determine the underlying handleACE_HANDLE get_handle(void) const{return this->peer_acceptor.get_handle();}private:Acceptor peer_acceptor;};int main(int argc, char * argv[]){//Create an address on which to receive connectionsACE_INET_Addr addr(PORT_NO);//Create the Accept Handler which automatically begins to “listen”//for client requests for connectionsMy_Accept_Handler *eh=new My_Accept_Handler(addr);//Register the reactor to call back when incoming client connectsACE_Reactor::instance()->register_handler(eh,ACE_Event_Handler::ACCEPT_MASK);//Start the event loopwhile(1){ACE_Reactor::instance()->handle_events();}return 0;}?
第一個具體事件處理器My_Accept_Handler用于接受和建立從客戶到來的連接。另一個事件處理器是My_Input_Handler,它用于在連接建立后對連接進行處理。因而,My_Accept_Handler接受連接,并將實際的處理委托給My_Input_Handler。
我們首先創建了一個ACE_INET_Addr地址對象,將我們希望在其上接受連接的端口作為參數傳給它。其次,實例化一個類型為My_Accept_Handler的對象。隨后地址對象通過My_Accept_Handler的構造器傳遞給它。My_Accept_Handler有一個用于連接建立的底層“具體接受器”(在講述“IPC”的一章中有與具體接受器相關的內容)。My_Accept_Handler的構造器將對新連接的“偵聽”委托給該具體接受器的open()方法。在處理器開始偵聽連接后,它在反應堆上登記,通知說在接收到新連接請求時,它需要被回調。為完成此操作,我們采用ACE_Event_Handler::ACCEPT_MASK掩碼調用register_handler()。
當反應堆被告知要登記處理器時,它執行“雙重分派”來確定事件處理器的底層句柄。為完成此操作,它調用get_handler()方法。因為反應堆使用get_handle()方法來確定底層流的句柄,在My_Accept_Handler中必須實現get_handle()方法。在此例中,我們簡單地調用具體接受器的get_handle(),它會將適當的句柄返回給反應堆。
一旦在該句柄上接收到新的連接請求,反應堆會自動地回調My_Accept_Handler的handle_input()方法。隨后Accept?Handler(接受處理器)實例化一個新的Input?Handler(輸入處理器),并調用具體接受器的accept()方法來實際地建立連接。注意Input?Handler底層的流是作為accept()調用的第一個參數傳入的。這使得新實例化的Input?Handler中的流被設置為在連接建立(由accept()完成)后立即創建的新流。隨后Accept?Handler將Input?Handler登記到反應堆,通知它如果有任何可讀的輸入就進行回調(使用ACE_Event_Handler::READ_MASK)。隨后接受處理器返回-1,使自己從反應堆的內部事件分派表中被拆除。
現在,如果有任何輸入從客戶到達,反應堆將自動回調My_Input_Handler::handle_input()。注意在My_Input_Handler的handle_input()方法中,返回給反應堆是0。這指示我們希望保持它的登記;反之在My_Accept_Handler中我們在它的handle_input()中返回-1,以確保它被注銷。
除了在上面的例子中使用的READ_MASK和ACCEPT_MASK而外,還有若干其他的掩碼,可在登記或是拆除處理器時使用。這些掩碼如表6-2所示,它們可與register_handler()和remove_handler()方法一起使用。每個掩碼保證反應堆回調事件處理器時的不同行為方式,通常這意味著不同的“handle”方法會被回調。
| 掩碼 | 回調方法 | 何時 | 和……一起使用 |
| ACE_Event_Handler::READ_MASK | handle_input() | 在句柄上有數據可讀時。 | register_handler() |
| ACE_Event_Handler::WRITE_MASK | handle_output() | 在I/O設備輸出緩沖區上有可用空間、并且新數據可以發送給它時。 | register_handler() |
| ACE_Event_Handler::TIMER_MASK | handle_close() | 傳給handle_close()以指示調用它的原因是超時。 | 接受器和連接器的handle_timeout方法。反應堆不使用此掩碼。 |
| ACE_Event_Handler::ACCEPT_MASK | handle_input() | 在OS內部的偵聽隊列上收到了客戶的新連接請求時。 | register_handler() |
| ACE_Event_Handler::CONNECT_MASK | handle_input() | 在連接已經建立時。 | register_handler() |
| ACE_Event_Handler::DONT_CALL | None. | 在反應堆的remove_handler()被調用時保證事件處理器的handle_close()方法不被調用。 | remove_handler() |
表6-2?反應堆中的掩碼
6.4?定時器(Timer)
反應堆還包括了調度定時器的方法,它們在超時的時候回調適當的事件處理器的handle_timeout()方法。為調度這樣的定時器,反應堆擁有一個schedule_timer()方法。該方法接收事件處理器(該事件處理器的handle_timeout()方法將會被回調)、以及以ACE_Time_value對象形式出現的延遲作為參數。此外,還可以指定時間間隔,使定時器在它超時后自動被復位。
反應堆在內部維護ACE_Timer_Queue,它以定時器要被調度的順序對它們進行維護。實際使用的用于保存定時器的數據結構可以通過反應堆的set_timer_queue()方法進行改變。反應堆有若干不同的定時器結構可用,包括定時器輪(timer?wheel)、定時器堆(timer?heap)和哈希式定時器輪(hashed?timer?wheel)。這些內容將在后面的部分詳細討論。
6.4.1?ACE_Time_Value
ACE_Time_Value是封裝底層OS平臺的日期和時間結構的包裝類。它基于在大多數UNIX操作系統上都可用的timeval結構;該結構存儲以秒和微秒計算的絕對時間。
其他的OS平臺,比如POSIX和Win32,使用略有不同的表示方法。該類封裝這些不同,并提供了可移植的C++接口。
ACE_Time_Value類使用運算符重載,提供簡單的算術加、減和比較。該類中的方法會對時間量進行“規范化”(normalize)。所謂規范化,是將timeval結構中的兩個域調整為規范化的編碼方式;這種編碼方式可以確保精確的比較
首先通過實現事件處理器Time_Handler的handle_timeout()方法,將其設置用以處理超時。主函數實例化Time_Handler類型的對象,并使用反應堆的schedule_timer()方法調度多個定時器(10個)。handle_timeout方法需要以下參數:指向將被回調的處理器的指針、定時器超時時間,以及一個將在handle_timeout()方法被回調時發送給它的參數。每次調用schedule_timer(),它都返回一個唯一的定時器標識符,并隨即存儲在timer_id[]數組里。這個標識符可用于在任何時候取消該定時器。在上面的例子中也演示了定時器的取消:在所有定時器被初始調度后,程序通過調用反應堆的cancel_timer()方法(使用相應的timer_id作為參數)取消了第五個定時器。
6.4.3?使用不同的定時器隊列
不同的環境可能需要不同的調度和取消定時器的方法。在下面的任一條件為真時,實現定時器的算法的性能就會成為一個問題:
·?需要細粒度的定時器。?
·?在某一時刻未完成的定時器的數目可能會非常大。?
·?算法使用過于昂貴的硬件中斷來實現。?
ACE允許用戶從若干在ACE中已存在的定時器中進行選擇,或是根據為定時器定義的接口開發他們自己的定時器。表6-3詳細列出了ACE中可用的各種定時器:
| 定時器 | 數據結構描述 | 性能 |
| ACE_Timer_Heap | 定時器存儲在優先級隊列的堆實現中。 | schedule_timer()的開銷=O(lg?n) cancel_timer()的開銷=O(lg?n) 查找當前定時器的開銷=O(1) |
| ACE_Timer_List | 定時器存儲在雙向鏈表中。 | schedule_timer()的開銷=O(n) cancel_timer()的開銷=O(1) 查找當前定時器的開銷=O(1) |
| ACE_Timer_Hash | 在這里使用的這種結構是定時器輪算法的變種。性能高度依賴于所用的哈希函數。 | schedule_timer()的開銷=最壞=O(n)?最佳=O(1) cancel_timer()的開銷=O(1) 查找當前定時器的開銷=O(1) |
| ACE_Timer_Wheel | 定時器存儲在“數組指針”(pointers?to?arrays)的數組中。每個被指向的數組都已排序。 | schedule_timer()的開銷=最壞=O(n) cancel_timer()的開銷=O(1) 查找當前定時器的開銷=O(1) |
表6-3?ACE中的定時器
6.5?處理信號(Signal)
如我們在例6-1中所看到的,反應堆含有進行信號處理的方法。處理信號的事件處理器應重載handle_signal()方法,因為該方法將在信號發生時被回調。要為信號登記處理器,可以使用多個register_handler()方法中的一個,就如同例6-1中所演示的那樣。如果對特定信號不再感興趣,通過調用remove_handler(),處理器可以被拆除,并恢復為先前安裝的信號處理器。反應堆在內部使用sigaction()系統調用來設置和恢復信號處理器。通過使用ACE_Sig_Handlers類和與其相關聯的方法,無需反應堆也可以進行信號處理。
使用反應堆進行信號處理和使用ACE_Sig_Handlers類的重要區別是基于反應堆的機制只允許應用給每個信號關聯一個事件處理器,而ACE_Sig_Handlers類允許在信號發生時,回調多個事件處理器。
6.6?使用通知(Notification)
反應堆不僅可以在系統事件發生時發出回調,也可以在用戶定義的事件發生時回調處理器。這是通過反應堆的“通知”接口來完成的;該接口由兩個方法組成:notify()和max_notify_iterations()。
通過使用notify()方法,可以明確地指示反應堆對特定的事件處理器對象發出回調。在反應堆與消息隊列、或是協作任務協同使用時,這是十分有用的。可在ASX構架組件與反應堆一起使用時找到這種用法的一些好例子。
max_notify_iterations()方法通知反應堆,每次只完成指定次數的“迭代”(iterations)。也就是說,在一次handle_events()調用中只處理指定數目的“通知”。因而如果使用max_notify_iterations()將迭代的次數設置為20,而又有25個通知同時到達,handle_events()方法一次將只處理這些通知中的20個。剩下的五個通知將在handle_events()下一次在事件循環中被調用時再處理。
事件處理循環中值得注意的一個主要區別是,程序傳遞給handle_events()一個ACE_Time_Value。如果在此時間內沒有事件發生,handle_events()方法就會結束。在handle_events()結束后,perform_notification()被調用,它使用反應堆的notify()方法來請求反應堆通知處理器(它是在事件發生時被作為參數傳入的)。隨后反應堆就使用所收到的掩碼來執行對處理器的適當“handle”方法的調用。在此例中,通過傳遞ACE_Event_Handler::READ_MASK,我們使用notify()來通知我們的事件處理器有輸入,從而使得反應堆回調該處理器的handle_input()方法。因為我們已將max_notify_iterations設為5,所以在一次handle_events()調用過程中反應堆實際上只會發出5個通知。
第7章?接受器(Acceptor)和連接器(Connector):連接建立模式
接受器/連接器模式設計用于降低連接建立與連接建立后所執行的服務之間的耦合。因為該模式降低了服務和連接建立方法之間的耦合,非常容易改動其中一個,而不影響另外一個,從而也就可以復用以前編寫的連接建立機制和服務例程的代碼。
7.1?接受器模式
在ACE中,接收器模式借助名為ACE_Acceptor的“工廠”(Factory)實現。工廠(通常)是用于對助手對象的實例化過程進行抽象的類。在面向對象設計中,復雜的類常常會將特定功能委托給助手類。復雜的類對助手的創建和委托必須很靈活。這種靈活性是在工廠的幫助下獲得的。工廠允許一個對象通過改變它所委托的對象來改變它的底層策略,而工廠提供給應用的接口卻是一樣的,這樣,可能根本就無需對客戶代碼進行改動(有關工廠的更多信息,請閱讀“設計模式”中的參考文獻)。
ACE_Acceptor工廠允許應用開發者改變“助手”對象,以用于:
·?被動連接建立?
·?連接建立后的處理?
同樣地,ACE_Connector工廠允許應用開發者改變“助手”對象,以用于:
·?主動連接建立?
·?連接建立后的處理?
ACE_Acceptor被實現為模板容器,通過兩個類作為實參來進行實例化。第一個參數實現特定的服務(類型為ACE_Event_Handler。因為它被用于處理I/O事件,所以必須來自事件處理類層次),應用在建立連接后執行該服務;第二個參數是“具體的”接受器(可以是在IPC_SAP一章中討論的各種變種)。
特別要注意的是ACE_Acceptor工廠和底層所用的具體接受器是非常不同的。具體接受器可完全獨立于ACE_Acceptor工廠使用,而無需涉及我們在這里討論的接受器模式(獨立使用接受器已在IPC_SAP一章中討論和演示)。ACE_Acceptor工廠內在于接受器模式,并且不能在沒有底層具體接受器的情況下使用。ACE_Acceptor使用底層的具體接受器來建立連接。如我們已看到的,有若干ACE的類可被用作ACE_Acceptor工廠模板的第二個參數(也就是,具體接受器類)。但是服務處理類必須由應用開發者來實現,而且其類型必須是ACE_Event_Handler。ACE_Acceptor工廠可以這樣來實例化:
typedef?ACE_Acceptor<My_Service_Handler,ACE_SOCK_ACCEPTOR>?MyAcceptor;
這里,名為My_Service_Handler的事件處理器和具體接受器ACE_SOCK_ACCEPTOR被傳給MyAcceptor。ACE_SOCK_ACCEPTOR是基于BSD?socket流家族的TCP接受器(各種可傳給接受器工廠的不同類型的接受器,見表7-1和IPC一章)。請再次注意,在使用接受器模式時,我們總是處理兩個接受器:名為ACE_Acceptor的工廠接受器,和ACE中的某種具體接受器,比如ACE_SOCK_ACCEPTOR(你可以創建自定義的具體接受器來取代ACE_SOCK_ACCEPTOR,但你將很可能無需改變ACE_Acceptor工廠類中的任何東西)。
重要提示:ACE_SOCK_ACCEPTOR實際上是一個宏,其定義為:
#define?ACE_SOCK_ACCEPTOR?ACE_SOCK_Acceptor,?ACE_INET_Addr
我們認為這個宏的使用是必要的,因為在類中的typedefs在某些平臺上無法工作。如果不是這樣的話,ACE_Acceptor就可以這樣來實例化:
typedef?ACE_Acceptor<My_Service_Handler,ACE_SOCK_Acceptor>MyAcceptor;
7.1.1?組件
如上面的討論所說明的,在接受器模式中有三個主要的參與類:
·?具體接受器:它含有建立連接的特定策略,連接與底層的傳輸協議機制系在一起。下面是在ACE中的各種具體接受器的例子:ACE_SOCK_ACCEPTOR(使用TCP來建立連接)、ACE_LSOCK_ACCEPTOR(使用UNIX域socket來建立連接),等等。?
·?具體服務處理器:由應用開發者編寫,它的open()方法在連接建立后被自動回調。接受器構架假定服務處理類的類型是 ACE_Event_Handler,這是ACE定義的接口類(該類已在反應堆一章中詳細討論過)。另一個特別為接受器和連接器模式的服務處理而創建的類是ACE_Svc_Handler。該類不僅基于ACE_Event_Handler接口(這是使用反應堆所必需的),同時還基于在ASX流構架中使用的ACE_Task類。ACE_Task類提供的功能有:創建分離的線程、使用消息隊列來存儲到來的數據消息、并發地處理它們,以及其他一些有用的功能。如果與接受器模式一起使用的具體服務處理器派生自ACE_Svc_Handler、而不是ACE_Event_Handler,它就可以獲得這些額外的功能。對ACE_Svc_Handler中的額外功能的使用,在這一章的高級課程里詳細討論。在下面的討論中,我們將使用ACE_Svc_Handler作為我們的事件處理器。在簡單的ACE_Event_Handler和ACE_Svc_Handler類之間的重要區別是,后者擁有一個底層通信流組件。這個流在ACE_Svc_Handler模板被實例化的時候設置。而在使用ACE_Event_Handler的情況下,我們必須自己增加I/O通信端點(也就是,流對象),作為事件處理器的私有數據成員。因而,在這樣的情況下,應用開發者應該將他的服務處理器創建為ACE_Svc_Handler類的子類,并首先實現將被構架自動回調的open()方法。此外,因為ACE_Svc_Handler是一個模板,通信流組件和鎖定機制是作為模板參數被傳入的。?
·?反應堆:與ACE_Acceptor協同使用。如我們將看到的,在實例化接受器后,我們啟動反應堆的事件處理循環。反應堆,如先前所解釋的,是一個事件分派類;而在此情況下,它被接受器用于將連接建立事件分派到適當的服務處理例程。?
| 接受器類型 | 所用地址 | 所用流 | 具體接受器 |
| TCP流接受器 | ACE_INET_Addr | ACE_SOCK_STREAM | ACE_SOCK_ACCEPTOR |
| UNIX域本地流socket接受器 | ACE_UNIX_Addr | ACE_LSOCK_STREAM | ACE_LSOCK_ACCEPTOR |
| 管道作為底層通信機制 | ACE_SPIPE_Addr | ACE_SPIPE_STREAM | ACE_SPIPE_ACCEPTOR |
表7-1?ACE中的連接建立機制
7.2?連接器
連接器與接受器非常類似。它也是一個工廠,但卻是用于主動地連接遠程主機。在連接建立后,它將自動回調適當的服務處理對象的open()方法。連接器通常被用在你本來會使用BSD?connect()調用的地方。在ACE中,連接器,就如同接受器,被實現為名為ACE_Connector的模板容器類。如先前所提到的,它需要兩個參數,第一個是事件處理器類,它在連接建立時被調用;第二個是“具體的”連接器類。
你必須注意,底層的具體連接器和ACE_Connector工廠是非常不一樣的。ACE_Connector工廠使用底層的具體連接器來建立連接。隨后ACE_Connector工廠使用適當的事件或服務處理例程(通過模板參數傳入)來在具體的連接器建立起連接之后處理新連接。如我們在IPC一章中看到的,沒有ACE_Connector工廠,也可以使用這個具體的連接器。但是,沒有具體的連接器類,就會無法使用ACE_Connector工廠(因為要由具體的連接器類來實際處理連接建立)。
下面是對ACE_Connector類進行實例化的一個例子:
typedef?ACE_Connector<My_Svc_Handler,ACE_SOCK_CONNECTOR>?MyConnector;
這個例子中的第二個參數是具體連接器類ACE_SOCK_CONNECTOR。連接器和接受器模式一樣,在內部使用反應堆來在連接建立后回調服務處理器的open()方法。我們可以復用我們為前面的接受器例子所寫的服務處理例程。
7.3?高級課程
下面的部分更為詳細地解釋接受器和連接器模式實際上是如何工作的。如果你想要調諧服務處理和連接建立策略(其中包括調諧底層具體連接器將要使用的服務處理例程的創建和并發策略,以及連接建立策略),對該模式的進一步了解就是必要的。此外,還有一部分內容解釋怎樣使用通過ACE_Svc_Handler類自動獲得的高級特性。最后,我們說明怎樣與接受器和連接器模式一起使用簡單的輕量級ACE_Event_Handler。
7.3.1?ACE_SVC_HANDLER類
如上面所提到的,ACE_Svc_Handler類基于ACE_Task(它是ASX流構架的一部分)和ACE_Event_Handler接口類。因而ACE_Svc_Handler既是任務,又是事件處理器。這里我們將簡要介紹ACE_Task和ACE_Svc_Handler的功能。
7.3.1.1?ACE_Task
ACE_Task被設計為與ASX流構架一起使用;ASX基于UNIX系統V中的流機制。在設計上ASX與Larry?Peterson構建的X-kernel協議工具非常類似。
ASX的基本概念是:到來的消息會被分配給由若干模塊(module)組成的流。每個模塊在到來的消息上執行某種固定操作,然后把它傳遞給下一個模塊作進一步處理,直到它到達流的末端為止。模塊中的實際處理由任務來完成。每個模塊通常有兩個任務,一個用于處理到來的消息,一個用于處理外出的消息。在構造協議棧時,這種結構是非常有用的。因為每個模塊都有固定的簡單接口,所創建的模塊可以很容易地在不同的應用間復用。例如,設想一個應用,它處理來自數據鏈路層的消息。程序員會構造若干模塊,每個模塊分別處理不同層次的協議。因而,他會構造一個單獨的模塊,進行網絡層處理;另一個進行傳輸層處理;還有一個進行表示層處理。在構造這些模塊之后,它們可以(在ASX的幫助下)被“串”成一個流來使用。如果后來創建了一個新的(也許是更好的)傳輸模塊,就可以在不對程序產生任何影響的情況下、在流中替換先前的傳輸模塊。注意模塊就像是容納任務的容器。這些任務是實際的處理元件。一個模塊可能需要兩個任務,如同在上面的例子中;也可能只需要一個任務。如你可能會猜到的,ACE_Task是模塊中被稱為任務的處理元件的實現。
7.3.1.2任務通信的體系結構
每個ACE_Task都有一個內部的消息隊列,用以與其他任務、模塊或是外部世界通信。如果一個ACE_Task想要發送一條消息給另一個任務,它就將此消息放入目的任務的消息隊列中。一旦目的任務收到此消息,它就會立即對它進行處理。
所有ACE_Task都可以作為0個或多個線程來運行。消息可以由多個線程放入ACE_Task的消息隊列,或是從中取出,程序員無需擔心破壞任何數據結構。因而任務可被用作由多個協作線程組成的系統的基礎構建組件。各個線程控制都可封裝在ACE_Task中,與其他任務通過發送消息到它們的消息隊列來進行交互。
這種體系結構的唯一問題是,任務只能通過消息隊列與在同一進程內的其他任務相互通信。ACE_Svc_Handler解決了這一問題,它同時繼承自ACE_Task和ACE_Event_Handler,并且增加了一個私有數據流。這種結合使得ACE_Svc_Handler對象能夠用作這樣的任務(并發,同一進程);它能夠處理事件(異步,不同進程)、并與遠地主機的任務間發送和接收數據。
ACE_Task被實現為模板容器,它通過鎖定機制來進行實例化。該鎖用于保證內部的消息隊列在多線程環境中的完整性。如先前所提到的,ACE_Svc_Handler模板容器不僅需要鎖定機制,還需要用于與遠地任務通信的底層數據流來作為參數。
7.3.1.3?創建ACE_Svc_Handler
ACE_Svc_Handler模板通過鎖定機制和底層流來實例化,以創建所需的服務處理器。如果應用只是單線程的,就不需要使用鎖,可以用ACE_NULL_SYNCH來將其實例化。但是,如果我們想要在多線程應用中使用這個模板,可以這樣來進行實例化:
class?MySvcHandler:
public?ACE_Svc_Handler<ACE_SOCK_STREAM,ACE_MT_SYNCH>
{
}
7.3.1.4?在服務處理器中創建多個線程
在上面的例7-5中,我們使用ACE_Thread包裝類和它的靜態方法spawn(),創建了單獨的線程來發送數據給遠地對端。但是,在我們完成此工作時,我們必須定義使用C++?static修飾符的文件范圍內的靜態send_data()方法。結果當然就是,我們無法訪問我們實例化的實際對象的任何數據成員。換句話說,我們被迫使send_data()成員函數成為class-wide的函數,而這并不是我們所想要的。這樣做的唯一原因是,ACE_Thread::spawn()只能使用靜態成員函數來作為它所創建的線程的入口。另一個有害的副作用是到對端流的引用也必須成為靜態的。簡而言之,這不是編寫這些代碼的最好方式。
ACE_Task提供了更好的機制來避免發生這樣的問題。每個ACE_Task都有activate()方法,可用于為ACE_Task創建線程。所創建的線程的入口是非靜態成員函數svc()。因為svc()是非靜態函數,它可以調用任何對象實例專有的數據或成員函數。ACE對程序員隱藏了該機制的所有實現細節。activate()方法有著非常多的用途,它允許程序員創建多個線程,所有這些線程都使用svc()方法作為它們的入口。還可以設置線程優先級、句柄、名字,等等。activate()方法的原型是:
//?=?Active?object?activation?method.
virtual?int?activate?(long?flags?=?THR_NEW_LWP,
int?n_threads?=?1,
int?force_active?=?0,
long?priority?=?ACE_DEFAULT_THREAD_PRIORITY,
int?grp_id?=?-1,
ACE_Task_Base?*task?=?0,
ACE_hthread_t?thread_handles[]?=?0,
void?*stack[]?=?0,
size_t?stack_size[]?=?0,
ACE_thread_t?thread_names[]?=?0);
第一個參數flags描述將要創建的線程所希望具有的屬性。在線程一章里有詳細描述。可用的標志有:
THR_CANCEL_DISABLE,?THR_CANCEL_ENABLE,?THR_CANCEL_DEFERRED,
THR_CANCEL_ASYNCHRONOUS,?THR_BOUND,?THR_NEW_LWP,?THR_DETACHED,
THR_SUSPENDED,?THR_DAEMON,?THR_JOINABLE,?THR_SCHED_FIFO,
THR_SCHED_RR,?THR_SCHED_DEFAULT
第二個參數n_threads指定要創建的線程的數目。第三個參數force_active用于指定是否應該創建新線程,即使activate()方法已在先前被調用過、因而任務或服務處理器已經在運行多個線程。如果此參數被設為false(0),且如果activate()是再次被調用,該方法就會設置失敗代碼,而不會生成更多的線程。
第四個參數用于設置運行線程的優先級。缺省情況下,或優先級被設為ACE_DEFAULT_THREAD_PRIORITY,方法會使用給定的調度策略(在flags中指定,例如,THR_SCHED_DEFAULT)的“適當”優先級。這個值是動態計算的,并且是在給定策略的最低和最高優先級之間。如果顯式地給定一個值,這個值就會被使用。注意實際的優先級值極大地依賴于實現,最好不要直接使用。在線程一章中,可讀到更多有關線程優先級的內容。
還可以傳入將要創建的線程的線程句柄、線程名和棧空間,以在線程創建過程中使用。如果它們被設置為NULL,它們就不會被使用。但是如果要使用activate創建多個線程,就必須傳入線程的名字或句柄,才能有效地對它們進行使用。
7.3.1.5使用服務處理器中的消息隊列機制
如前面所提到的,ACE_Svc_Handler類擁有內建的消息隊列。這個消息隊列被用作在ACE_Svc_Handler和外部世界之間的主要通信接口。其他任務想要發給該服務處理器的消息被放入它的消息隊列中。這些消息會在單獨的線程里(通過調用activate()方法創建)處理。隨后另一個線程就可以把處理過的消息通過網絡發送給另外的遠地目的地(很可能是另外的ACE_Svc_Handler)。
如先前所提到的,在這種多線程情況下,ACE_Svc_Handler會自動地使用鎖來確保消息隊列的完整性。所用的鎖即通過實例化ACE_Svc_Handler模板類創建具體服務處理器時所傳遞的鎖。之所用通過這樣的方式來傳遞鎖,是因為這樣程序員就可以對他的應用進行“調諧”。不同平臺上的不同鎖定機制有著不同程度的開銷。如果需要,程序員可以創建他自己的優化的、遵從ACE的鎖接口定義的鎖,并將其用于服務處理器。這是程序員通過使用ACE可獲得的靈活性的又一范例。重要的是程序員必須意識到,在此服務處理例程中的額外線程將帶來顯著的鎖定開銷。為將此開銷降至最低,程序員必須仔細地設計他的程序,確保使這樣的開銷最小化。特別地,上面描述的例子有可能導致過度的開銷,在大多數情況下可能并不實用。
ACE_Task,進而是ACE_Svc_Handler(因為服務處理器也是一種任務),具有若干可用于對底層隊列進行設置、操作、入隊和出隊操作的方法。這里我們將只討論這些方法中的一部分。因為在服務處理器中(通過使用msg_queue()方法)可以獲取指向消息隊列自身的指針,所以也可以直接調用底層隊列(也就是,ACE_Message_Queue)的所有公共方法。(有關消息隊列提供的所有方法的更多細節,請參見后面的“消息隊列”一章。)
如上面所提到的,服務處理器的底層消息隊列是ACE_Message_Queue的實例,它是由服務處理器自動創建的。在大多數情況下,沒有必要調用ACE_Message_Queue的底層方法,因為在ACE_Svc_Handler類中已對它們的大多數進行了包裝。ACE_Message_Queue是用于使ACE_Message_Block進隊或出隊的隊列。每個ACE_Message_Block都含有指向“引用計數”(reference-counted)的ACE_Data_Block的指針,ACE_Data_Block依次又指向存儲在塊中的實際數據(見“消息隊列”一章)。這使得ACE_Message_Block可以很容易地進行數據共享。
ACE_Message_Block的主要作用是進行高效數據操作,而不帶來許多拷貝開銷。每個消息塊都有一個讀指針和寫指針。無論何時我們從塊中讀取時,讀指針會在數據塊中向前增長。類似地,當我們向塊中寫的時候,寫指針也會向前移動,這很像在流類型系統中的情況。可以通過ACE_Message_Block的構造器向它傳遞分配器,以用于分配內存(有關Allocator的更多信息,參見“內存管理”一章)。例如,可以使用ACE_Cached_Allocation_Strategy,它預先分配內存并從內存池中返回指針,而不是在需要的時候才從堆中分配內存。這樣的功能在需要可預測的性能時十分有用,比如在實時系統中。
7.4?接受器和連接器模式工作原理
接受器和連接器工廠(也就是ACE_Connector和ACE_Acceptor)有著非常類似的運行結構。它們的工作可大致劃分為三個階段:
·?端點或連接初始化階段?
·?服務初始化階段?
·?服務處理階段?
7.4.1?端點或連接初始化階段
在使用接受器的情況下,應用級程序員可以調用ACE_Acceptor工廠的open()方法,或是它的缺省構造器(它實際上會調用open()方法),來開始被動偵聽連接。當接受器工廠的open()方法被調用時,如果反應堆單體還沒有被實例化,open()方法就首先對其進行實例化。隨后它調用底層具體接受器的open()方法。于是具體接受器會完成必要的初始化來偵聽連接。例如,在使用ACE_SOCK_Acceptor的情況中,它打開socket,將其綁定到用戶想要在其上偵聽新連接的端口和地址上。在綁定端口后,它將會發出偵聽調用。open方法隨后將接受器工廠登記到反應堆。因而在接收到任何到來的連接請求時,反應堆會自動回調接受器工廠的handle_input()方法。注意正是因為這一原因,接受器工廠才從ACE_Event_Handler類層次派生;這樣它才可以響應ACCEPT事件,并被反應堆自動回調。
在使用連接器的情況中,應用程序員調用連接器工廠的connect()方法或connect_n()方法來發起到對端的連接。除了其他一些選項,這兩個方法的參數包括我們想要連接到的遠地地址,以及我們是想要同步還是異步地完成連接。我們可以同步或異步地發起NUMBER_CONN個連接:
//Synchronous
OurConnector.connect_n(NUMBER_CONN,ArrayofMySvcHandlers,Remote_Addr,0,
ACE_Synch_Options::synch);
?
//Asynchronous
OurConnector.connect_n(NUMBER_CONN,ArrayofMySvcHandlers,Remote_Addr,0,
ACE_Synch_Options::asynch);
??如果連接請求是異步的,ACE_Connector會在反應堆上登記自己,等待連接被建立(ACE_Connector也派生自ACE_Event_Handler類層次)。一旦連接被建立,反應堆將隨即自動回調連接器。但如果連接請求是同步的,connect()調用將會阻塞,直到連接被建立、或是超時到期為止。超時值可通過改變特定的ACE_Synch_Options來指定。詳情請參見參考手冊。
7.4.2?接受器的服務初始化階段
在有連接請求在指定的地址和端口上到來時,反應堆自動回調ACE_Acceptor工廠的handle_input()方法。
該方法是一個“模板方法”(Template?Method)。模板方法用于定義一個算法的若干步驟的順序,并允許改變特定步驟的執行。這種變動是通過允許子類定義這些方法的實現來完成的。(有關模板方法的更多信息見“設計模式”參考指南)。
在我們的這個案例中,模板方法將算法定義如下:
·?make_svc_handler():創建服務處理器。?
·?accept_svc_handler():將連接接受進前一步驟創建的服務處理器。?
·?activate_svc_handler():啟動這個新服務處理器。?
這些方法都可以被重新編寫,從而靈活地決定這些操作怎樣來實際執行。
這樣,handle_input()將首先調用make_svc_handler()方法,創建適當類型的服務處理器(如我們在上面的例子中所看到的那樣,服務處理器的類型由應用程序員在ACE_Acceptor模板被實例化時傳入)。在缺省情況下,make_svc_handler()方法只是實例化恰當的服務處理器。但是,make_svc_handler()是一個“橋接”(bridge)方法,可被重載以提供更多復雜功能。(橋接是一種設計模式,它使類層次的接口與實現去耦合。參閱“設計模式”參考文獻)。例如,服務處理器可創建為進程級或線程級的單體,或者從庫中動態鏈接,從磁盤中加載,甚或通過更復雜的方式創建,如從數據庫中查找并獲取服務處理器,并將它裝入內存。
在服務處理器被創建后,handle_input()方法調用accept_svc_handler()。該方法將連接“接受進”服務處理器;缺省方式是調用底層具體接受器的accept()方法。在ACE_SOCK_Acceptor被用作具體接受器的情況下,它調用BSD?accept()例程來建立連接(“接受”連接)。在連接建立后,連接句柄在服務處理器中被自動設置(接受“進”服務處理器);這個服務處理器是先前通過調用make_svc_handler()創建的。該方法也可被重載,以提供更復雜的功能。例如,不是實際創建新連接,而是“回收利用”舊連接。在我們演示各種不同的接受和連接策略時,將更為詳盡地討論這一點。
7.4.3?連接器的服務初始化階段
應用發出的connect()方法與接受器工廠中的handle_input()相類似,也就是,它是一個“模板方法”。
在我們的這個案例中,模板方法connect()定義下面一些可被重定義的步驟:
·?make_svc_handler():創建服務處理器。?
·?connect_svc_handler():將連接接受進前一步驟創建的服務處理器。?
·?activate_svc_handler():啟動這個新服務處理器。?
每一方法都可以被重新編寫,從而靈活地決定這些操作怎樣來實際執行。
這樣,在應用發出connect()調用后,連接器工廠通過調用make_svc_handler()來實例化恰當的服務處理器,一如在接受器的案例中所做的那樣。其缺省行為只是實例化適當的類,并且也可以通過與接受器完全相同的方式重載。進行這樣的重載的原因可以與上面提到的原因非常類似。
在服務處理器被創建后,connect()調用確定連接是要成為異步的還是同步的。如果是異步的,在繼續下一步驟之前,它將自己登記到反應堆,隨后調用connect_svc_handler()方法。該方法的缺省行為是調用底層具體連接器的connect()方法。在使用ACE_SOCK_Connector的情況下,這意味著將適當的選項設置為阻塞或非阻塞式I/O,然后發出BSD?connect()調用。如果連接被指定為同步的,connect()調用將會阻塞、直到連接完全建立。在這種情況下,在連接建立后,它將在服務處理器中設置句柄,以與它現在連接到的對端通信(該句柄即是通過在服務處理器中調用peer()方法獲得的在流中存儲的句柄,見上面的例子)。在服務處理器中設置句柄后,連接器模式將進行到最后階段:服務處理。
如果連接被指定為異步的,在向底層的具體連接器發出非阻塞式connect()調用后,對connect_svc_handler()的調用將立即返回。在使用ACE_SOCK_Connector的情況中,這意味著發出非阻塞式BSD?connect()調用。在連接稍后被實際建立時,反應堆將回調ACE_Connector工廠的handle_output()方法,該方法在通過make_svc_handler()方法創建的服務處理器中設置新句柄。然后工廠將進行到下一階段:服務處理。
與accept_svc_handler()情況一樣,connect_svc_handler()是一個“橋接”方法,可進行重載以提供變化的功能。
7.4.4?服務處理(是不是有問題???)
一旦服務處理器被創建、連接被建立,以及句柄在服務處理器中被設置,ACE_Acceptor的handle_input()方法(或者在使用ACE_Connector的情況下,是handle_output()或connect_svc_handler())將調用activate_svc_handler()方法。該方法將隨即啟用服務處理器。其缺省行為是調用作為服務處理器的入口的open()方法。如我們在上面的例子中所看到的,在服務處理器開始執行時,open()方法是第一個被調用的方法。是在open()方法中,我們調用activate()方法來創建多個線程控制;并在反應堆上登記服務處理器,這樣當新的數據在連接上到達時,它會被自動回調。該方法也是一個“橋接”方法,可被重載以提供更為復雜的功能。特別地,這個重載的方法可以提供更為復雜的并發策略,比如,在另一不同的進程中運行服務處理器。
7.5?調諧接受器和連接器策略
如上面所提到的,因為使用了可以重載的橋接方法,很容易對接受器和連接器進行調諧。橋接方法允許調諧:
·?服務處理器的創建策略:通過重載接受器或連接器的make_svc_handler()方法來實現。例如,這可以意味著復用已有的服務處理器,或使用某種復雜的方法來獲取服務處理器,如上面所討論的那樣。?
·?連接策略:連接創建策略可通過重載connect_svc_handler()或accept_svc_handler()方法來改變。?
·?服務處理器的并發策略:服務處理器的并發策略可通過重載activate_svc_handler()方法來改變。例如,服務處理器可以在另外的進程中創建。?
如上所示,調諧是通過重載ACE_Acceptor或ACE_Connector類的橋接方法來完成的。ACE的設計使得程序員很容易完成這樣的重載和調諧。
7.5.1?ACE_Strategy_Connector和ACE_Strategy_Acceptor類
為了方便上面所提到的對接受器和連接器模式的調諧方法,ACE提供了兩種特殊的“可調諧”接受器和連接器工廠,那就是ACE_Strategy_Acceptor和ACE_Strategy_Connector。它們和ACE_Acceptor與ACE_Connector非常類似,同時還使用了“策略”模式。
策略模式被用于使算法行為與類的接口去耦合。其基本概念是允許一個類(稱為Context?Class,上下文類)的底層算法獨立于使用該類的客戶進行變動。這是通過具體策略類的幫助來完成的。具體策略類封裝執行操作的算法或方法。這些具體策略類隨后被上下文類用于執行各種操作(上下文類將“工作”委托給具體策略類)。因為上下文類不直接執行任何操作,當需要改變功能時,無需對它進行修改。對上下文類所做的唯一修改是使用另一個具體策略類來執行改變了的操作。(要閱讀有關策略模式的更多信息,參見“設計模式”的附錄)。
在ACE中,ACE_Strategy_Connector和ACE_Strategy_Acceptor使用若干具體策略類來改變算法,以創建服務處理器,建立連接,以及為服務處理器設置并發方法。如你可能已經猜到的一樣,ACE_Strategy_Connector和ACE_Strategy_Acceptor利用了上面提到的橋接方法所提供的可調諧性。
7.5.1.1?使用策略接受器和連接器
在ACE中已有若干具體的策略類可用于“調諧”策略接受器和連接器。當類被實例化時,它們作為參數被傳入策略接受器或連接器。表7-2顯示了可用于調諧策略接受器和連接器類的一些類。
| 需要修改 | 具體策略類 | 描述 |
| 創建策略 (重定義make_svc_handler()) | ACE_NOOP_Creation_Strategy | 這個具體策略并不實例化服務處理器,而只是一個空操作。 |
| ACE_Singleton_Strategy | 保證服務處理器被創建為單體。也就是,所有連接將有效地使用同一個服務處理例程。 | |
| ACE_DLL_Strategy | 通過從動態鏈接庫中動態鏈接服務處理器來對它進行創建。 | |
| 連接策略 (重定義connect_svc_handler()) | ACE_Cached_Connect_Strategy | 檢查是否有已經連接到特定的遠地地址的服務處理器沒有在被使用。如果有這樣一個服務處理器,就對它進行復用。 |
| 并發策略 (重定義activate_svc_handler()) | ACE_NOOP_Concurrency_Strategy | 一個“無為”(do-nothing)的并發策略。它甚至不調用服務處理器的open()方法。 |
| ACE_Process_Strategy | 在另外的進程中創建服務處理器,并調用它的open()方法。 | |
| ACE_Reactive_Strategy | 先在反應堆上登記服務處理器,然后調用它的open()方法。 | |
| ACE_Thread_Strategy | 先調用服務處理器的open()方法,然后調用它的activate()方法,以讓另外的線程來啟動服務處理器的svc()方法。 |
表7-2?用于調諧策略接受器和連接器類的類
7.5.1.2?使用ACE_Cached_Connect_Strategy進行連接緩存
在許多應用中,客戶會連接到服務器,然后重新連接到同一服務器若干次;每次都要建立連接,執行某些工作,然后掛斷連接(比如像在Web客戶中所做的那樣)。不用說,這樣做是非常低效而昂貴的,因為連接建立和掛斷是非常昂貴的操作。在這樣的情況下,連接者可以采用一種更好的策略:“記住”老連接并保持它,直到確定客戶不會再重新建立連接為止。ACE_Cached_Connect_Strategy就提供了這樣一種緩存策略。這個策略對象被ACE_Strategy_Connector用于提供基于緩存的連接建立。如果一個連接已經存在,ACE_Strategy_Connector將會復用它,而不是創建新的連接。
當客戶試圖重新建立連接到先前已經連接的服務器時,ACE_Cached_Connect_Strategy確保對老的連接和服務處理器進行復用,而不是創建新的連接和服務處理器。因而,實際上,ACE_Cached_Connect_Strategy不僅管理連接建立策略,它還管理服務處理器創建策略。因為在此例中,用戶不想創建新的服務處理器,我們將ACE_Null_Creation_Strategy傳遞給ACE_Strategy_Connector。如果連接先前沒有建立過,ACE_Cached_Connect_Strategy將自動使用內部的創建策略來實例化適當的服務處理器,它是在這個模板類被實例化時傳入的。這個策略可被設置為用戶想要使用的任何一種策略。除此而外,也可以將ACE_Cached_Connect_Strategy自己在其構造器中使用的創建、并發和recycling策略傳給它。
第8章?服務配置器(Service?Configurator):用于服務動態配置的模式
如果服務可以被動態地啟動、移除、掛起和恢復,那將會方便得多。這樣,服務開發者就不必再擔心配置的服務。他所需關心的是服務如何完成工作。管理員就可以在應用中增加或替換新服務,而不用重新編譯或關閉服務進程。
服務配置器模式可以完成所有這些任務。它使服務的實現與配置去耦合。無需關閉服務器,就可以在應用中增加新服務和移除舊服務。在大多數情況下,提供服務的服務器都被實現為看守(daemon)進程。
8.1?構架組件
ACE中的服務配置器由以下組件組成:
·?名為ACE_Service_Object的抽象類。應用開發者必須從它派生出子類,以創建他自己的應用特有的具體服務對象(Service?Object)。?
·?應用特有的具體服務對象。?
·?服務倉庫ACE_Service_Repository。它記錄服務器所運行的和所知道的服務。?
·?ACE_Service_Config。它是整個服務配置器框架的應用開發接口。?
·?服務配置文件。該文件含有所有服務對象的配置信息。其缺省的名字是svc.conf。當你的應用對ACE_Service_Config發出open()調用時,服務配置器框架會讀取并處理你寫在此文件中的所有配置信息,隨后相應地配置應用。?
ACE_Service_Object包括了一些由框架調用的方法,用于服務要啟動(init())、停止(fini())、掛起(suspend())或是恢復(resume())時。ACE_Service_Object派生自ACE_Shared_Object和ACE_Event_Handler。ACE_Shared_Object在應用想要使用操作系統的動態鏈接機制來進行加載時被用作抽象基類。ACE_Event_Handler已在對反應堆的討論中進行了介紹。當開發者想要他的類響應來自反應堆的事件時,他就從ACE_Event_Handler派生他的子類。
為什么服務對象要從ACE_Event_Handler繼承?用戶發起重配置的一種方法是生成一個信號;當這樣的信號事件發生時,反應堆被用于處理信號,并向ACE_Service_Config發出重配置請求。除此而外,軟件的重配置也可能在某事件產生后發生。因而所有的服務對象都被構造為能對事件進行處理。
服務配置文件有它自己的簡單腳本,用于描述你想要服務怎樣啟動和運行。你可以定義你是想要增加新服務,還是掛起、恢復或移除應用中現有的服務。另外還可以給服務發送參數。服務配置器還允許進行基于ACE的流(stream)的重配置。我們將在討論了ACE流構架之后再來更多地討論這一點。
8.2?定義配置文件
服務配置文件指定在應用中哪些服務要被加載和啟動。此外,你可以指定哪些服務要被停止、掛起或恢復。還可以發送參數給你的服務對象的init()方法。
8.2.1?啟動服務
服務可以被靜態或動態地啟動。如果服務要動態啟動,服務配置器實際上會從共享對象庫(也就是,動態鏈接庫)中加載服務對象。為此,服務配置器需要知道哪個庫含有此對象,并且還需要知道對象在該庫中的名字。因而,在你的代碼文件中你必須通過你需要記住的名字來實例化服務對象。于是動態服務會這樣被配置:
dynamic?service_name?type_of_service?*?location_of_shared_lib:name_of_object?“parameters”
而靜態服務這樣被初始化:
static?service_name?“parameters_send_to_service_object”
8.2.2?掛起或恢復服務
如剛才所提到的,你在啟動服務時分配給它一個名字。這個名字隨后被用于掛起或恢復該服務。于是要掛起服務,你所要做的就是在svc.conf文件中指定:
suspend?service_name
這使得服務對象中的suspend()方法被調用。隨后你的服務對象就應該掛起它自己(基于特定服務不同的“掛起”含義)。
如果你想要恢復這個服務,你所要做的就是在svc.conf文件中指定:
resume?service_name
這使得服務對象中的resume()方法被調用。隨后你的服務對象就應該恢復它自己(基于特定服務不同的“恢復”含義。)
8.2.3?停止服務
停止并移除服務(如果服務是動態加載的)同樣是很簡單的操作,可以通過在你的配置文件中指定以下指令來完成:
remove?service_name
這使得服務配置器調用你的應用的fini()方法。該方法應該使此服務停止。服務配置器自己會負責將動態對象從服務器的地址空間里解除鏈接。
8.3?編寫服務
為服務配置器編寫你自己的服務相對比較簡單。你可以讓這個服務做任何你想做的事情。唯一的約束是它應該是ACE_Service_Object的子類。所以它必須實現init()和fini()方法。在ACE_Service_Config被打開(open())時,它讀取配置文件(也就是svc.conf)并根據這個文件來對服務進行初始化。一旦服務被加載,它會調用該服務對象的init()方法。類似地,如果配置文件要求移除服務,fini()方法就會被調用。這些方法負責分配和銷毀服務所需的任何資源,比如內存、連接、線程等等。在svc.conf文件中指定的參數通過服務對象的init()方法來傳入。
下面的例子演示一個派生自ACE_Task_Base的服務。ACE_Task_Base類含有activate()方法,用于在對象里創建線程。(在“任務和主動對象”一章中討論過的ACE_Task派生自ACE_Task_Base,并包括了用于通信目的的消息隊列。因為我們不需要我們的服務與其它任務通信,我們僅僅使用ACE_Task_Base來幫助我們完成工作。)更多詳細信息,請閱讀“任務和主動對象”一章。該服務是一個“無為”(do-nothing)的服務,一旦啟動,它只是周期性地廣播當天的時間。
相應的實現如下所述:在時間服務接收到init()調用時,它在任務中啟用(activate())一個線程。這將會創建一個新線程,其入口為svc()方法。在svc()方法中,該線程將會進行循環,直到它看到canceled_標志被設置為止。此標志在服務配置構架調用fini()時設置。但是,在fini()方法返回底層的服務配置框架之前,它必須確定在底層的線程已經終止。因為服務配置器將要實際地卸載含有TimeService的共享庫,從而將TimeService對象從應用進程中刪除。如果在此之前線程并未終止,它將會對已經被服務配置器“蒸發”的代碼發出調用!我們當然不需要這個。為了確保線程在服務配置器“蒸發”TimeService對象之前終止,程序使用了條件變量。(要更多地了解怎樣使用條件變量,請閱讀有關線程的章節)。
下面是一個簡單的、只是用于啟用時間服務的配置文件。可以去掉注釋#號來掛起、恢復和移除服務。
例8-1c
#?To?configure?different?services,?simply?uncomment?the?appropriate
#lines?in?this?file!
#resume?TimeService
#suspend?TimeService
#remove?TimeService
#set?to?dynamically?configure?the?TimeService?object?and?do?so?without
#sending?any?parameters?to?its?init?method
dynamic?TimeService?Service_Object?*?./Server:time_service?""
最后,下面是啟動服務配置器的代碼段。這些代碼還設置了一個信號處理器對象,用于發起重配置。該信號處理器已被設置成響應SIGWINCH信號(在窗口發生變化時產生的信號)。在啟動服務配置器之后,應用進入一個反應式循環,等待SIGWINCH信號事件發生。一旦事件發生,就會回調事件處理器,由它調用ACE_Service_Config的reconfigure()方法。如先前所講述的,在此調用發生時,服務配置器重新讀取配置文件,并處理用戶放在其中的任何新指令。例如,在動態啟動TimeService后,在這個例子中你可以改變svc.conf文件,只留下一個掛起命令在里面。當配置器讀取它時,它將調用TimeService的掛起方法,從而使它掛起它的底層線程。類似地,如果稍后你又改變了svc.conf,要求恢復服務,配置器就會調用TimeService::resume()方法,從而恢復先前被掛起的線程。
8.4?使用服務管理器
ACE_Service_Manager是可用于對服務配置器進行遠程管理的服務。它目前可以接受兩種類型的請求。其一,你可以向它發送“help”消息,列出當前被加載進應用的所有服務。其二,你可以向服務管理器發送“reconfigure”消息,從而使得服務配置器重新配置它自己。
第9章?消息隊列(Message?Queue)
現代的實時應用通常被構建成一組相互通信、但又相互獨立的任務。這些任務可以通過若干機制來與對方進行通信,其中常用的一種就是消息隊列。在這一情況下,基本的通信模式是:發送者(或生產者)任務將消息放入消息隊列,而接收者(或消費者)任務從此隊列中取出消息。這當然只是消息隊列的使用方式之一。在接下來的討論中,我們將看到若干不同的使用消息隊列的例子。
ACE中的消息隊列是仿照UNIX系統V的消息隊列設計的,如果你已經熟悉系統V的話,就很容易掌握ACE的消息隊列的使用。在ACE中有多種不同類型的消息隊列可用,每一種都使用不同的調度算法來進行隊列的入隊和出隊操作。
9.1?消息塊
在ACE中,消息作為消息塊(Message?Block)被放入消息隊列中。消息塊包裝正被存儲的實際消息數據,并提供若干數據插入和處理操作。每個消息塊“包含”一個頭和一個數據塊。注意在這里“包含”是在寬松的意義上使用的。消息塊可以不對與數據塊(Data?Block)或是消息頭(Message?Header)相關聯的內存進行管理(盡管你可以讓消息塊進行這樣的管理)。它僅僅持有指向兩者的指針。所以包含只是邏輯上的。數據塊持有指向實際的數據緩沖區的指針。如圖9-1所示,這樣的設計帶來了多個消息塊之間的數據的靈活共享。注意在圖中兩個消息塊共享一個數據塊。這樣,無需帶來數據拷貝開銷,就可以將同一數據放入不同的隊列中。
消息塊類名為ACE_Message_Block,而數據塊類名為ACE_Data_Block。ACE_Message_Block的構造器是實際創建消息塊和數據塊的方便辦法。
9.1.1?構造消息塊
ACE_Message_Block類包含有若干不同的構造器。你可以使用這些構造器來幫助你管理隱藏在消息和數據塊后面的消息數據。ACE_Message_Block類可用于完全地隱藏ACE_Data_Block,并為你管理消息數據;或者,如果你需要,你可以自己創建和管理數據塊及消息數據。下一部分將考查怎樣使用ACE_Message_Block來管理消息內存和數據塊。然后我們將考查怎樣獨立地進行這樣的管理,而不用依賴ACE_Message_Block的管理特性。
9.1.1.1?ACE_Message_Block分配和管理數據內存
要創建消息塊,最容易的方法是將底層數據塊的大小傳給ACE_Message_Block的構造器,從而創建ACE_Data_Block,并為消息數據分配空的內存區。在創建消息塊后,你可以使用rd_ptr()和wr_ptr()方法來在消息塊中插入和移除數據。讓ACE_Message_Block來為數據和數據塊創建內存區的主要優點是,它會為你正確地管理所有內存,從而使你免于在將來為許多內存泄漏而頭疼。
ACE_Message_Block的構造器還允許程序員指定ACE_Message_Block在內部分配內存時所應使用的分配器。如果你傳入一個分配器,消息塊將用它來為數據塊和消息數據區的創建分配內存。ACE_Message_Block的構造器為:
ACE_Message_Block?(size_t?size,
ACE_Message_Type?type?=?MB_DATA,
ACE_Message_Block?*cont?=?0,
const?char?*data?=?0,
ACE_Allocator?*allocator_strategy?=?0,
ACE_Lock?*locking_strategy?=?0,
u_long?priority?=?0,
const?ACE_Time_Value?&?execution_time?=?ACE_Time_Value::zero,
const?ACE_Time_Value?&?deadline_time?=?ACE_Time_Value::max_time);
上面的構造器的參數為:
1.?要與消息塊相關聯的數據緩沖區的大小。注意消息塊的大小是size,但長度將為0,直到wr_ptr被設置為止。這將在后面進一步解釋。?
2.?消息的類型。(在ACE_Message_Type枚舉中有若干類型可用,其中包括缺省的數據消息)。?
3.?指向“片段鏈”(fragment?chain)中的下一個消息塊的指針。消息塊可以實際地鏈接在一起來形成鏈。隨后鏈可被放入消息隊列中,就好像它是單個數據塊一樣。該參數缺省為0,意味著此塊不使用鏈。?
4.?指向要存儲在此消息塊中的數據緩沖區的指針。如果該參數的值為零,就會創建緩沖區(大小由第一個參數指定),并由該消息塊進行管理。當消息塊被刪除時,相應的數據緩沖區也被刪除。但是,如果在此參數中指定了數據緩沖區,也就是,參數不為空,當消息塊被銷毀時它就不會刪除數據緩沖區。這是一個重要特性,必須牢牢記住。?
5.?用于分配數據緩存(如果需要)的allocator_strategy,在第四個參數為空時使用(如上面所解釋的)。任何ACE_Allocator的子類都可被用作這一參數。(關于ACE_Allocator的更多信息,參見“內存管理”一章)。?
6.?如果locking_strategy不為零,它就將用于保護訪問共享狀態(例如,引用計數)的代碼區,以避免競爭狀態。?
7.?這個參數以及后面兩個參數用于ACE中的實時消息隊列的調度,目前應保留它們的缺省值。?
9.1.1.2?用戶分配和管理消息內存
如果你正在使用ACE_Message_Block,你并不一定要讓它來為你分配內存。消息塊的構造器允許你:
·?創建并傳入你自己的指向消息數據的數據塊。?
·?傳入指向消息數據的指針,消息塊將創建并設置底層的數據塊。消息塊將為數據塊、而不是消息數據管理內存。?
下面的例子演示怎樣將指向消息數據的指針傳給消息塊,以及ACE_Message_Block怎樣創建和管理底層的ACE_Data_Block。
//The?data
char?data[size];
data?=?”This?is?my?data”;
?
//Create?a?message?block?to?hold?the?data
ACE_Message_Block?*mb?=?new?ACE_Message_Block?(data,?//?data?that?is?stored
//?in?the?newly?created?data
//
blocksize);?//size?of?the?block?that
//is?to?be?stored.
該構造器創建底層數據塊,并將它設置為指向傳遞給它的數據的開頭。被創建的消息塊并不拷貝該數據,也不假定自己擁有它的所有權。這就意味著在消息塊mb被銷毀時,相關聯的數據緩沖區data將不會被銷毀。這是有意義的:消息塊沒有拷貝數據,因此內存也不是它分配的,這樣它也不應該負責銷毀它。
9.1.2?在消息塊中插入和操作數據
除了構造器,ACE_Message_Block還提供若干方法來直接在消息塊中插入數據。另外還有一些方法可用來操作已經在消息塊中的數據。
每個ACE_Message_Block都有兩個底層指針:rd_ptr和wr_ptr,用于在消息塊中讀寫數據。它們可以通過調用rd_ptr()和wr_ptr()方法來直接訪問。rd_ptr指向下一次讀取數據的位置,而wr_ptr指向下一次寫入數據的位置。程序員必須小心地管理這些指針,以保證它們總是指向正確的位置。在使用這些指針讀寫數據時,程序員必須自己來增加它們的值,它們不會魔法般地自動更新。大多數內部的消息塊方法也使用這兩個指針,從而使它們能夠在你調用消息塊方法時改變指針狀態。程序員必須保證自己了解這些指針的變化。
9.1.2.1?拷貝與復制(Copying?and?Duplicating)
可以使用ACE_Message_Block的copy()方法來將數據拷貝進消息塊。
int?copy(const?char?*buf,?size_t?n);
copy方法需要兩個參數,其一是指向要拷貝進消息塊的緩沖區的指針,其二是要拷貝的數據的大小。該方法從wr_pt指向的位置開始往前寫,直到它到達參數n所指示的數據緩沖區的末尾。copy()還會保證wr_ptr的更新,使其指向緩沖區的新末尾處。注意該方法將實際地執行物理拷貝,因而應該小心使用。
base()和length()方法可以聯合使用,以將消息塊中的整個數據緩沖區拷貝出來。base()返回指向數據塊的第一個數據條目的指針,而length()返回隊中數據的總大小。將base和length相加,可以得到指向數據塊末尾的指針。合起來使用這些方法,你就可以寫一個例程來從消息塊中取得數據,并做一次外部拷貝。
duplicate()和clone()方法用于制作消息塊的“副本”。如它的名字所暗示的,clone()方法實際地創建整個消息塊的新副本,包括它的數據塊和附加部分;也就是說,這是一次“深度復制”。而另一方面,duplicate()方法使用的是ACE_Message_Block的引用計數機制。它返回指向要被復制的消息塊的指針,并在內部增加內部引用計數。
9.1.2.2?釋放消息塊
一旦使用完消息塊,程序員可以調用它的release()方法來釋放它。如果消息數據內存是由該消息塊分配的,調用release()方法就也會釋放此內存。如果消息塊是引用計數的,release()就會減少計數,直到到達0為止;之后消息塊和與它相關聯的數據塊才從內存中被移除。
9.2?ACE的消息隊列
如先前所提到的,ACE有若干不同類型的消息隊列,它們大體上可劃分為兩種范疇:靜態的和動態的。靜態隊列是一種通用的消息隊列(ACE_Message_Queue),而動態消息隊列(ACE_Dynamic_Message_Queue)是實時消息隊列。這兩種消息隊列的主要區別是:靜態隊列中的消息具有靜態的優先級,也就是,一旦優先級被設定就不會再改變;而另一方面,在動態消息隊列中,基于諸如執行時間和最終期限等參數,消息的優先級可以動態地改變。
例子由一個Qtest類組成,它通過ACE_NULL_SYNCH鎖定來實例化缺省大小的消息隊列。鎖(互斥體和條件變量)被消息隊列用來:
·?保證由消息塊維護的引用計數的安全,防止在有多個線程訪問時的競爭狀態。?
·?“喚醒”所有因為消息隊列空或滿而休眠的線程。?
在此例中,因為只有一個線程,消息隊列的模板同步參數被設置為空(ACE_NULL_SYNCH,意味著使用ACE_Null_Mutex和ACE_Null_Condition)。隨后Qtest的enq_msgs()方法被調用,它進入循環,創建消息、并將其放入消息隊列中。消息數據的大小作為參數傳給ACE_Message_Block的構造器。使用該構造器使得內存被自動地管理(也就是,內存將在消息塊被刪除時,即release()時被釋放)。wr_ptr隨后被獲取(使用wr_ptr()訪問方法),且數據被拷貝進消息塊。在此之后,wr_ptr向前增長。然后使用消息隊列的enqueue_prio()方法來實際地將消息塊放入底層消息隊列中。
在no_msgs_個消息塊被創建、初始化和插入消息隊列后,enq_msgs()調用deq_msgs()方法。該方法使用ACE_Message_Queue的dequeue_head()方法來使消息隊列中的每個消息出隊。在消息出隊后,就顯示它的數據,然后再釋放消息。
9.3?水位標
水位標用于在消息隊列中指示何時在其中的數據已過多(消息隊列到達了高水位標),或何時在其中的數據的數量不足(消息隊列到達了低水位標)。兩種水位標都用于流量控制。例如,low_water_mark可用于避免像TCP中的“傻窗口綜合癥”(silly?window?syndrome)那樣的情況,而high_water_mark可用于“阻止“或減緩數據的發送或生產。
ACE中的消息隊列通過維護已經入隊的總數據量的字節計數來獲得這些功能。因而,無論何時有新消息塊被放入消息隊列中,消息隊列都將先確定它的長度,然后檢查是否能將此消息塊放入隊列中(也就是,確認如果將此消息塊入隊,消息隊列沒有超過它的高水位標)。如果消息隊列不能將數據入隊,而它又持有一個鎖(也就是,使用了ACE_SYNC,而不是ACE_NULL_SYNCH作為消息隊列的模板參數),它就會阻塞調用者,直到有足夠的空間可用,或是入隊方法的超時(timeout)到期。如果超時已到期,或是隊列持有一個空鎖,入隊方法就會返回-1,指示無法將消息入隊。
類似地,當ACE_Message_Queue的dequeue_head方法被調用時,它檢查并確認在出隊之后,剩下的數據的數量高于低水位標。如果不是這樣,而它又持有一個鎖,它就會阻塞;否則就返回-1,指示失敗(和入隊方法的工作方式一樣)。
分別有兩個方法可用于設置和獲取高低水位標:
//get?the?high?water?mark
size_t?high_water_mark(void)
?
//set?the?high?water?mark
void?high_water_mark(size_t?hwm);
?
//get?the?low?water_mark
size_t?low_water_mark(void)
?
//set?the?low?water_mark
void?low_water_mark(size_t?lwm)
9.4?使用消息隊列迭代器(Message?Queue?Iterator)
和其它容器類的常見情況一樣,可將前進(forward)和后退(reverse)迭代器用于ACE中的消息隊列。這兩個迭代器名為ACE_Message_Queue_Iterator和ACE_Message_Queue_Reverse_Iterator。它們都需要一個模板參數,用于在遍歷消息隊列時進行同步。如果有多個線程使用消息隊列,該參數就應設為ACE_SYNCH;否則,就可設為ACE_NULL_SYNCH。在迭代器對象被創建時,必須將我們想要進行迭代的消息隊列的引用傳給它的構造器。
9.5?動態或實時消息隊列
如上面所提到的,動態消息隊列是其中的消息的優先級隨時間變化的隊列。實時應用需要這樣的行為特性,因而這樣的隊列在實時應用中天生更為有用。
ACE目前提供兩種動態消息隊列:基于最終期限(deadline)的和基于松弛度(laxity)的(參見[IX])動態消息隊列。基于最終期限的消息隊列通過每個消息的最終期限來設置它們的優先級。在使用最早deadline優先算法來調用dequeue_head()方法時,隊列中有著最早的最終期限的消息塊將最先出隊。而基于松弛度的消息隊列,同時使用執行時間和最終期限來計算松弛度,并將其用于劃分各個消息塊的優先級。松弛度是十分有用的,因為在根據最終期限來調度時,被調度的任務有可能有最早的最終期限,但同時又有相當長的執行時間,以致于即使它被立即調度,也不能夠完成。這會消極地影響其它任務,因為它可能阻塞那些可以調度的任務。松弛度把這樣的長執行時間考慮在內,并保證任務如果不能完成,就不會被調度。松弛度隊列中的調度基于最小松弛度優先算法。
基于松弛度的消息隊列和基于最終期限的消息隊列都實現為ACE_Dynamic_Message_Queue。ACE使用策略(STRATEGY)模式來為動態隊列提供不同的調度特性。每種消息隊列使用不同的“策略”對象來動態地設置消息隊列中消息的優先級。每個這樣的“策略”對象都封裝了一種不同的算法來基于執行時間、最終期限,等等,計算優先級;并且無論何時消息入隊或是出隊,都會調用這些策略對象來完成前述計算工作。(有關策略模式的更多信息,請參見“設計模式”)。消息策略模式派生自ACE_Dynamic_Message_Strategy,目前有兩種策略可用:ACE_Laxity_Message_Strategy和ACE_Deadline_Message_Strategy。因此,要創建基于松弛度的動態消息隊列,首先必須創建ACE_Laxity_Message_Strategy對象。隨后,應該對ACE_Dynamic_Message_Queue對象進行實例化,并將新創建的策略對象作為參數之一傳給它的構造器。
創建消息隊列
為簡化這些不同類型的消息隊列的創建,ACE提供了名為ACE_Message_Queue_Factory的具體消息隊列工廠,它使用工廠方法(FACTORY?METHOD,更多信息參見“設計模式”)模式的一種變種來創建適當類型的消息隊列。消息隊列工廠有三個靜態的工廠方法,可用來創建三種不同類型的消息隊列:
static?ACE_Message_Queue<ACE_SYNCH_USE>?*
create_static_message_queue();
?
static?ACE_Dynamic_Message_Queue<ACE_SYNCH_USE>?*
create_deadline_message_queue();
?
static?ACE_Dynamic_Message_Queue<ACE_SYNCH_USE>?*
create_laxity_message_queue();
每個方法都返回指向剛創建的消息隊列的指針。注意這些方法都是靜態的,而create_static_message_queue()方法返回的是ACE_Message_Queue,其它兩個方法則返回ACE_Dynamic_Message_Queue。
能看到這里對ACE估計感興趣,下載學習ACE調試過的例程代碼。
----------------------------------------------------
兄弟的公司:立即購--手機購物,誠信網購
歡迎轉載,請注明作者和出處。
轉載于:https://www.cnblogs.com/zhenjing/archive/2010/11/05/1867538.html
總結
- 上一篇: 网络技术(十一)加强MSTP 、STP、
- 下一篇: 精益思想与软件工程