接受器-連接器設(shè)計(jì)模式(Acceptor-Connector)使分布式系統(tǒng)中的連接建立及服務(wù)初始化與一旦服務(wù)初始化后所執(zhí)行的處理去耦合。這樣的去耦合通過三種組件來完成:acceptor、connector和service handler(服務(wù)處理器)。連接器主動(dòng)地建立到遠(yuǎn)地接受器組件的連接,并初始化服務(wù)處理器來處理在連接上交換的數(shù)據(jù)。同樣地,接受器被動(dòng)地等待來自遠(yuǎn)地連接器的連接請(qǐng)求,在這樣的請(qǐng)求到達(dá)時(shí)建立連接,并初始化服務(wù)處理器來處理在連接上交換的數(shù)據(jù)。隨后已初始化的服務(wù)處理器執(zhí)行應(yīng)用特有的處理,并通過連接器和接受器組件建立的連接來進(jìn)行通信。
圖9-1 面向連接的應(yīng)用級(jí)網(wǎng)關(guān)的物理體系
為演示接受器-連接器模式,考慮圖9-1中所示的多服務(wù)、應(yīng)用級(jí)Gateway(網(wǎng)關(guān))。通常,Gateway使分布式系統(tǒng)中的協(xié)作組件去耦合,并允許它們?cè)跓o需互相直接依賴的情況下進(jìn)行交互。圖9-1中的這個(gè)Gateway在不同的服務(wù)端點(diǎn)間路由數(shù)據(jù),這些端點(diǎn)運(yùn)行在用于監(jiān)視和控制人造衛(wèi)星群的遠(yuǎn)地Peer(對(duì)端)上。每個(gè)Peer中的服務(wù)經(jīng)由Gateway發(fā)送和接收若干類型的數(shù)據(jù),比如狀態(tài)信息、大塊數(shù)據(jù)和命令。一般而言,Peer可以分布在局域網(wǎng)和廣域網(wǎng)中。
該Gateway是一個(gè)路由器,負(fù)責(zé)協(xié)調(diào)它的Peer之間的通信。從Gateway的角度來看,它為之路由數(shù)據(jù)的Peer服務(wù)僅由其應(yīng)用級(jí)通信協(xié)議來進(jìn)行區(qū)分,這些協(xié)議可能會(huì)使用不同的幀格式和有效負(fù)載類型。
Gateway在它的Peer之間使用面向連接的TCP/IP協(xié)議[11]來傳輸數(shù)據(jù)。在我們的示例網(wǎng)絡(luò)配置中,每個(gè)服務(wù)都與一個(gè)連接端點(diǎn)綁定在一起;該端點(diǎn)由IP主機(jī)地址和TCP端口號(hào)指定。端口號(hào)唯一地標(biāo)識(shí)服務(wù)的類型。為每種類型的服務(wù)/端口維護(hù)單獨(dú)的連接增強(qiáng)了路由策略的靈活性,并提供了更為健壯的錯(cuò)誤處理,如果網(wǎng)絡(luò)連接意外關(guān)閉的話。
在我們的分布式應(yīng)用中,Gateway和Peer必須能改變它們的連接角色,以支持不同的使用情況。特別地,或是可以主動(dòng)地發(fā)起連接,或是可以被動(dòng)地等待連接請(qǐng)求。例如,在一種配置中,Gateway可以主動(dòng)地發(fā)起到遠(yuǎn)地Peer的連接,以便路由數(shù)據(jù)給它們。在另一種配置中,Gateway可以被動(dòng)地接收來自Peer的連接請(qǐng)求,后者隨即經(jīng)由Gateway路由數(shù)據(jù)給另外的Peer。同樣地,在一種使用情況下,Peer可以是主動(dòng)的連接發(fā)起者,而在另一種使用情況下則是被動(dòng)的連接接受者。
由于我們的分布式應(yīng)用的天性,預(yù)先指定連接建立和服務(wù)初始化角色、并將它們硬編碼進(jìn)Gateway和Peer組件的傳統(tǒng)設(shè)計(jì)太不靈活。這樣的設(shè)計(jì)過度地將連接建立、服務(wù)初始化和服務(wù)處理組件耦合在一起。這樣的緊耦合使得獨(dú)立于通信角色改變連接角色變得很困難。
分布式系統(tǒng)中利用面向連接協(xié)議來在服務(wù)端點(diǎn)間進(jìn)行通信的客戶/服務(wù)器應(yīng)用。
分布式應(yīng)用常常含有復(fù)雜的代碼,執(zhí)行連接建立和服務(wù)初始化。一般而言,分布式應(yīng)用中在服務(wù)端點(diǎn)間交換的數(shù)據(jù)的處理極大地獨(dú)立于配置問題,比如(1)是哪一個(gè)端點(diǎn)發(fā)起連接,也就是,連接角色 vs. 通信角色,以及(2)連接管理協(xié)議 vs. 網(wǎng)絡(luò)編程API。下面對(duì)這些問題進(jìn)行概述:
-
連接角色 vs.
通信角色:連接建立角色天然地不對(duì)稱,也就是,被動(dòng)的服務(wù)端點(diǎn)進(jìn)行等待,而主動(dòng)的服務(wù)端點(diǎn)發(fā)起連接。但是,一旦連接建立,通信角色和連接角色可以是互不相關(guān)的。因而,數(shù)據(jù)可以任意的服從服務(wù)通信協(xié)議的方式在服務(wù)端點(diǎn)間進(jìn)行傳輸。常用的通信協(xié)議包括點(diǎn)對(duì)點(diǎn)、請(qǐng)求-響應(yīng)和單路流。
-
連接管理協(xié)議 vs.
網(wǎng)絡(luò)編程API
:不同的網(wǎng)絡(luò)編程接口,比如socket或TLI,提供不同的API來使用多種連接管理協(xié)議建立連接。但是,不管用于建立連接的協(xié)議是什么,數(shù)據(jù)可以使用統(tǒng)一的消息傳遞操作來在端點(diǎn)間進(jìn)行傳輸,例如,send/recv調(diào)用。
一般而言,用于連接建立和服務(wù)初始化的策略變動(dòng)的頻度要遠(yuǎn)小于應(yīng)用服務(wù)實(shí)現(xiàn)和通信協(xié)議。因而,使這些方面去耦合、以使它們能獨(dú)立地變化,對(duì)于開發(fā)和維護(hù)分布式應(yīng)用來說是必要的。對(duì)于將連接及初始化協(xié)議與通信協(xié)議分離的問題,下面一些壓力會(huì)對(duì)解決方案產(chǎn)生影響:
- 應(yīng)該容易增加新類型的服務(wù)、新的服務(wù)實(shí)現(xiàn)和新的通信協(xié)議,而又不影響現(xiàn)有的連接建立和服務(wù)初始化軟件。例如,可能有必要擴(kuò)展Gateway,以與運(yùn)行在IPX/SPX通信協(xié)議、而不是TCP/IP之上的目錄服務(wù)進(jìn)行互操作。
- 應(yīng)該有可能使下面的兩種角色去耦合:(1)連接角色,也就是,哪一個(gè)進(jìn)程發(fā)起連接 vs. 接受連接,以及(2)通信角色,也就是,哪一個(gè)服務(wù)端點(diǎn)是客戶或服務(wù)器。通常,“客戶”和“服務(wù)器”之間的區(qū)分指的是通信角色,它們可以與連接角色不相關(guān)。例如,在發(fā)起到被動(dòng)服務(wù)器的連接時(shí),客戶常常扮演主動(dòng)角色。但是,這些連接角色可以反轉(zhuǎn)過來。例如,扮演主動(dòng)通信角色的客戶可以被動(dòng)地等待另一個(gè)進(jìn)程對(duì)其進(jìn)行連接。9.2中的例子演示了后一種使用情況。
- 應(yīng)該有可能編寫可以移植到許多OS平臺(tái)上的通信軟件,以最大化可用性和市場(chǎng)占有率。許多低級(jí)網(wǎng)絡(luò)編程API的語(yǔ)義只是有著表面的不同,而語(yǔ)法卻互不兼容,因而難以使用低級(jí)API,比如socket和TLI,來編寫可移植應(yīng)用。
- 應(yīng)該有可能將程序員與低級(jí)網(wǎng)絡(luò)編程API(像socket或TLI)類型安全性的缺乏屏蔽開來。例如,連接建立代碼應(yīng)完全地與后續(xù)的數(shù)據(jù)傳輸代碼去耦合,以確保端點(diǎn)被正確地使用。沒有這種強(qiáng)去耦,服務(wù)可能會(huì)錯(cuò)誤地在被動(dòng)模式的傳輸端點(diǎn)工廠上讀寫數(shù)據(jù),而后者僅應(yīng)被用于接受連接。
- 應(yīng)該有可能通過使用像異步連接建立這樣的OS特性來降低連接響應(yīng)延遲。例如,有大量對(duì)端的應(yīng)用可能需要異步、并發(fā)地建立許多連接。高效和可伸縮的連接建立對(duì)于運(yùn)行在高響應(yīng)延遲的WAN上的應(yīng)用來說特別重要。
- 應(yīng)該可以盡可能多地復(fù)用通用的連接建立和服務(wù)初始化軟件,以有效利用先前的開發(fā)成果。
對(duì)于分布式應(yīng)用提供的每個(gè)服務(wù),使用接受器-連接器模式來使連接建立及服務(wù)初始化與由服務(wù)的兩個(gè)端點(diǎn)在連接和初始化之后執(zhí)行的后續(xù)處理去耦合。
引入兩個(gè)工廠,生成已連接和初始化的服務(wù)處理器,用于實(shí)現(xiàn)應(yīng)用的服務(wù)。第一個(gè)工廠,稱為接受器,創(chuàng)建并初始化傳輸端點(diǎn),被動(dòng)地在特定地址上偵聽來自遠(yuǎn)地連接器的連接請(qǐng)求。第二個(gè)工廠,連接器,主動(dòng)地發(fā)起到遠(yuǎn)地接受器的連接。接受器和連接器都初始化相應(yīng)的服務(wù)處理器,處理在連接上交換的數(shù)據(jù)。一旦服務(wù)處理器被連接和初始化,它們就執(zhí)行應(yīng)用特有的處理,一般不再與接受器和連接器進(jìn)行交互。
在圖9-2中通過Booch類圖[2]演示了接受器-連接器模式中的參與者的結(jié)構(gòu)。
服務(wù)處理器(Service Handler
):Service Handler實(shí)現(xiàn)應(yīng)用服務(wù),通常扮演客戶角色、服務(wù)器角色,或同時(shí)扮演這兩種角色。它提供掛鉤方法,由Acceptor或Connector調(diào)用,以在連接建立時(shí)啟用應(yīng)用服務(wù)。此外,Service Handler還提供數(shù)據(jù)模式傳輸端點(diǎn),其中封裝了一個(gè)I/O句柄,比如socket。一旦連接和初始化后,該端點(diǎn)被Service Handler用于與和其相連的對(duì)端交換數(shù)據(jù)。
接受器(Acceptor
):Acceptor是一個(gè)工廠,實(shí)現(xiàn)用于被動(dòng)地建立連接并初始化與其相關(guān)聯(lián)的Service Handler的策略。此外,Acceptor包含有被動(dòng)模式的傳輸端點(diǎn)工廠,它創(chuàng)建新的數(shù)據(jù)模式端點(diǎn),由Service Handler用于在相連的對(duì)端間傳輸數(shù)據(jù)。通過將傳輸端點(diǎn)工廠綁定到網(wǎng)絡(luò)地址,比如Acceptor在其上偵聽的TCP端口號(hào),Acceptor的open方法對(duì)該工廠進(jìn)行初始化。
一旦初始化后,被動(dòng)模式的傳輸端點(diǎn)工廠偵聽來自對(duì)端的連接請(qǐng)求。當(dāng)連接請(qǐng)求到達(dá)時(shí),Acceptor創(chuàng)建Service Handler,并使用它的傳輸端點(diǎn)工廠來將新連接接受進(jìn)Service Handler中。
連接器(Connector
):Connector是一個(gè)工廠,實(shí)現(xiàn)用于主動(dòng)地建立連接并初始化與其相關(guān)聯(lián)的Service Handler的策略。它提供方法,由其發(fā)起到遠(yuǎn)地Acceptor的連接。同樣地,它還提供另一個(gè)方法,完成對(duì)Service Handler的啟用;該處理器的連接是被同步或異步地發(fā)起的。Connector使用兩個(gè)分開的方法來透明地支持異步連接建立。
圖9-2 Acceptor-Connector模式的參與者的結(jié)構(gòu)
當(dāng)連接建立時(shí),Acceptor和Connector都通過調(diào)用Service Handler的啟用掛鉤方法來將其啟用。一旦Service Handler被Acceptor或Connector工廠完全初始化,它通常就不再與這些組件進(jìn)行交互了。
分派器(Dispatcher
):為Acceptor,Dispatcher將在一或多個(gè)傳輸端點(diǎn)上接收到的連接請(qǐng)求多路分離給適當(dāng)?shù)腁cceptor。Dispatcher允許多個(gè)Acceptor向其登記,以偵聽同時(shí)在不同端口上從不同對(duì)端而來的連接。
為Connector,Dispatcher處理異步發(fā)起的連接的完成。在這種情況下,當(dāng)異步連接被建立時(shí),Dispatcher回調(diào)Connector。Dispatcher允許多個(gè)Service Handler通過一個(gè)Connector來異步地發(fā)起和完成它們的連接。注意對(duì)于同步連接建立,Dispatcher并不是必需的,因?yàn)榘l(fā)起連接的線程控制也完成服務(wù)服務(wù)處理器的啟用。
Dispatcher通常使用事件多路分離模式來實(shí)現(xiàn),這些模式由反應(yīng)堆(Reactor)[3]或前攝器(Proactor)[4]來提供,它們分別處理同步和異步的多路分離。同樣地,Dispatcher也可以使用主動(dòng)對(duì)象(Active Object)模式[5]來實(shí)現(xiàn)為單獨(dú)的線程或進(jìn)程。
下面的部分描述接受器-連接器模式中Acceptor和Connector組件所執(zhí)行的協(xié)作。我們檢查三種規(guī)范的情況:Acceptor、異步的Connector和同步的Connector。
圖9-3演示Acceptor和Service Handler之間的協(xié)作。這些協(xié)作被劃分為三個(gè)階段:
-
端點(diǎn)初始化階段:為被動(dòng)地初始化連接,應(yīng)用調(diào)用Acceptor的open方法。該方法創(chuàng)建被動(dòng)模式的傳輸端點(diǎn),將其綁定到網(wǎng)絡(luò)地址,例如,本地主機(jī)的IP地址和TCP端口號(hào),并隨后偵聽來自對(duì)端Connector的連接請(qǐng)求。其次,open方法將Acceptor對(duì)象登記到Dispatcher,以使分派器能夠在連接事件到達(dá)時(shí)回調(diào)Acceptor。最后,應(yīng)用發(fā)起Dispatcher的事件循環(huán),等待連接請(qǐng)求從對(duì)端Connector到來。
-
服務(wù)初始化階段:當(dāng)連接請(qǐng)求到達(dá)時(shí),Dispatcher回調(diào)Acceptor的accept方法。該方法裝配以下活動(dòng)所必需的資源:(1)創(chuàng)建新的Service Handler,(2)使用它的被動(dòng)模式傳輸端點(diǎn)工廠來將連接接受進(jìn)該處理器的數(shù)據(jù)模式傳輸端點(diǎn)中,以及(3)通過調(diào)用Service Handler的open掛鉤將其啟用。Service Handler的open掛鉤可以執(zhí)行服務(wù)特有的初始化,比如分配鎖、派生線程、打開日志文件,和/或?qū)⒃揝ervice Handler登記到Dispatcher。
-
服務(wù)處理階段:在連接被動(dòng)地建立和Service Handler被初始化后,服務(wù)處理階段開始了。在此階段,應(yīng)用級(jí)通信協(xié)議,比如HTTP或IIOP,被用于在本地Service Handler和與其相連的遠(yuǎn)地Peer之間、經(jīng)由前者的peer_stream_端點(diǎn)交換數(shù)據(jù)。當(dāng)交換完成,可關(guān)閉連接和Service Handler,并釋放資源。
圖9-3 Acceptor參與者之間的協(xié)作
9.7.2 Connector
組件協(xié)作
Connector組件可以使用兩種常用方案來初始化它的Service Handler:同步的和異步的。同步的服務(wù)初始化對(duì)于以下情形來說是有用的:
- 如果建立連接的延遲非常低,例如,經(jīng)由回路設(shè)備與在同一主機(jī)上的服務(wù)器建立連接;或是
- 如果有多個(gè)線程控制可用,并且使用不同的線程來同步地連接每個(gè)Service Handler有足夠的效率;或是
- 如果服務(wù)必須以固定順序初始化,而客戶不到連接建立不能執(zhí)行其他有用的工作。
同樣地,異步服務(wù)初始化在相反的情形中是有用的:
- 如果連接延遲很高,并且有許多對(duì)端需要連接,例如,在高延遲WAN之上建立大量連接;或是
- 如果僅有單個(gè)線程控制可用,例如,如果OS平臺(tái)不提供應(yīng)用級(jí)線程;或是
- 如果服務(wù)被初始化的順序不重要,或如果客戶應(yīng)用必須在建立連接的同時(shí)執(zhí)行另外的工作,比如刷新GUI。
同步的Connector情況中的參與者之間的協(xié)作可被劃分為以下三個(gè)階段:
-
連接發(fā)起階段:為在Service Handler和它的遠(yuǎn)地Peer之間發(fā)起連接,應(yīng)用調(diào)用Connector的connect方法。該方法阻塞調(diào)用線程的線程控制、直到連接同步完成,以主動(dòng)地建立連接。
-
服務(wù)初始化階段:在連接完成后,Connector的connect方法調(diào)用complete方法來啟用Service Handler。complete方法通過調(diào)用Service_Handler的open掛鉤方法來完成啟用;open方法執(zhí)行服務(wù)特有的初始化。
-
服務(wù)處理階段:此階段與Service Handler被Acceptor創(chuàng)建后所執(zhí)行的服務(wù)處理階段相類似。特別地,一旦Service Handler被啟用,它使用與和其相連接的遠(yuǎn)地Service Handler交換的數(shù)據(jù)來執(zhí)行應(yīng)用特有的服務(wù)處理。
同步服務(wù)初始化的協(xié)作如圖9-4所示。在此方案中,Connector將連接發(fā)起和服務(wù)初始化階段結(jié)合進(jìn)單一的阻塞操作中。在此情況中,只為每個(gè)線程控制中的每次connect調(diào)用建立一個(gè)連接。
圖9-4 用于同步連接的Connector參與者之間的協(xié)作
異步的Connector中的參與者之間的協(xié)作可被劃分為以下三個(gè)階段:
-
連接發(fā)起階段:為在Service Handler和其遠(yuǎn)地Peer之間發(fā)起一個(gè)連接,應(yīng)用調(diào)用Connector的connect方法。就如同同步方案,Connector主動(dòng)地建立連接。但是,在連接異步完成的同時(shí),它不會(huì)阻塞調(diào)用者的線程控制。相反,它將Service Handler的傳輸端點(diǎn)(我們?cè)诖死袑⑵浞Q為peer_stream_)登記到Dispatcher,并將控制返回給它的調(diào)用者。
-
服務(wù)初始化階段:在連接異步完成后,Dispatcher回調(diào)Connector的complete方法。該方法通過調(diào)用Service Handler的open掛鉤來將其啟用。這個(gè)open掛鉤執(zhí)行服務(wù)特有的初始化。
-
服務(wù)處理階段:此階段與前面描述的其他服務(wù)處理階段相類似。一旦Service Handler被啟用,它使用與和其相連接的遠(yuǎn)地Service Handler交換的數(shù)據(jù)來執(zhí)行應(yīng)用特有的服務(wù)處理。
圖9-5演示這三個(gè)階段的使用異步連接建立的協(xié)作。在異步方案中,注意連接發(fā)起階段被暫時(shí)與服務(wù)初始化階段分離開來。這樣的去耦合使得多個(gè)連接發(fā)起(經(jīng)由connect)和完成(經(jīng)由complete)能夠在各自的線程控制中并行地進(jìn)行。
圖9-5 用于異步連接的Connector參與者之間的協(xié)作
這一部分解釋使用接受器-連接器模式來構(gòu)建通信軟件應(yīng)用所涉及的步驟。這里的實(shí)現(xiàn)基于ACE OO網(wǎng)絡(luò)編程工具包[6]中的可復(fù)用組件和應(yīng)用。ACE提供一組豐富的可復(fù)用C++包裝和構(gòu)架組件,它們可在一系列OS平臺(tái)上執(zhí)行常用的通信軟件任務(wù)。
接受器-連接器模式中的參與者被劃分為反應(yīng)、連接和應(yīng)用層,如圖9-6所示。
反應(yīng)和連接層分別為分派事件和初始化服務(wù)執(zhí)行通用的、與應(yīng)用無關(guān)的策略。應(yīng)用層通過提供建立連接和執(zhí)行服務(wù)處理的具體類來實(shí)例化這些通用策略。這樣的事務(wù)分離增強(qiáng)了接受器-連接器模式實(shí)現(xiàn)中的可復(fù)用性、可移植性和可擴(kuò)展性。
圖9-6 Acceptor-Connector模式實(shí)現(xiàn)中的參與者的分層和劃分
下面對(duì)接受器-連接器模式實(shí)現(xiàn)的討論從底部的反應(yīng)層開始,并向上通過連接層和應(yīng)用層。
反應(yīng)層處理發(fā)生在由I/O句柄表示的傳輸端點(diǎn)(比如socket端點(diǎn))上的事件。該層的兩個(gè)參與者,Initiation Dispatcher(發(fā)起分派器)和Event Handler(事件處理器),是由反應(yīng)堆(Reactor)模式[3]定義的。該模式使得程序在單線程控制中就能夠高效地完成來自多個(gè)來源的多種類型的事件的多路分離。
反應(yīng)層中的兩個(gè)主要角色是:
事件處理器:它規(guī)定由掛鉤方法[7]組成的接口,抽象地表示應(yīng)用可提供的事件處理操作。例如,這些掛鉤方法表示這樣一些事件:新連接請(qǐng)求、異步開始的連接請(qǐng)求的完成,或是來自相連對(duì)端的數(shù)據(jù)的到達(dá),等等。Acceptor和Connector組件是從Event Handler派生的具體的事件處理器。
發(fā)起分派器:為登記、移除和分派Event Handler定義接口。Synchronous Event Demultiplexer(同步的事件多路分離器),比如select[8]或WaitForMultipleObjects[9],通知Initiation Dispatcher何時(shí)回調(diào)應(yīng)用特有的事件處理器,以響應(yīng)特定類型的事件。常用事件包括連接接受事件、數(shù)據(jù)輸入和輸出事件,以及超時(shí)事件。
注意Initiation Dispatcher是9.6描述的Dispatcher的實(shí)現(xiàn)。一般而言,接受器-連接器Dispatcher可以是反應(yīng)式、前攝式(Proactive)或多線程的。在這一實(shí)現(xiàn)中的特定的Initiation Dispatcher使用反應(yīng)式模型來在單線程控制中多路分離和分派具體的事件處理器。在我們的例子中,Initiation Dispatcher是單體(Singleton)[10],因?yàn)槲覀冎恍枰囊粋€(gè)實(shí)例用于整個(gè)進(jìn)程。
連接層:
- 創(chuàng)建Service Handler;
- 被動(dòng)地或主動(dòng)地將Service Handler連接到它們的遠(yuǎn)地對(duì)端;以及
- 一旦連接,啟用Service Handler。
在此層中的所有行為都是完全通用的。特別地,注意下面描述的實(shí)現(xiàn)中的類是怎樣委托具體的IPC機(jī)制和Concrete Service Handler的;后者是由在9.8.3中描述的應(yīng)用層實(shí)例化的。
應(yīng)用層委托連接層的方式與連接層委托反應(yīng)層的方式相類似。例如,反應(yīng)層中的Initiation Dispatcher代表連接層處理與初始化有關(guān)的事件,比如異步的建立連接。
在連接層中有三個(gè)主要角色:Service Handler(服務(wù)處理器)、Acceptor和Connector。
-
服務(wù)處理器:該抽象類繼承自Event_Handler,并為客戶、服務(wù)器或同時(shí)扮演兩種角色的組件所提供的服務(wù)處理提供通用接口。應(yīng)用必須通過繼承來定制此類,以執(zhí)行特定類型的服務(wù)。Service Handler接口如下所示:
// PEER_STREAM is the type of the
// Concrete IPC mechanism.
template <class PEER_STREAM>
class Service_Handler : public Event_Handler
{
public:
// Pure virtual method (defined by a subclass).
virtual int open (void) = 0;
// Accessor method used by Acceptor and
// Connector to obtain the underlying stream.
PEER_STREAM &peer (void)
{
return peer_stream_;
}
// Return the address that we’re connected to.
PEER_STREAM::PEER_ADDR &remote_addr (void)
{
return peer_stream_.remote_addr ();
}
protected:
// Concrete IPC mechanism instance.
PEER_STREAM peer_stream_;
};
一旦Acceptor或Connector建立了連接,它們調(diào)用Service Handler的open掛鉤。該純虛方法必須被Concrete Service Handler子類定義;后者執(zhí)行服務(wù)特有的初始化和后續(xù)處理。
連接器:該抽象類實(shí)現(xiàn)主動(dòng)連接建立和初始化Service Handler的通用策略。它的接口如下所示:
// The SERVICE_HANDLER is the type of service.
// The PEER_CONNECTOR is the type of concrete
// IPC active connection mechanism.
template <class SERVICE_HANDLER,
class PEER_CONNECTOR>
class Connector : public Event_Handler
{
public:
enum Connect_Mode
{
SYNC, // Initiate connection synchronously.
ASYNC // Initiate connection asynchronously.
};
// Initialization method.
Connector (void);
// Actively connecting and activate a service.
int connect (SERVICE_HANDLER *sh,
const PEER_CONNECTOR::PEER_ADDR &addr,
Connect_Mode mode);
protected:
// Defines the active connection strategy.
virtual int connect_service_handler(SERVICE_HANDLER *sh,
const PEER_CONNECTOR::PEER_ADDR &addr,
Connect_Mode mode);
// Register the SERVICE_HANDLER so that it can
// be activated when the connection completes.
int register_handler (SERVICE_HANDLER *sh, Connect_Mode mode);
// Defines the handler’s concurrency strategy.
virtual int activate_service_handler(SERVICE_HANDLER *sh);
// Activate a SERVICE_HANDLER whose
// non-blocking connection completed.
virtual int complete (HANDLE handle);
private:
// IPC mechanism that establishes
// connections actively.
PEER_CONNECTOR connector_;
// Collection that maps HANDLEs
// to SERVICE_HANDLER *s.
Map_Manager<HANDLE, SERVICE_HANDLER *>handler_map_;
// Inherited from the Event_Handler -- will be
// called back by Eactor when events complete
// asynchronously.
virtual int handle_event (HANDLE, EVENT_TYPE);
};
// Useful "short-hand" macros used below.
#define SH SERVICE_HANDLER
#define PC PEER_CONNECTOR
Conncetor通過特定類型的PEER CONNECTOR和SERVICE HANDLER被參數(shù)化。PEER CONNECTOR提供的傳輸機(jī)制被Connector用于主動(dòng)地建立連接,或是同步地、或是異步地。SERVICE HANDLER提供的服務(wù)對(duì)與相連的對(duì)端交換的數(shù)據(jù)進(jìn)行處理。C++參數(shù)化類型被用于使(1)連接建立策略與(2)服務(wù)處理器類型、網(wǎng)絡(luò)編程接口和傳輸層連接協(xié)議去耦合。
參數(shù)化類型是有助于提高可移植性的實(shí)現(xiàn)決策。例如,它們?cè)试S整體地替換Connector所用的IPC機(jī)制。這使得Connector的連接建立代碼可在含有不同網(wǎng)絡(luò)編程接口(例如,有socket,但沒有TLI;反之亦然)的平臺(tái)間進(jìn)行移植。例如,取決于平臺(tái)是支持socket還是TLI[11],PEER CONNECTOR模板參數(shù)可以通過SOCK Connector或TLI Connector來實(shí)例化。使用參數(shù)化類型的另一個(gè)動(dòng)機(jī)是改善運(yùn)行時(shí)效率,因?yàn)槟0鍖?shí)例化發(fā)生在編譯時(shí)。
更為動(dòng)態(tài)的去耦合可以經(jīng)由繼承和多態(tài)、通過使用[10]中描述的工廠方法(Factory Method)和策略(Strategy)模式來完成。例如,Connector可以存儲(chǔ)指向PEER CONNECTOR基類的指針。根據(jù)從工廠返回的PEER CONNECTOR的子類,這個(gè)PEER CONNECTOR的connect方法可在運(yùn)行時(shí)被動(dòng)態(tài)地綁定。一般而言,在參數(shù)化類型和動(dòng)態(tài)綁定之間的權(quán)衡是參數(shù)化類型可能帶來額外的編譯/鏈接時(shí)開銷,而動(dòng)態(tài)綁定可能帶來額外的運(yùn)行時(shí)開銷。
connect方法是應(yīng)用用以通過Connector來發(fā)起連接的入口。它的實(shí)現(xiàn)如下所示:
template <class SH, class PC> int
Connector<SH, PC>::connect(SERVICE_HANDLER *service_handler,
const PEER_CONNECTOR::PEER_ADDR &addr,
Connect_Mode mode)
{
connect_service_handler (service_handler, addr, mode);
}
該方法使用橋接(Bridge)模式[10]來使Concrete Connector能透明地修改連接策略,而不用改變組件接口。為此,connect方法委托Connector的連接策略,connect_service_handler,來發(fā)起連接。如下所示:
template <class SH, class PC> int
Connector<SH, PC>::connect_service_handler
(SERVICE_HANDLER *service_handler,
const PEER_CONNECTOR::PEER_ADDR &remote_addr,
Connect_Mode mode)
{
// Delegate to concrete PEER_CONNECTOR
// to establish the connection.
if (connector_.connect (*service_handler,
remote_addr,
mode) == -1)
{
if (mode == ASYNC && errno == EWOULDBLOCK)
{
// If connection doesn’t complete immediately
// and we are using non-blocking semantics
// then register this object with the
// Initiation_Dispatcher Singleton so it will
// callback when the connection is complete.
Initiation_Dispatcher::instance
()->register_handler (this, WRITE_MASK);
// Store the SERVICE_HANDLER in the map of
// pending connections.
handler_map_.bind
(connector_.get_handle (), service_handler);
}
}
else if (mode == SYNC)
// Activate if we connect synchronously.
activate_service_handler (service_handler);
}
如圖9-7所示,如果Connect_Mode參數(shù)的值是SYNC,一旦連接同步地完成,SERVICE HANDLER將會(huì)被啟用。該圖與圖9-4相類似,但是提供了另外的實(shí)現(xiàn)細(xì)節(jié),比如get_handle和handle_event掛鉤方法的使用。
圖9-7 用于同步連接的Connector參與者之間的協(xié)作
為高效地與多個(gè)Peer相連,Connector可能還需要主動(dòng)、異步地建立連接,也就是,不阻塞調(diào)用者。如圖9-8所示,異步行為通過將ASYNC連接模式傳遞給Connector::connect來指定。該圖與圖9-5相類似,但是還提供了其他與當(dāng)前實(shí)現(xiàn)相應(yīng)的細(xì)節(jié)。
一旦實(shí)例化,PEER CONNECTOR類提供具體的IPC機(jī)制來同步或異步地發(fā)起連接。這里所顯示的Connector模式的實(shí)現(xiàn)使用OS和通信協(xié)議棧所提供的異步連接機(jī)制。例如,在UNIX或Win32上,Connector可以將socket設(shè)置進(jìn)非阻塞模式,并使用像select或WaitForMultipleObject這樣的事件多路分離器來確定連接何時(shí)完成。
為處理還未完成的異步連接,Connector維護(hù)Service Handler映射表。因?yàn)镃onnector繼承自Event Handler,當(dāng)連接完成時(shí),Initiation Dispatcher可以自動(dòng)回調(diào)Connector的handle_event方法。
handle_event方法是一個(gè)適配器(Adapter)[10],它將Initiation Dispatcher的事件處理接口轉(zhuǎn)換為對(duì)Connector模式的complete方法的調(diào)用。
圖9-8 用于異步連接的Connector參與者之間的協(xié)作
Connector的handle_event方法如下所示:
template <class SH, class PC> int
Connector<SH, PC>::handle_event (HANDLE handle, EVENT_TYPE type)
{
// Adapt the Initiation_Dispatcher’s event
// handling API to the Connector’s API.
complete (handle);
}
complete方法啟用剛剛成功完成非阻塞連接的SERVICE HANDLER,如下所示:
template <class SH, class PC> int
Connector<SH, PC>::complete (HANDLE handle)
{
SERVICE_HANDLER *service_handler = 0;
// Locate the SERVICE_HANDLER corresponding
// to the HANDLE.
handler_map_.find (handle, service_handler);
// Transfer I/O handle to SERVICE_HANDLER *.
service_handler->set_handle (handle);
// Remove handle from Initiation_Dispatcher.
Initiation_Dispatcher::instance
()->remove_handler (handle, WRITE_MASK);
// Remove handle from the map.
handler_map_.unbind (handle);
// Connection is complete, so activate handler.
activate_service_handler (service_handler);
}
complete方法在其內(nèi)部映射表中查找并移除已連接的SERVICE HANDLER,并將I/O句柄傳遞給SERVICE HANDLER。最后,它通過調(diào)用activate_service_handler方法初始化SERVICE HANDLER。該方法委托由SERVICE HANDLER的open掛鉤指定的并發(fā)策略。如下所示:
template <class SH, class PC> int
Connector<SH, PC>::activate_service_handler
(SERVICE_HANDLER *service_handler)
{
service_handler->open ();
}
Service Handler的open掛鉤在連接成功建立時(shí)被調(diào)用。注意該掛鉤都將被調(diào)用,不管(1)連接是同步還是異步發(fā)起的,或(2)它們是被主動(dòng)還是被動(dòng)連接的。這樣的統(tǒng)一性使得開發(fā)者有可能編寫這樣的Service Handler,其處理可以完全地與它們是怎樣被連接和初始化的去耦合。
接受器(Acceptor
):該抽象類為被動(dòng)連接建立和初始化Service Handler實(shí)現(xiàn)通用的策略。Acceptor的接口如下所示:
// The SERVICE_HANDLER is the type of service.
// The PEER_ACCEPTOR is the type of concrete
// IPC passive connection mechanism.
template <class SERVICE_HANDLER,
class PEER_ACCEPTOR>
class Acceptor : public Event_Handler
{
public:
// Initialize local_addr transport endpoint factory
// and register with Initiation_Dispatcher Singleton.
virtual int open(const PEER_ACCEPTOR::PEER_ADDR &local_addr);
// Factory Method that creates, connects, and
// activates SERVICE_HANDLER’s.
virtual int accept (void);
protected:
// Defines the handler’s creation strategy.
virtual SERVICE_HANDLER *make_service_handler (void);
// Defines the handler’s connection strategy.
virtual int accept_service_handler(SERVICE_HANDLER *);
// Defines the handler’s concurrency strategy.
virtual int activate_service_handler(SERVICE_HANDLER *);
// Demultiplexing hooks inherited from Event_Handler,
// which is used by Initiation_Dispatcher for
// callbacks.
virtual HANDLE get_handle (void) const;
virtual int handle_close (void);
// Invoked when connection requests arrive.
virtual int handle_event (HANDLE, EVENT_TYPE);
private:
// IPC mechanism that establishes
// connections passively.
PEER_ACCEPTOR peer_acceptor_;
};
// Useful "short-hand" macros used below.
#define SH SERVICE_HANDLER
#define PA PEER_ACCEPTOR
Acceptor通過特定類型的PEER ACCEPTOR和SERVICE HANDLER被參數(shù)化。PEER ACCEPTOR提供的傳輸機(jī)制被Acceptor用于被動(dòng)地建立連接。SERVICE HANDLER提供的服務(wù)對(duì)與遠(yuǎn)地對(duì)端交換的數(shù)據(jù)進(jìn)行處理。注意SERVICE HANDLER是由應(yīng)用層提供的具體的服務(wù)處理器。
參數(shù)化類型使Acceptor的連接建立策略與服務(wù)處理器的類型、網(wǎng)絡(luò)編程接口及傳輸層連接發(fā)起協(xié)議去耦合。就如同Connector一樣,通過允許整體地替換Acceptor所用的機(jī)制,參數(shù)化類型的使用有助于提高可移植性。這使得連接建立代碼可在含有不同網(wǎng)絡(luò)編程接口(比如有socket,但沒有TLI;反之亦然)的平臺(tái)間移植。例如,取決于平臺(tái)能夠更為高效地支持socket還是TLI,PEER ACCEPTOR模板參數(shù)可以通過SOCK Acceptor或TLI Acceptor來實(shí)例化。
下面給出Acceptor的方法的實(shí)現(xiàn)。應(yīng)用通過調(diào)用Acceptor的open方法來將其初始化。如下所示:
template <class SH, class PA> int
Acceptor<SH, PA>::open
(const PEER_ACCEPTOR::PEER_ADDR &local_addr)
{
// Forward initialization to the PEER_ACCEPTOR.
peer_acceptor_.open (local_addr);
// Register with Initiation_Dispatcher, which
// ‘‘double-dispatches’’ without get_handle()
// method to extract the HANDLE.
Initiation_Dispatcher::instance
()->register_handler (this, READ_MASK);
}
local_addr被傳遞給open方法。該參數(shù)含有網(wǎng)絡(luò)地址,例如,本地主機(jī)的IP地址和TCP端口號(hào),用于偵聽連接。Open方法將此地址轉(zhuǎn)發(fā)給PEER ACCEPTOR定義的被動(dòng)連接接受機(jī)制。該機(jī)制初始化傳輸端點(diǎn)工廠,由后者將地址廣告給有興趣與此Acceptor連接的客戶。
傳輸端點(diǎn)工廠的行為由用戶所實(shí)例化的PEER ACCEPTOR的類型來決定。例如,它可以是socket[13]、TLI[14]、STREAM管道[15]、Win32命名管道等的C++包裝。
在傳輸端點(diǎn)工廠被初始化后,open方法將其自身登記到Initiation Dispatcher。Initiation Dispatcher執(zhí)行“雙重分派”,回調(diào)Acceptor的get_handle方法,以獲取底層傳輸端點(diǎn)工廠的HANDLE。如下所示:
template <class SH, class PA> HANDLE
Acceptor<SH, PA>::get_handle (void)
{
return peer_acceptor_.get_handle ();
}
Initiation Dispatcher在內(nèi)部表中存儲(chǔ)此HANDLE。Synchronous Event Demultipler(同步事件多路分離器),比如select,隨即被用于檢測(cè)和多路分離到來的來自客戶的連接請(qǐng)求。因?yàn)锳cceptor類繼承自Event Handler,當(dāng)連接從對(duì)端到達(dá)時(shí),Initiation Dispatcher可以自動(dòng)回調(diào)Acceptor的handle_event方法。該方法是一個(gè)適配器(Adapter),它將Initiation Dispatcher的事件處理接口轉(zhuǎn)換為對(duì)Acceptor的accept方法的調(diào)用。如下所示:
template <class SH, class PA> int
Acceptor<SH, PA>::handle_event (HANDLE, EVENT_TYPE)
{
// Adapt the Initiation_Dispatcher’s event handling
// API to the Acceptor’s API.
accept ();
}
如下所示,accept方法是一個(gè)模板方法(Template Method)[10],它為創(chuàng)建新SERVICE HANDLER、將連接接受進(jìn)其中并啟用服務(wù)而實(shí)現(xiàn)接受器-連接器模式的被動(dòng)初始化策略:
template <class SH, class PA> int
Acceptor<SH, PA>::accept (void)
{
// Create a new SERVICE_HANDLER.
SH *service_handler = make_service_handler ();
// Accept connection from client.
accept_service_handler (service_handler);
// Activate SERVICE_HANDLER by calling
// its open() hook.
activate_service_handler (service_handler);
}
該方法非常簡(jiǎn)潔,因?yàn)樗鼘⑺械图?jí)細(xì)節(jié)都分解進(jìn)具體的SERVICE HANDLER和PEER ACCEPTOR中,后二者通過參數(shù)化類型被實(shí)例化,并可被Acceptor的子類定制。特別地,因?yàn)閍ccept是模板方法,子類可以擴(kuò)展Acceptor的任意或所有的連接建立和初始化策略。這樣的靈活性使得開發(fā)者有可能編寫這樣的Service Handler,其行為與它們被被動(dòng)地連接和初始化的方式是相分離的。
make_service_handler工廠方法定義Acceptor用于創(chuàng)建SERVICE HANDLER的缺省策略。如下所示:
template <class SH, class PA> SH *
Acceptor<SH, PA>::make_service_handler (void)
{
return new SH;
}
缺省行為使用了“請(qǐng)求策略”(demand strategy),它為每個(gè)新連接創(chuàng)建新的SERVICE HANDLER。但是,Acceptor的子類可以重定義這一策略,以使用其他策略創(chuàng)建SERVICE HANDLE,比如創(chuàng)建單獨(dú)的單體(Singleton)[10]或從共享庫(kù)中動(dòng)態(tài)鏈接SERVICE HANDLER。
accept_service_handler方法在下面定義Acceptor所用的SERVICE HANDLER連接接受策略:
template <class SH, class PA> int
Acceptor<SH, PA>::accept_service_handler(SH *handler)
{
peer_acceptor_->accept (handler->peer ());
}
缺省行為委托PEER ACCEPTOR所提供的accept方法。子類可以重定義accept_service_handler方法,以執(zhí)行更為復(fù)雜的行為,比如驗(yàn)證客戶的身份,以決定是接受還是拒絕連接。
Activate_service_handler定義Acceptor的SERVICE HANDLER并發(fā)策略:
template <class SH, class PA> int
Acceptor<SH, PA>::activate_service_handler(SH *handler)
{
handler->open ();
}
該方法的缺省行為是通過調(diào)用SERVICE HANDLER的open掛鉤將其啟用。這允許SERVICE HANDLER選擇它自己的并發(fā)策略。例如,如果SERVICE HANDLER繼承自Event Handler,它可以登記到Initiation Dispatcher,從而在事件發(fā)生在SERVICE HANDLER的PEER STREAM通信端點(diǎn)上時(shí),使Initiation Dispatcher能夠分派其handle_event方法。Concrete Acceptor可以重定義此策略,以完成更為復(fù)雜的并發(fā)啟用。例如,子類可以使SERVICE HANDLER成為主動(dòng)對(duì)象(Active Object)[5],使用多線程或多進(jìn)程來處理數(shù)據(jù)。
當(dāng)Acceptor終止時(shí),無論是由于錯(cuò)誤還是由于整個(gè)應(yīng)用的關(guān)閉,Initiation Dispatcher都調(diào)用Acceptor的handle_close方法,后者可以釋放任何動(dòng)態(tài)獲取的資源。在此例中,handle_close方法簡(jiǎn)單地將close請(qǐng)求轉(zhuǎn)發(fā)給PEER ACCEPTOR的傳輸端點(diǎn)工廠。如下所示:
template <class SH, class PA> int
Acceptor<SH, PA>::handle_close (void)
{
peer_acceptor_.close ();
}
應(yīng)用層提供具體的進(jìn)程間通信(IPC)機(jī)制和具體的Service Handler。IPC機(jī)制被封裝在C++類中,以簡(jiǎn)化編程、增強(qiáng)復(fù)用,并使開發(fā)者能夠整個(gè)地替換IPC機(jī)制。例如,9.9中使用的SOCK Acceptor、SOCK Connector,以及SOCK Stream類是ACE C++ socket包裝類庫(kù)[11]的一部分。它們通過高效、可移植和類型安全的C++包裝來封裝像TCP和SPX這樣的面向連接協(xié)議的面向流的語(yǔ)義。
應(yīng)用層中的三個(gè)主要角色描述如下:
具體的服務(wù)處理器(Concrete Service Handler
):該類定義具體的應(yīng)用服務(wù),由Concrete Acceptor或Concrete Connector啟用。Concrete Service Handler通過特定類型的C++ IPC包裝(它與和其相連的對(duì)端進(jìn)行數(shù)據(jù)交換)來實(shí)例化。
具體的連接器(Concrete Connector
):該類通過具體的參數(shù)化類型參數(shù)SERVICE HANDLER和PEER CONNECTOR來實(shí)例化通用的Connector工廠。
具體的接受器(Concrete Acceptor
):該類通過具體的參數(shù)化類型參數(shù)SERVICE HANDLER和PEER ACCEPTOR來實(shí)例化通用的Acceptor工廠。
Concrete Service Handler還可以定義服務(wù)的并發(fā)策略。例如,Service Handler可以從Event Handler繼承,并采用反應(yīng)堆(Reactor)[3]模式來在單線程控制中處理來自對(duì)端的數(shù)據(jù)。相反,Service Handler也可以使用主動(dòng)對(duì)象(Active Object)模式[5]處理到來的數(shù)據(jù),而其所在線程控制與Acceptor連接它所用的不相同。下面,我們?yōu)槲覀兊腉ateway例子實(shí)現(xiàn)Concrete Service Handler,演示怎樣靈活地配置若干不同的并發(fā)策略,而又不影響接受器-連接器模式的結(jié)構(gòu)或行為。
在9.9的示例代碼中,SOCK Connector和SOCK Acceptor是分別用于主動(dòng)和被動(dòng)地建立連接的IPC機(jī)制。同樣地,SOCK Stream被用作數(shù)據(jù)傳輸遞送機(jī)制。但是,通過其他機(jī)制(比如TLI Connector或Named Pipe Acceptor)來參數(shù)化Connector和Acceptor也是相當(dāng)直接的,因?yàn)镮PC機(jī)制被封裝在C++包裝類中。同樣地,通過使用不同的PEER STREAM,(比如SVR4 UNIX TLI Stream或Win32 Named Pipe Stream)來參數(shù)化Concrete Service Handler,很容易改變數(shù)據(jù)傳輸機(jī)制。
9.9演示怎樣實(shí)例化Concrete Service Handler、Concrete Connector和Concrete Acceptor,實(shí)現(xiàn)9.2中描述的Peer和Gateway。這個(gè)特定的應(yīng)用層例子定制連接層中的Connector和Acceptor組件所提供的通用初始化策略。
下面的代碼演示9.2中描述的Peer和Gateway怎樣使用接受器-連接器模式來簡(jiǎn)化連接建立和服務(wù)初始化。9.9.1演示Peer怎樣扮演被動(dòng)角色,9.9.2演示Gateway怎樣在與被動(dòng)的Peer的連接建立中扮演主動(dòng)角色。
圖9-9演示Concrete Acceptor和Concrete Service Handler組件是怎樣在Peer中構(gòu)造的。該圖中的Acceptor組件與圖9-11中的Connector組件是互補(bǔ)的。
用于與Gateway
通信的服務(wù)處理器:如下所示的Status Handler、Bulk Data Handler和Command Handler類處理發(fā)送到Gateway和從Gateway接收的路由消息。因?yàn)檫@些Concrete Service Handler類繼承自Service Handler,它們可以被Acceptor被動(dòng)地初始化。
為演示接受器-連接器模式的靈活性,這些Service Handler中的每個(gè)open例程都可以實(shí)現(xiàn)不同的并發(fā)策略。特別地,當(dāng)Status Handler被啟用時(shí),它運(yùn)行在單獨(dú)的線程中;Bulk Data Handler作為單獨(dú)的進(jìn)程運(yùn)行;而Command Handler運(yùn)行在與Initiation Dispatcher相同的線程中,后者為Acceptor工廠進(jìn)行連接請(qǐng)求的多路分離。注意這些并發(fā)策略的改變并不影響Acceptor的實(shí)現(xiàn),它是通用的,因而也是高度靈活和可復(fù)用的。
我們從定義一個(gè)Service Handler開始,它為基于socket的數(shù)據(jù)傳輸使用SOCK Stream:
typedef Service_Handler <SOCK_Stream>PEER_HANDLER;
圖9-9 對(duì)端的Acceptor參與者的結(jié)構(gòu)
PEER HANDLER的typedef構(gòu)成所有后續(xù)服務(wù)處理器的基礎(chǔ)。例如,Status Handler類處理發(fā)送到Gateway和從Gateway接收的狀態(tài)數(shù)據(jù):
class Status_Handler : public PEER_HANDLER
{
public:
// Performs handler activation.
virtual int open (void)
{
// Make handler run in separate thread (note
// that Thread::spawn requires a pointer to
// a static method as the thread entry point).
Thread::spawn (&Status_Handler::service_run, this);
}
// Static entry point into thread, which blocks
// on the handle_event () call in its own thread.
static void *service_run (Status_Handler *this_)
{
// This method can block since it
// runs in its own thread.
while (this_->handle_event () != -1)
continue;
}
// Receive and process status data from Gateway.
virtual int handle_event (void)
{
char buf[MAX_STATUS_DATA];
stream_.recv (buf, sizeof buf);
// ...
}
// ...
};
PEER HANDLER還可被子類化,以生成具體的服務(wù)處理器,處理大塊數(shù)據(jù)和命令。例如,Bulk Data Handler類處理發(fā)送到Gateway和從Gateway接收的大塊數(shù)據(jù):
class Bulk_Data_Handler : public PEER_HANDLER
{
public:
// Performs handler activation.
virtual int open (void)
{
// Handler runs in separate process.
if (fork () == 0) // In child process.
// This method can block since it
// runs in its own process.
while (handle_event () != -1)
continue;
// ...
}
// Receive and process bulk data from Gateway.
virtual int handle_event (void)
{
char buf[MAX_BULK_DATA];
stream_.recv (buf, sizeof buf);
// ...
}
// ...
};
Command Handler類處理發(fā)送到Gateway和從Gateway接收的命令。
class Command_Handler : public PEER_HANDLER
{
public:
// Performs handler activation.
virtual int open (void)
{
// Handler runs in same thread as main
// Initiation_Dispatcher singleton.
Initiation_Dispatcher::instance
()->register_handler (this, READ_MASK);
}
// Receive and process command data from Gateway.
virtual int handle_event (void)
{
char buf[MAX_COMMAND_DATA];
// This method cannot block since it borrows
// the thread of control from the
// Initiation_Dispatcher.
stream_.recv (buf, sizeof buf);
// ...
}
//...
};
用于創(chuàng)建Peer Service Handler
的接受器:如下所示的s_acceptor、bd_acceptor和c_acceptor對(duì)象是Concrete Acceptor工廠實(shí)例,它們分別創(chuàng)建并啟用Status Handler、Bulk Data Handler和Command Handler。
// Accept connection requests from Gateway and
// activate Status_Handler.
Acceptor<Status_Handler, SOCK_Acceptor> s_acceptor;
// Accept connection requests from Gateway and
// activate Bulk_Data_Handler.
Acceptor<Bulk_Data_Handler, SOCK_Acceptor> bd_acceptor;
// Accept connection requests from Gateway and
// activate Command_Handler.
Acceptor<Command_Handler, SOCK_Acceptor> c_acceptor;
注意模板和動(dòng)態(tài)綁定的使用是怎樣允許特定細(xì)節(jié)靈活地變化的。特別地,在整個(gè)這一部分中,當(dāng)并發(fā)策略被修改時(shí),沒有Acceptor組件發(fā)生變化。這樣的靈活性的原因是并發(fā)策略已被分解進(jìn)Service Handler中,而不是與Acceptor耦合在一起。
圖9-10 對(duì)端中的Acceptor組件的對(duì)象圖
Peer
主函數(shù):主程序通過調(diào)用具體的Acceptor工廠的open掛鉤(以每個(gè)服務(wù)的TCP端口為參數(shù))來對(duì)它們進(jìn)行初始化。如9.8.2所示,每個(gè)Acceptor工廠自動(dòng)地在它的open方法中將其自身登記到Initiation Dispatcher的實(shí)例。
// Main program for the Peer.
int main (void)
{
// Initialize acceptors with their
// well-known ports.
s_acceptor.open (INET_Addr (STATUS_PORT));
bd_acceptor.open (INET_Addr (BULK_DATA_PORT));
c_acceptor.open (INET_Addr (COMMAND_PORT));
// Event loop that handles connection request
// events and processes data from the Gateway.
for (;;)
Initiation_Dispatcher::instance()->handle_events ();
}
一旦Acceptor被初始化,主程序進(jìn)入事件循環(huán),使用Initiation Dispatcher來檢測(cè)來自Gateway的連接請(qǐng)求。當(dāng)連接到達(dá)時(shí),Initiation Dispatcher回調(diào)適當(dāng)?shù)腁cceptor,由其創(chuàng)建適當(dāng)?shù)腜EER HANDLER來執(zhí)行服務(wù)、將連接接受進(jìn)處理器、并啟用處理器。
圖9-10演示在與Gateway(如圖9-12所示)的四個(gè)連接被建立、以及四個(gè)Service Handler被創(chuàng)建和啟用后,Peer中的Concrete Acceptor組件之間的關(guān)系。在Concrete Service Handler與Gateway交換數(shù)據(jù)的同時(shí),三個(gè)Acceptor也在主線程中持續(xù)地偵聽新連接。
圖9-11演示Concrete Connector和Concrete Service Handler組件是怎樣在假想的Gateway配置中構(gòu)造的。該圖中的Connector組件與圖9-9中的Acceptor組件是互補(bǔ)的。
圖9-11 網(wǎng)關(guān)的Connector參與者的結(jié)構(gòu)
用于Gateway
路由的服務(wù)處理器:如下所示的Status Router、Buld Data Router和Command Router類將它們從源Peer接收到的數(shù)據(jù)路由到一或多個(gè)目的Peer。因?yàn)檫@些Concrete Service Handler類繼承自Service Handler,它們可以被Connector主動(dòng)地連接和初始化。
為演示接受器-連接器模式的靈活性,Service Handler中的每個(gè)open例程實(shí)現(xiàn)不同的并發(fā)策略。特別地,當(dāng)Status Router被啟用時(shí),它運(yùn)行在單獨(dú)的線程中;Bulk Data Router作為單獨(dú)的進(jìn)程運(yùn)行;而Command Router運(yùn)行在與Initiation Dispatcher相同的線程中,后者為Connector工廠進(jìn)行連接完成事件的多路分離。就如同Acceptor一樣,注意這些并發(fā)策略的變動(dòng)并不會(huì)影響Connector的實(shí)現(xiàn),它是通用的,因而也是高度靈活和可復(fù)用的。
我們從定義一個(gè)Service Handler開始,它為基于socket的數(shù)據(jù)傳輸而定制:
typedef Service_Handler <SOCK_Stream>PEER_ROUTER;
該類構(gòu)成所有后續(xù)路由服務(wù)的基礎(chǔ)。例如,Status Router類路由狀態(tài)數(shù)據(jù)到Peer,或路由來自Peer的數(shù)據(jù):
class Status_Router : public PEER_ROUTER
{
public:
// Activate router in separate thread.
virtual int open (void)
{
// Thread::spawn requires a pointer to a
// static method as the thread entry point).
Thread::spawn (&Status_Router::service_run, this);
}
// Static entry point into thread, which blocks
// on the handle_event() call in its own thread.
static void *service_run (Status_Router *this_)
{
// This method can block since it
// runs in its own thread.
while (this_->handle_event () != -1)
continue;
}
// Receive and route status data from/to Peers.
virtual int handle_event (void)
{
char buf[MAX_STATUS_DATA];
peer_stream_.recv (buf, sizeof buf);
// Routing takes place here...
}
// ...
};
PEER ROUTER可被子類化,以生成用于路由大塊數(shù)據(jù)和命令具體的服務(wù)處理器。例如,Bulk Data Router路由數(shù)據(jù)到Peer,或路由來自Peer的數(shù)據(jù):
class Bulk_Data_Router : public PEER_ROUTER
{
public:
// Activates router in separate process.
virtual int open (void)
{
if (fork () == 0) // In child process.
// This method can block since it
// runs in its own process.
while (handle_event () != -1)
continue;
// ...
}
// Receive and route bulk data from/to Peers.
virtual int handle_event (void)
{
char buf[MAX_BULK_DATA];
peer_stream_.recv (buf, sizeof buf);
// Routing takes place here...
}
};
Command Router類路由命令數(shù)據(jù)到Peer,或路由來自Peer的命令數(shù)據(jù):
class Command_Router : public PEER_ROUTER
{
public:
// Activates router in same thread as Connector.
virtual int open (void)
{
Initiation_Dispatcher::instance
()->register_handler (this, READ_MASK);
}
// Receive and route command data from/to Peers.
virtual int handle_event (void)
{
char buf[MAX_COMMAND_DATA];
// This method cannot block since it borrows the
// thread of control from the Initiation_Dispatcher.
peer_stream_.recv (buf, sizeof buf);
// Routing takes place here...
}
};
用于創(chuàng)建Peer Service Handler
的接受器:下面的typedef定義為PEER ROUTER而定制的connector工廠:
typedef Connector<PEER_ROUTERS, SOCK_Connector>PEER_CONNECTOR;
不像 Concrete Acceptor組件,我們只需要單個(gè)Concrete Connector。原因是每個(gè)Concrete Acceptor都被用作創(chuàng)建特定類型的Concrete Service Handler(比如Bulk Data Handler或Command Handler)的工廠。因此,必須預(yù)先知道全部的類型,從而使多種Concrete Acceptor類型成為必要。相反,傳遞給Connector的connect方法的Concrete Service Handler是在外部初始化的。因此,它們可以統(tǒng)一地被當(dāng)作PEER ROUTER處理。
Gateway
主函數(shù):Gateway的主程序如下所示。get_peer_addrs函數(shù)創(chuàng)建Status、Bulk Data和Command Router,通過Gateway路由消息。該函數(shù)(它的實(shí)現(xiàn)沒有給出)從配置文件或名字服務(wù)中讀取Peer地址列表。每個(gè)Peer地址含有IP地址和端口號(hào)。一旦Router被初始化,上面定義的Connector工廠通過將ASYNC標(biāo)志傳遞給connect方法來異步地發(fā)起所有連接。
// Main program for the Gateway.
// Obtain an STL vector of Status_Routers,
// Bulk_Data_Routers, and Command_Routers
// from a config file.
void get_peer_addrs (vector<PEER_ROUTERS> &peers);
int main (void)
{
// Connection factory for PEER_ROUTERS.
PEER_CONNECTOR peer_connector;
// A vector of PEER_ROUTERs that perform
// the Gateway’s routing services.
vector<PEER_ROUTER> peers;
// Get vector of Peers to connect with.
get_peer_addrs (peers);
// Iterate through all the Routers and
// initiate connections asynchronously.
for (vector<PEER_ROUTER>::iterator i = peers.begin ();
i != peers.end ();
i++)
{
PEER_ROUTER &peer = *i;
peer_connector.connect (peer,
peer.remote_addr (),
PEER_CONNECTOR::ASYNC);
}
// Loop forever handling connection completion
// events and routing data from Peers.
for (;;)
Initiation_Dispatcher::instance()->handle_events ();
/* NOTREACHED */
}
所有連接都被異步地調(diào)用。它們通過Connector的complete方法并發(fā)地完成,該方法在Initiation Dispatcher的事件循環(huán)中被回調(diào)。此事件循環(huán)還為Command Router對(duì)象多路分離和分派路由事件;該對(duì)象運(yùn)行在Initiation Dispatcher的線程控制中。Status Router和Bulk Data Router分別執(zhí)行在單獨(dú)的線程和進(jìn)程中。
圖9-12演示在與Peer(如圖9-10所示)的四個(gè)連接被建立、以及四個(gè)Concrete Service Handler被創(chuàng)建和啟用后,Gateway中的組件之間的關(guān)系。該圖演示到另一個(gè)Peer的四個(gè)連接,它們被Connector“擁有”,還沒有完成。當(dāng)所有Peer連接完全建立時(shí),Gateway將路由并轉(zhuǎn)發(fā)由Peer發(fā)送給它的消息。
圖9-12 網(wǎng)關(guān)中的Connector組件的對(duì)象圖
接受器-連接器模式已被用于廣泛的構(gòu)架、工具包和系統(tǒng)中:
UNIX網(wǎng)絡(luò)超級(jí)服務(wù)器:比如inetd[13]、listen[14]以及來自ASX構(gòu)架[6]的Service Configurator(服務(wù)配置器)看守。這些超級(jí)服務(wù)器利用主Acceptor進(jìn)程,在一組通信端口上偵聽連接。每個(gè)端口都和與通信有關(guān)的服務(wù)(比如標(biāo)準(zhǔn)的Internet服務(wù)ftp、telnet、daytime和echo)相關(guān)聯(lián)。接受器進(jìn)程使inetd超級(jí)服務(wù)器的功能分解為兩個(gè)分離的部分:一個(gè)用于建立連接,另一個(gè)用于接收和處理來自對(duì)端的的請(qǐng)求。當(dāng)服務(wù)請(qǐng)求在被監(jiān)控的端口上到達(dá)時(shí),接受器進(jìn)程接受請(qǐng)求,并分派適當(dāng)?shù)念A(yù)登記的處理器來執(zhí)行服務(wù)。
CORBA ORB:許多CORBA實(shí)現(xiàn)中的ORB核心層使用接受器-連接器來在客戶請(qǐng)求ORB服務(wù)時(shí)被動(dòng)地初始化服務(wù)器對(duì)象實(shí)現(xiàn)。[17]描述接受器-連接器模式怎樣被用于實(shí)現(xiàn)The ACE ORB(TAO)[18];TAO是CORBA一種實(shí)時(shí)實(shí)現(xiàn)。
WWW瀏覽器:像Netscape和Internet Explorer這樣的WWW瀏覽器中的HTML解析組件使用connector組件的異步版本來建立與服務(wù)器的連接;這些服務(wù)器與HTML頁(yè)面中嵌入的圖像相關(guān)聯(lián)。這樣的行為是特別重要的,于是多個(gè)HTTP連接就可被異步地發(fā)起,以避免阻塞瀏覽器主事件循環(huán)。
Ericsson EOS呼叫中心管理系統(tǒng):該系統(tǒng)使用接受器-連接器模式來使應(yīng)用級(jí)呼叫中心管理器事件服務(wù)器[19]主動(dòng)地與分布式中央管理系統(tǒng)中的被動(dòng)的超級(jí)用戶建立連接。
Spectrum項(xiàng)目:Spectrum項(xiàng)目的高速醫(yī)學(xué)圖像傳輸子系統(tǒng)[20]使用接受器-連接器模式來為存儲(chǔ)大型醫(yī)學(xué)圖像被動(dòng)地建立連接,并初始化應(yīng)用服務(wù)。一旦連接被建立,應(yīng)用就發(fā)送數(shù)兆字節(jié)的醫(yī)學(xué)圖像給圖像倉(cāng)庫(kù);或從圖像倉(cāng)庫(kù)中進(jìn)行接收。
ACE 構(gòu)架:在本論文中描述的Service Handler、Connector和Acceptor類的實(shí)現(xiàn)在ACE面向?qū)ο缶W(wǎng)絡(luò)編程構(gòu)架[6]中作為可復(fù)用組件提供。
接受器-連接器模式提供以下好處:
增強(qiáng)面向?qū)ο筌浖目蓮?fù)用性、可移植性和可擴(kuò)展性:通過使服務(wù)初始化機(jī)制與后續(xù)服務(wù)處理去耦合來實(shí)現(xiàn)。例如,Acceptor和Connector中的應(yīng)用無關(guān)的機(jī)制是可復(fù)用的組件,它們知道怎樣(1)分別主動(dòng)和被動(dòng)地建立連接,以及(2)一旦連接被建立,初始化相關(guān)聯(lián)的Service Handler。相反,Service Handler知道怎樣執(zhí)行應(yīng)用特有的服務(wù)處理。
這樣的事務(wù)分離是通過使初始化策略與服務(wù)處理策略去耦合來完成的。因而,每種策略都可以獨(dú)立地發(fā)展。用于主動(dòng)初始化的策略可以只編寫一次,放進(jìn)類庫(kù)或構(gòu)架中,并通過繼承、對(duì)象合成或模板實(shí)例化來復(fù)用。因而,不需要為每個(gè)應(yīng)用都重寫同樣的主動(dòng)初始化代碼。相反,服務(wù)可以根據(jù)不同的應(yīng)用需求進(jìn)行變化。通過使用Service Handler來參數(shù)化Acceptor和Connector,可以使這樣的變化的影響被局限在軟件的少量組件中。
改善應(yīng)用健壯性:應(yīng)用健壯性是通過徹底地使Service Handler和Acceptor去耦合來改善的。這樣的去耦合確保了被動(dòng)模式傳輸端點(diǎn)工廠peer_acceptor_不會(huì)偶然地被用于讀寫數(shù)據(jù)。這消除了在使用像socket或TLI[11]這樣的弱類型網(wǎng)絡(luò)編程接口時(shí),可能發(fā)生的一類常見錯(cuò)誤。
接受器-連接器模式有以下缺點(diǎn):
額外的間接性:與直接使用底層的網(wǎng)絡(luò)編程接口相比較,接受器-連接器模式可能帶來額外的間接性。但是,支持參數(shù)化類型的語(yǔ)言(比如C++、Ada或Eiffel)可以較小的代價(jià)代價(jià)實(shí)現(xiàn)這些模式,因?yàn)榫幾g器可以內(nèi)聯(lián)用于實(shí)現(xiàn)這些模式的方法調(diào)用。
額外的復(fù)雜性:對(duì)于簡(jiǎn)單客戶應(yīng)用(使用單個(gè)網(wǎng)絡(luò)編程接口與單個(gè)服務(wù)器相連,并執(zhí)行單項(xiàng)服務(wù))來說,該模式可能會(huì)增加不必要的復(fù)雜性。
接受器-連接器模式使用模板方法(Template Method)和工廠方法(Factory Method)模式[10]。Acceptor的accept和Connector的connect及complete函數(shù)是模板方法,它們?cè)谶B接建立時(shí)為連接到遠(yuǎn)地對(duì)端并初始化Service Handler而實(shí)現(xiàn)通用的服務(wù)策略。模板方法的使用使子類可以對(duì)創(chuàng)建、連接和啟用Concrete Service Handler的特定細(xì)節(jié)進(jìn)行修改。工廠方法被用于使Service Handler的創(chuàng)建與它的后續(xù)使用去耦合。
接受器-連接器模式有與客戶-分派器-服務(wù)器(Client-Dispatcher-Service)模式(在[21]中描述)類似的意圖。它們都關(guān)心使主動(dòng)連接建立與后續(xù)服務(wù)去耦合。主要的區(qū)別是接受器-連接器模式同時(shí)致力于同步和異步連接的被動(dòng)和主動(dòng)服務(wù)初始化,而客戶-分派器-服務(wù)器)模式只專注于同步連接建立。
感謝Frank Buschmann和Hans Rohnert對(duì)本論文提出的有益意見。
[1] W. R. Stevens, TCP/IP Illustrated, Volume 1. Reading, Massachusetts: Addison Wesley, 1993.
[2] G. Booch, Object Oriented Analysis and Design with Applications (2nd Edition). Redwood City, California: Benjamin/Cummings, 1993.
[3] D. C. Schmidt, “Reactor: An Object Behavioral Pattern for Concurrent Event Demultiplexing and Event Handler Dispatching,” in Pattern Languages of Program Design (J. O. Coplien and D. C. Schmidt, eds.), pp. 529–545, Reading, MA: Addison-Wesley, 1995.
[4] T. Harrison, I. Pyarali, D. C. Schmidt, and T. Jordan, “Proactor – An Object Behavioral Pattern for Dispatching Asynchronous Event Handlers,” in The 4th Pattern Languages of Programming Conference (Washington University technical report #WUCS-97-34), September 1997.
[5] R. G. Lavender and D. C. Schmidt, “Active Object: an Object Behavioral Pattern for Concurrent Programming,” in Pattern Languages of Program Design (J. O. Coplien, J. Vlissides, and N. Kerth, eds.), Reading, MA: Addison-Wesley, 1996.
[6] D. C. Schmidt, “ACE: an Object-Oriented Framework for Developing Distributed Applications,” in Proceedings of the 6th USENIX C++ Technical Conference, (Cambridge, Massachusetts), USENIX Association, April 1994.
[7] W. Pree, Design Patterns for Object-Oriented Software Development. Reading, MA: Addison-Wesley, 1994.
[8] W.R.Stevens,UNIX Network Programming, Second Edition. Englewood Cliffs, NJ: Prentice Hall, 1997.
[9] H. Custer, Inside Windows NT. Redmond, Washington: Microsoft Press, 1993.
[10] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995.
[11] D. C. Schmidt, T. H. Harrison, and E. Al-Shaer, “Object-Oriented Components for High-speed Network Programming,” in Proceedings of the 1st Conference on Object-Oriented Technologies and Systems,(Monterey,CA), USENIX, June 1, 995.
[12] D. C. Schmidt, “IPC SAP: An Object-Oriented Interface to Interprocess Communication Services,” C++ Report,vol.4, November/December 1992.
[13] W. R. Stevens, UNIX Network Programming, First Edition. Englewood Cliffs, NJ: Prentice Hall, 1990.
[14] S. Rago, UNIX System V Network Programming. Reading, MA: Addison-Wesley, 1993.
[15] D. L. Presotto and D. M. Ritchie, “Interprocess Communication in the Ninth Edition UNIX System,” UNIX Research System Papers, Tenth Edition, vol. 2, no. 8, pp. 523–530, 1990.
[16] Object Management Group, The Common Object Request Broker: Architecture and Specification, 2.0 ed., July 1995.
[17] D. C. Schmidt and C. Cleeland, “Applying Patterns to Develop Extensible ORB Middleware,” Submitted to the IEEE Communications Magazine, 1998.
[18] D. C. Schmidt, D. L. Levine, and S. Mungee, “The Design and Performance of Real-Time Object Request Brokers,” Computer Communications, vol. 21, pp. 294–324, Apr. 1998.
[19] D. C. Schmidt and T. Suda, “An Object-Oriented Framework for Dynamically Configuring Extensible Distributed Communication Systems,” IEE/BCS Distributed Systems Engineering Journal (Special Issue on Configurable Distributed Systems), vol. 2, pp. 280–293, December 1994.
[20] G. Blaine, M. Boyd, and S. Crider, “Project Spectrum: Scalable Bandwidth for the BJC Health System,” HIMSS, Health Care Communications, pp. 71–81, 1994.
[21] F. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, and M. Stal, Pattern-Oriented Software Architecture - A System of Patterns. Wiley and Sons, 1996.
本文轉(zhuǎn)載至ACE開發(fā)者
posted on 2007-02-27 21:47
walkspeed 閱讀(3602)
評(píng)論(0) 編輯 收藏 引用 所屬分類:
ACE Farmeworks