體驗CORBA組件模型CCM:2、實例
摘要:
通過一個簡單的實例,詳細介紹基於CIAO的CCM組件開發過程。
正文:
前面講過,CCM是以EJB爲藍本來定義的,因此,二者在組件分類(與EJB被分爲Session、Entity、Message Driven三種類型一樣,CCM組件被分爲Service、Session、Process、Entity四種類型)、組件的基本組成、開發/部署基本流程等方面十分相似,但由於目前CCM組件應用服務器/應用框架等遠不如EJB成熟,在開發環境的支持方面也遠不如EJB完善,因此,其開發過程還比較繁瑣。
下面將圍繞如下的應用需求詳細討論CIAO平臺下CCM開發的詳細過程:
一個Monitor程序負責監視多臺設備的狀態,且每個設備上均運行一個設備控制程序Controller,Controller的客戶端通過該組件提供的接口來操控Controller;按照常規的方法,可以採用輪詢的方式,Monitor定期向Controller查詢設備目前的狀態信息,也可以通過Event Service在Controller與Monitor間建立事件通道。在這裏,我們採用CCM來解決上述問題。
整個系統包括兩個組件Controller和Monitor;當設備啓動時,Controller通過Monitor提供的DeviceIDAllocator接口獲得由Monitor分配的唯一標識信息(如IP地址等信息,本例中爲簡化問題,該唯一標識僅由一個表示名稱的字符串組成,且Monitor不會記憶已分配給每個設備的標識);當Controller狀態發生變化時,向Monitor發佈DeviceStatus狀態變化通知事件。
一、編寫IDL
IDL文件被用於描述組件Controller、Monitor之間的通信接口、組件的基本組成(包括組件所支持的Facet/Receptacle、eventtype、Event Source/Event Sink、Component Home、組件的Attribute等)以及組件對外提供的接口。
步驟:
1、建立一個目錄DeviceAdmin,在該目錄下創建三個子目錄:Controller、DeviceBase、Monitor,分別作爲Controller組件工程文件、組件公共工程文件、Monitor組件工程文件的存放目錄。
2、在DeviceBase下建立如下idl文件,其中定義了一些組件間通信需要用到的基本接口及結構。
//DeviceBase.idl
#include <Components.idl>
module Device {
const long MAX_RUN_LEVEL = 5;
const long MIN_RUN_LEVEL = 0;
// a demo device unique id
struct DeviceID {
string device_name;
};
// a demo device status
struct DeviceStatus {
long run_level;
};
struct StatusPair {
DeviceID device_id;
DeviceStatus device_status;
};
/**
* @event DeviceStatus
*
* @brief component event between Monitor and Controller
* Controller publishes this event when status change.
*/
eventtype StatusEvent {
public StatusPair status_pair;
};
/**
* @interface DeviceIDAllocator
*
* @brief Controller use this facet to get a unique id from Monitor
*/
interface DeviceIDAllocator {
DeviceID get_id();
};
/**
* @interface DeviceOperate
*
* @brief a interface exposed to client by Controller
*/
interface DeviceOperate {
void power_on();
void power_off();
DeviceID get_device_id();
DeviceStatus get_device_status();
boolean tune(in boolean tune_up);
};
};
其中,DeviceID表示設備的唯一標識,DeviceStatus表示設備的狀態信息,StatusEvent是Controller、Monitor間傳遞的通知事件,DeviceIDAllocator是由Monitor提供的一個Facet接口,DeviceOperate是Controller組件支持的一個普通接口。
3、在Controller目錄下建立如下的idl文件,用於聲明Controller組件對外的接口及與其他組件的通信接口。Controller組件支持DeviceOperate接口,並有一個DeviceIDAllocator類型的receptacle,和可對外發布StatusEvent類型的通知事件,此外,Controller組件還有兩個屬性,分別表示設備的唯一標識和設備的當前狀態,其中唯一標識是隻讀屬性。
//Controller.idl
#include "../DeviceBase/DeviceBase.idl"
module Device
{
/**
* @class Controller
*
* @brief component
*/
component Controller supports DeviceOperate {
publishes StatusEvent notify_out;
uses DeviceIDAllocator id_allocator;
readonly attribute DeviceID device_id;
attribute DeviceStatus device_status;
};
/**
* @class ControllerHome
*
* @brief home for Controller component
*/
home ControllerHome manages Controller { };
};
4、在Monitor目錄下添加如下idl文件,用於聲明Monitor組件對外的接口及與其他組件的通信接口。Monitor組件支持一個DeviceIDAllocator類型的facet,並可接收StatusEvent通知事件。
//Monitor.idl
#include "../DeviceBase/DeviceBase.idl"
module Device
{
/**
* @class Monitor
*
* @brief component
*/
component Monitor {
provides DeviceIDAllocator id_allocator;
consumes StatusEvent notify_in;
};
/**
* @class MonitorHome
*
* @brief home for Monitor component
*/
home MonitorHome manages Monitor {};
};
二、編寫cidl並生成程序架構
cidl文件用於描述組件和組件Home接口的實現和持久狀態,cidl編譯器cidlc可以根據idl和cidl文件爲我們自動生成組件程序框架,從而大大簡化組件的開發。CIDL所生成的實現稱爲executor,executor包含了一些自動實現,並提供了鉤子方法以允許開發人員可以增加定製的組件專門的邏輯。executor可以打包到DLL中,並可以安裝到支持特定目標平臺和編程語言的組件Server中。
步驟:
1、進入Controller目錄,添加如下cidl文件:
//Controller.idl
#include "Controller.idl"
composition session Controller_Impl {
home executor ControllerHome_Exec {
implements Device::ControllerHome;
manages Controller_Exec;
}
};
並執行:
%CIAO_ROOT%/bin/cidlc -I%CIAO_ROOT% -I%CIAO_ROOT%/DAnCE -I%CIAO_ROOT%/ciao -I%TAO_ROOT% -I%TAO_ROOT%/tao -I%TAO_ROOT%/orbsvcs --gen-exec-impl -- Controller.cidl
以生成最終實現類的基本結構,通過執行上述命令,我們將得到Controller_exec.h和Controller_exec.cpp(以及servant類和其它幾個文件,但只有上述兩個文件是我們需要手工修改的),這是我們實現Controller組件方法和ControllerHome接口的地方,其中包含了爲實現組件需要實現的各方法(包括屬性的accessor/mutator方法、組件支持的接口所包含的方法、其它CCM相關的基本方法,如ccm_activate(), ccm_passivate(), ccm_remove()、set_session_context()等)的聲明和空的函數體,你可以無需任何修改即可將上述文件加入組件工程完成編譯。
2、進入Monitor目錄,添加如下cidl文件:
//Monitor.idl
#include "Monitor.idl"
composition session Monitor_Impl {
home executor MonitorHome_Exec {
implements Device::MonitorHome;
manages Monitor_Exec;
}
};
並執行:
%CIAO_ROOT%/bin/cidlc -I%CIAO_ROOT% -I%CIAO_ROOT%/DAnCE -I%CIAO_ROOT%/ciao -I%TAO_ROOT% -I%TAO_ROOT%/tao -I%TAO_ROOT%/orbsvcs --gen-exec-impl -- Monitor.cidl
以生成最終實現類的基本結構,通過執行上述命令,我們將得到Monitor_exec.h和Monitor_exec.cpp。
三、實現組件
雖然上面已經生成了組件實現類的基本架構,但我們還需要藉助其他CCM自動化工具生成我們的工程文件。
步驟:
1、進入DeviceBase目錄,依次執行如下命令:
%CIAO_ROOT%/bin/generate_component_mpc.pl -n DeviceBase
生成DeviceBase基礎工程描述文件。
由於DeviceBase工程僅用於編譯我們的組件工程依賴的基本接口及結構信息,並不是一個組件,因此,我們需要手動刪除DeviceBase_svnt工程的部分內容。打開DeviceBase.mwc文件,刪除DeviceBase_svnt工程CIDL_Files、IDL_Files兩個說明項,僅保留Source_Files說明項下的DeviceBaseS.cpp文件。修改後的DeviceBase_svnt工程部分的內容應該是:
project(DeviceBase_svnt) : ciao_servant_dnc {
after += DeviceBase_stub
sharedname = DeviceBase_svnt
libs += DeviceBase_stub
idlflags += -Wb,export_macro=DEVICEBASE_SVNT_Export -Wb,export_include=DeviceBase_svnt_export.h
dynamicflags = DEVICEBASE_SVNT_BUILD_DLL
Source_Files {
DeviceBaseS.cpp
}
}
2、進入Controller目錄,依次執行如下命令:
%CIAO_ROOT%/bin/generate_component_mpc.pl -p DeviceBase Controller
生成Controller組件工程描述文件。
3、進入Monitor目錄,依次執行如下命令:
%CIAO_ROOT%/bin/generate_component_mpc.pl -p DeviceBase Monitor
生成Monitor組件工程描述文件。
4、進入DeviceAdmin目錄,並在該目錄下執行:
mwc.pl -type vc8
以生成整個Solution文件。打開生成的.sln文件,將看到該Solution下包含有8個Project,分別是:
DeviceBase_stub
DeviceBase_svnt
DeviceBase_Controller_exec
DeviceBase_Controller_stub
DeviceBase_Controller_svnt
DeviceBase_Monitor_exec
DeviceBase_Monitor_stub
DeviceBase_Monitor_svnt
且各工程間的依賴關係已建立好,你只需編譯DeviceBase_Controller_exec、DeviceBase_Monitor_exec即可完成整個工程的編譯,試試編譯這兩個工程,你應該可以順利通過編譯,但由於還沒有添加實現代碼,組件什麼也做不了。
仔細查看自動生成的Monitor_exec.h/Monitor_exec.cpp、Controller_exec.h/Controller_exec.cpp會發現:
1)對於組件的每個普通attribute,會生成一個accessor和一個mutator方法,而對於readonly attribute,僅會生成一個accessor方法;
2)自動工具還自動生成了組件所支持的對外接口的框架;
3)對於組件所支持的Facet,會單獨生成一個框架類,其中包含了該Facet所有方法的空的實現;而Receptacle一方可以通過相應Context類提供的“get_ + 接口名”方法來訪問由對方組件提供的服務。
4)對於Event的接收方,會自動生成一個以“push_ + 事件名”命名的方法;而對於Event的發送方,相應的Servant類以包含了發送事件相關的代碼,我們只需調用相應Context類的“push_ + 事件名”方法即可。
爲了節省篇幅,這裏不詳細介紹實現的具體內容,讀者可以比較剛創建的工程文件和附件中的工程文件,以找出其中被修改的地方,所有的修改均集中在Monitor_exec.h/Monitor_exec.cpp、Controller_exec.h/Controller_exec.cpp幾個文件中。
四、運用CoSMIC描述組件
現在到了整個組件應用開發中最不令人愉快的階段,我們要用CoMIC這個工具來描述我們的組件,以生成組件的descriptor文件。CoMIC是一個用於生成組件部署descriptor的MDD工具,其安裝請參照該項目的安裝說明,雖然CoMIC想用儘可能簡便、直觀的方式來幫助我們編寫descriptor,但其過程仍然有些煩瑣。我不久前曾建議CIAO的作者實現一個類似idl的組件描述語言,以便通過編譯該文件獲得組件的描述信息,但Schmdit博士似乎對此不感興趣,並認爲可以藉助另一項目Cadena提供的支持來實現相關功能,而Cadena是一個Eclipse的插件(它主要面向的是另一基於Java的CCM實現OpenCCM,雖然它也支持CIAO),對於Java開發者來說,Eclipse簡直太完美了,但用Eclipse來開發C++應用實在不是什麼讓人愉快的事情。
作爲一個MDD工具,CoSMIC允許我們用類似繪製UML圖的方式來描述系統內各組件間的關係,以及系統內包含的等。
運用CoSMIC描述組件的基本流程如下:
1、運行idl_to_picml命令解析各idl文件,生成可被CoSMIC導入的平臺無關組件模型語言(PICML,Platform-Independent Component Modeling Language)xml描述文件。
2、添加ComponentImplementation,以虛擬組件的形式描述各組件間Facet/Receptacle、Event Source/Event Sink等組件端口(Port)的集成關係;
3、添加ComponentPackage,描述組件與組件實現、組件端口之間的集成關係。
4、添加PackageConfiguration,描述組件與相應ComponentPackage之間的集成關係。
5、添加ToplevelPackage,描述頂層包與虛擬組件包之間的集成關係。
6、添加Targets描述,以定義可供組件駐留的節點;
7、添加DeploymentPlan,以定義組件與節點之間的關係。
限於篇幅,這裏不詳細介紹CoSMIC描述組件的細節,具體過程請參照%CIAO_ROOT%/docs/tutorials/CoSMIC,附件中包含了最終完成的CoSMIC工程文件,可供讀者對照。
完成CoSMIC工程後,在工程目錄DeviceAdmin下創建一個目錄descriptors目錄,選擇工具欄上的Generate Package Descriptors、Generate Domain Descriptors、Generate Flattend DeploymentPlan生成工程的部署描述文件,將所有輸出文件保存到descriptors目錄下。(提示:如果你在使用GME時無法看到整個CoSMIC工具欄,請將其拖到可見的範圍內。)
由於CoSMIC與generate_component_mpc.pl採用了不同的Home方法命名方式,你需要手工修改最後生成的Plan.cdp文件中的各Home方法的聲明,如:將createControllerHome_Servant改爲create_Device_ControllerHome_Servant,將createControllerHome_Impl改爲create_Device_ControllerHome_Impl,Monitor組件的兩處修改類似。
五、編寫客戶程序
CCM所定義的組件模型並不會對我們的客戶程序造成影響,所有IDL3所提供的新特性僅被用於組件之間的通信(除了supports關鍵字),因此我們可以像對待CORBA2.x服務程序一樣的方式通過CCM組件所支持的接口來訪問CCM組件程序。但同時,如果我們變換角度,將整個應用系統看作一個組件應用,則我們原來的客戶程序可能變化成組件應用程序的一部分(但我們仍然需要一個客戶程序來訪問組件所提供的功能)。
附件中的Operator客戶程序以Controller組件的IOR爲輸入參數,對Controller組件支持的所有接口方法進行了測試,該程序與普通的CORBA客戶程序並無任何差異。
六、部署與運行
步驟:
1、先通過NodeManager啓動可供組件部署、運行的節點
在descriptors目錄下執行:
run_NodeDaemons.pl
啓動兩個可供Controller、Monitor組件部署、運行的節點,你也可以分別在兩個終端窗口下運行:
%CIAO_ROOT%/DAnCE/NodeManager/NodeManager -ORBEndpoint iiop://localhost:40001 -s %CIAO_ROOT%/DAnCE/NodeApplication/NodeApplication
%CIAO_ROOT%/DAnCE/NodeManager/NodeManager -ORBEndpoint iiop://localhost:40002 -s %CIAO_ROOT%/DAnCE/NodeApplication/NodeApplication
來完成相同的工作。建議讀者用第二種方式,這種方式更加直觀。
2、運行Execution_Manager完成邏輯節點與具體NodeApplication的關聯
運行Execution_Manager需要一個節點與NodeApplication之間的映射關係的描述文件,其大致內容如下:
ControllerNode corbaloc:iiop:localhost:40001/NodeManager
MonitorNode corbaloc:iiop:localhost:40002/NodeManager
將上述內容保存爲NodeManagerMap.dat,並執行:
%CIAO_ROOT%/DAnCE/ExecutionManager/Execution_Manager -o ior.out -i NodeManagerMap.dat
即可完成邏輯節點與具體NodeApplication的關聯。
以後,在Execution_Manager收到Plan_Launcher發送的部署描述信息時便可將組件部署到相應的NodeApplication上。
3、運行Plan_Launcher完成組件的部署
執行
%CIAO_ROOT%/DAnCE/Plan_Launcher/Plan_Launcher -p Plan.cdp -k file://ior.out
其中的Plan.cdp是本文第四節中生成的組件部署描述文件,ior.out則是上一步輸出的Execution_Manager實例的IOR信息。
注:如果你在這一步失敗了,請認真查看錯誤提示確定錯誤原因。在執行1、2、3步之前執行:
set CIAO_DEBUG_LEVEL=11
打印調試信息可以幫助你定位錯誤。
4、運行測試程序
組件部署完畢,下面可以啓動客戶程序來操縱Controller組件了,在descriptors目錄下運行如下命令:
../debug/operator file://Controller.ior
將輸出如下如下信息:
Power on Device...
Tune device run level...
Device's name is [Device-0]
[Device-0] is running at level [2]
Power off device...
而MonitorNode對應NodeApplication所在的終端將輸出:
Device: [Device-0] is running at level [1]
Device: [Device-0] is running at level [2]
Device: [Device-0] is running at level [1]
Device: [Device-0] is running at level [0]
這些信息是在Monitor組件收到Controller組件的DeviceStatus改變通知事件時輸出的。
七、小結
CCM通過引入新的組件集成、配置規範,利用IDL3擴展簡化和規範了對組件間通信機制的描述,使得基於組件的開發成爲可能;同時,基於組件的開發方法由Container來完成組件間的通信,避免了組件間的直接訪問,從而使得我們可以像組裝零件一樣通過集成、配置來進行系統的設計、開發。但目前組件市場並沒有隨着基於組件的開發(Component-Based Development,CBD)這一概念的提出來而迅速發展,從而使得基於組件的開發並不可能那麼輕鬆和自由,但作爲軟件重用方法的一種較爲高級的形式,CBD仍是十分有益的。
與EJB相比,CCM大量借鑑了EJB的設計思想,可以認爲是EJB面向多語言層次的擴展,但由於這種擴展所帶來的衆多問題,使得CCM容器比EJB容器更難實現;與衆多EJB服務器不同,CIAO沒有一個便於操作和管理的CCM服務器,同時也沒有一個合適的集成開發環境可以支撐整個CCM組件的開發過程,使得其開發過程還比較繁瑣,這些都在一定程度上制約了CCM的發展;C++與Java語言的差異和適用範圍,也是造成CCM/EJB命運迥異的一個重要原因;由於CCM的複雜性,研究人員預期的EJB標準進行擴展以與CCM融合的局面也沒有出現。
對於CIAO而言,設計者的高明之處在於,CIAO被設計成一個面向分佈式嵌入式應用的CCM容器,在該領域,C++語言相對於Java具有明顯的優勢,因此也使得CCM比EJB更適用。在其他EJB廣泛應用的領域,雖然已有OpenCCM、K2-CCM等面向Java的CCM實現,但在這些領域嚮應用開發者推廣CCM是非常困難的,畢竟EJB在這些領域具有明顯的優勢:簡單,而且成熟。
參考:
1. Ming Xiong, Building a Stock Quoter with CoSMIC and DAnCE. http://www.dre.vanderbilt.edu/~mxiong/CoSMIC/.
2. Douglas C. Schmidt and Steve Vinoski, Object Interconnections: The CORBA Component Model: Part 1, Evolving Towards Component Middleware, C/C++ Users Journal, February, 2004.
3. Douglas C. Schmidt and Steve Vinoski, Object Interconnections: The CORBA Component Model: Part 2, Defining Components with the IDL 3.x Types, C/C++ Users Journal, April, 2004.
4. Bala Natarajan, Douglas C. Schmidt, and Steve Vinoski, The CORBA Component Model Part 3: The CCM Container Architecture and Component Implementation Framework, C/C++ Users Journal, September, 2004.
5. Bala Natarajan, Douglas C. Schmidt, and Steve Vinoski, The CORBA Component Model Part 4: The CORBA Component Model Part 4: Implementing Components with CCM, C/C++ Users Journal, October, 2004.