(ORBA與分佈式應用)TAO股票報價系統例用

. TAO創建股票報價系統

TAO創建股票報價系統

Building a Stock Quoter with TAO- A Tutorial

 

本教程是圍繞一個單一的應用程序組織的,它允許客戶端程序可以通過報價服務獲取股票報價。該應用程序是基於Doug SchmidtSteve VinoskiC++ Report雜誌的對象互聯專欄的序列文章而開發的。

篇教程從簡單的客戶程序及服務程序到剖析TAO的高級特性而逐步構建,這些高級特性包括了:asynchronous method invocation,reliable oneways, real-time Evnet Service, Interoperable Naming Service等等。

在您閱讀本教程時,您可以自由地從提供的鏈接處獲取到源文件。但是,如果您選擇了編譯和運行示例程序,這些源文件是作爲您下載的TAO源文件的一部分。您可以從這下目錄

$TAO_ROOT/docs/tutorials/Quoter

找到源代碼,裏面還包含了編譯時需要的Makefile文件。由於本在線教有可能使用了與您不同版本的ACE+TAO源代碼,因此單獨下載這些源代碼可能不能正確編譯。

  1. 介紹- 一個很簡單的客戶端
  2. 介紹-一個很簡單的服務端
  3. 介紹-改進服務端,通過POA策略之顯示激活和用戶自定義對象ID
  4. 介紹-改進服務端,通過POA策略之持久化對象引用
  5. 實現倉庫
  6. TAO的命名服務
  7. 異步方法調用-CORBA爲沒有耐心的客戶端提供的解決方案
  8. 按需激活
  9. TAOCos事件服務
  10. TAORT事件服務
  11. RTCORBA

1.介紹—— 一個很簡單的客戶端

們將從一個相當簡單的IDL接口開始我們的教程:我們想要創建一個股票報價服務,可以通過某些接口查詢股票的價格。爲了使學習之旅更加有趣,我們將用不同 CORBA對象表示不同的股票。哦,這看上去有些誇張,但是這些趣味性會激發我們更多的學習慾望,尤其在學習開始的時候。

定義IDL接口

對於股票報價系統,最起碼的操當屬查詢股票價格的操作了,表示如下:

interface Stock

{

    double price();

  };

但通常,股票有符號和全稱,於是我們再增加兩個屬性來便於查詢:

interface Stock

  {

      double price();

       readonly attribute string symbol;

       readonly attribute string  full_name;

   };

我們還需要某個接口,通過它可以根據股票的符號訪問它的對象引用。習慣上,我們把這種接口稱其爲工廠(Factory),它看上去像這樣:

interface Stock_Factory

   {

        Stock get_stock (in string stock_symbole);

    };

請注意參數怎樣具有方向性的:方向性用in,out, inout表示。僅輸入用(in)表示,僅輸出用(out)表示,既輸入也輸出用(inout)表示。到這裏我們還有一個問題,如果股票的符號無效會怎麼辦呢?CORBA的做法是拋出一個異常:

exception Invalid_Stock_Symbol{};

我們再把異常作爲Stock_Factory接口的一部分:

interface Stock_Factory

   {

        Stock get_stock (in string stock_symbole)

           raise (Invalid_Stock_Symbol);

    };

最後,我們把上面所有的IDL組織在一起並放進一個模塊中以避免對名字空間的污染,於是得到了Quoter.idl 文件。

//filename: Quoter.idl

module Quoter
{
    exception Invalid_Stock_Symbol {};
    // Used to report an invalid stock name
    // Forward declare the Stock interface
    interface Stock;
    interface Stock_Factory
    {
       // = TITLE
       // A factory class for the stock quoter interfaces
       //
       // = DESCRIPTION
       // Return the Quoter interfaces based on their names
       //
       Stock get_stock (in string stock_symbol)
                raises (Invalid_Stock_Symbol);
    };

    interface Stock
   {
        // = TITLE
        // A simple interface to query the name and price of stock
        //
        // = DESCRIPTION
        // Return the price and name of a single stock
        //
        readonly attribute string symbol;
        // Get the stock symbol.
        readonly attribute string full_name;
        // Get the name.
        double price ();
       // Get the price
    };
};

生成的文件

讓我們花幾分鐘來查看生成的代碼。你無須經常這樣做,事實上你根本無須這樣做。但是我們這樣做是爲了學習的需要,並幫助您打消對IDL編譯器的神密感。

爲了生成代碼,你必須調用IDL編譯器,操作如下:

$ACE_ROOT/TAO/TAO_IDL/tao_idl Quoter.idl

Windows下,tao_idl.exe%ACE_ROOT%/bin中。

%ACE_ROOT%/bin/tao_idl Quoter.idl

如果把%ACE_ROOT%/bin 添加至環境變量PATH中,則可以簡化爲

tao_idl Quoter.idl

IDL編譯器完整的文檔和選項都包含在compiler.html 中。

TAO爲每個IDL文件生成9個文件。

QuoterC.h, QuoterC.inl QuoterC.cpp 包含了客戶端的接口。注意,內聯(inline)函數放在獨立的文件中,這樣你可以有選擇的編譯它們,以便生成更小的代碼。單純的客戶端只需要鏈接從QuoterC.cpp生成的目標文件。

與之相似,

QuoterS.h, QuoterS.inl QuoterS.cpp 包含了服務端的skeletons。服務端必須鏈接從QuoterS.cppQuoterC.cpp生成的目標文件。

最後,

QuoterT.h,  QuoterT.inl QuoterT.cpp 包含TIE類。有基於組合的方式代替基於繼承的skeleton 準(在CORBA 2.2規範之後)。把它們分離在不同的文件中僅僅是因爲有的編譯器不能處理中同一個源代碼文件中同時混合了模板和非模板的代碼。因此你根據不需要在所有平 臺上編譯這些文件。然後,編譯QuoterS.cpp需要這些文件。還要注意的事,如果您的平臺不支持命字空間,那麼對於有的IDL接口不能使用TIE 法。

上面提到的擴展名和後綴可以通過使用IDL編譯器的選擇來修改,要知道更多的細節,可以查閱文檔。另外還需要注意的事情是您需要在您的項目中使用一致的擴展名,否則在你的IDL源文件中使用某些#include指令時會出現問題。

創建一個簡單的客戶端

IDL接口就緒之後,我們想要編寫簡單的客戶端了。在所有的CORBA客戶端或服務端要做的第一件事就是初始化ORB:

int main( int argc, char* argv[])

{

   try {

          // First initialize the ORB, that will remove some arguments…

         CORBA::ORB_var orb =

              CORBA::ORB_init(argc, argv,

                                      "" /* the ORB name, it can be anything! */

                                     );

IDL支持在編譯時長度未知的變長類型,因此在運行時需要動態分配它們。_var 類型減化了我們對變長類型顯示內存管理,還隱藏了定長和變長結構化的類型之間的區別。

ORB 初始化可能失敗,實際上,所有的CORBA操作都可能拋出CORBA::SystemException 異常,我們用 try/catch語句塊來檢查可能的失敗。 不用說,這是非常天真的;某些失敗是臨時的,我們需要更好的方法從錯誤中恢復,但在我們的示例中這已經足夠了。因此,在main()函數的最後,我們捕獲 CORBA的所有類型的異常。

  }
  catch (CORBA::Exception &ex)

{
    std::cerr << "CORBA exception raised!" << std::endl;
  }
  return 0;
}

我們不能忘記ORB是一種資源,應用程序必須釋放它。直到CORBA 2.3,並沒有標準的方法來處理釋放ORB這件中。TAO已經採納了新的規範,於是,我們的客戶程序將如下所示:

int main (int argc, char* argv[])
{
  try {
    // First initialize the ORB, that will remove some arguments...
    CORBA::ORB_var orb =
      CORBA::ORB_init (argc, argv,
                       "" /* the ORB name, it can be anything! */);
    // the real code goes here!
    orb->destroy ();
  }
  catch (CORBA::Exception &ex) {
    std::cerr << "CORBA exception raised!" << std::endl;
  }
  return 0;
}

ORB名字,僅需要幾句話來說明:如果在CORBA::init中使用了相同的ORB標識,規範要求ORB返回相同的ORB指針,並且,即使使用了不同 的標識,返回相同指針的實現也是自由的。通常這不是問題,這是由於大多數應用程序只實例化單一的ORBTAO是實際支持多個ORB指針中少數CORBA 實現之一。這一點對於real-time應用程序非常重要,在這些應用程序裏,每一個ORB可以在與不同的優先級執行。

既然我們已經擁 有了ORB指針,我們可以啓動此應用程序了。正常情況下,我們使用Naming Service, Trading Service或者Interoperable Naming Service來定位股票工廠,但這裏爲了簡化起見,讓我們在第一個參數中使用IOR字符串。

最簡單的方法是用第一個參數來得到字符串,然後用string_to_object()函數把它轉換成對象引用。

CORBA::Object_var factory_object =
      orb->string_to_object (argv[1]);
    Quoter::Stock_Factory_var factory =
      Quoter::Stock_Factory::_narrow (factory_object.in ());

_narrow()函數用於測試一個引用是否爲指定的類型。如果引用是指定的類型,它返回一個非空的引用,否則返回爲空。

TAO有些有趣的事情:第一,對於對象引用,它支持file:scheme,因此,第一個參數可以是file://a_file_name. 在這種情形下,ORB將打開名爲"a_file_name"的文件並從文件中讀取IORTAO還支持corbaloc:scheme,例如

corbaloc:iiop:[email protected]:12345/Stock_Factory

因此使用字符串可能是非常強大的引導協議。

如果我們要使用從IDL編譯器生成的接口,那就就必須包含正確的頭文件。

#include "QuoterC.h"

注意,這是您僅需要包含的頭一個文件; IDL 編譯器生成的代碼包含了所有需要的內部頭文件。當我們使用TAO服務時,也不要忘了包含那些適當的頭文件。

TAO 另一個有趣的特性是它支持 _unchecked_narrow() 這是CORBA Messaging規範的一部分,在本質上它與_narrow()執行同樣的工作,但不同的時,它並不遠程的檢查類型。如果你在編譯時有必要的信息能確定 narrow操作的正確性,那麼使用不檢查的版本會更高效。

現在我們可以使用剩餘的參數來找回股票對象:

   for (int i = 2; i != argc; ++i) {
      try {
        // Get the stock object
        Quoter::Stock_var stock =
          factory->get_stock (argv[i]);

練習1

完成客戶端的實現。這一點應該很容易,但是它給了你機會來建立你的開發環境並能讓你熟悉構建TAO應用程序的基礎知識。

解決方案

查看 client.cpp文件;和你的代碼比應該沒有多少不同。在你的客戶端代碼、idl文件和QuoterC.*文件中分別數一下代碼的行數,你會想再一次重寫所有的代碼嗎?

測試

若要測試本程序,我們還需要服務端能工作,這是我們進入本教程下一課最好的理由

 

 

 

. 簡介——非常簡單的服務端

2.簡介——非常簡單的服務端

既然我們已經知道如何實現一個簡單的客戶端,那麼現在我們得實現一個服務端來測試它。我們必須提供StockStock_Factory這兩個接口的實現,並且創建可執行的應用程序把這兩個接口的實現組合在一起。

實現Stock (股票)接口

爲了簡單起見,讓我們用固定的價格來實現Stock對象。構造函數將接收所有的參數:

class Quoter_Stock_i : public POA_Quoter::Stock {
public:
  Quoter_Stock_i (const char *symbol,
                  const char *full_name,
                  CORBA::Double price);
private:
  std::string symbol_;
  std::string full_name_;
  CORBA::Double price_;
};

在一個服務器中,用編程語言的數據和函數來實現和表示CORBA對象和函數。這些用於實現和表示CORBA對象的編程實體稱爲伺服代碼(servants)。對象適配器(Object Adapters )把CORBA對象的世界和編程語言的伺服代碼連接在一起,還提供CORBA對象的創建服務和它們的對象引用以及將請求分發到適當的伺服代碼之上。

注意基類的名稱。TAO實現了CORBA 2.2版本的規範,這個規範包含了Portable Object Adapter(因此前綴爲POA)。這個新的對象適配器更正了以前版本的CORBA規範的許多問題,之前使用的的對象適配器於是被稱爲Basic Object Adapter。不幸的事,該規範含混不清,導致了不兼容的實現。由於遵循CORBA 2.2規範,基於POA的代碼幾乎是完全可移植的,不可移植的地方僅僅是生成的頭文件的名稱和其它細微的地方。這些問題很容易通過使用幫助類中進行包裝來 解決,大多數情況下,生成的文件名可以通過控制IDL編譯選項的方式得以處理。

一個服務端程序可能包含了多個POA實例,但所有的服務端程序至少有一個稱爲RootPOAPOA

我們必須實現的操作和屬性。

class Quoter_Stock_i : public POA_Quoter::Stock {
public:
  // some details omitted
  char *symbol ();
  char *full_name ();
  CORBA::Double price ();
};
// In the .cpp file:
char *
Quoter_Stock_i::symbol ()
{
  return CORBA::string_dup (this->symbol_.c_str ());
}

其它屬性和成員與此相似,因此我們沒有在這裏列出來。

對參數的內存管理規則

ORB將調用CORBA::string_free釋放字符串,所以在返回前複製它們是很重要的。 基本的原理是通過網絡,字符串必定會被某種方式進行復制,因此,客戶端必須擔當起釋入接收到的字符串的責任。當客戶端和服務端程序在同一地址空間的時 候,ORB可以優化路徑和直接調用服務端的操作而無須編碼和解碼。如果客戶端同時與本地和遠程服務端同時工作,它總會期待它自己的字符串。因此,服務端的 實現必段分配一份拷貝並返回此拷貝,這是因爲服務端對本地或遠程服務端一視同仁。雖然在CORBA的世界裏,對內存管理的規則比較狡猾,但這裏有一些可以 遵照的簡單原則:

  • 在返回時,你必須創建內存的一份拷貝,由調用者負責釋放它。
  • 相反,你必須釋放返回給你的內存。被調用者創建的拷貝。
  • 對於你接收到作爲輸入的參數,你未有管理它內存的權力,你僅有使用它的權力。如果你想在返回的時候仍要使用它,你必須爲之創建一個拷貝。

完整的內存管理規則可以在Henning Vinoski的書中找到,也可以在CORBA規範中找到。

錄入所有的代碼看上去很繁瑣,可否讓IDL編譯器幫助我們?畢意,看上去方法的聲明已被作了詳細的說明。答案是肯定的。TAOIDL編譯器可以生成空白的實現,而你要做的只是簡單在其上作修改。生成空白的實現只需要使用-GI 選項。

$ $ACE_ROOT/TAO/TAO_IDL/tao_idl -GI Quoter.idl

空白的實現生成在QuoterI.h QuoterI.cpp文件中。要注意的是用了 -GI選項後在每次生成文件時總會覆蓋這些文件,所以好的方法是把它拷貝成別的名稱。

Stock Factory的實現

我們工廠類的第一次實現將只爲兩支股票提供服務,一個是紅帽公司(RHAT的,一個是微軟公司的(MSFT

class Quoter_Stock_Factory_i : public POA_Quoter::Stock_Factory
{
public:
  Quoter_Stock_Factory ();
  Quoter::Stock_ptr get_stock (const char *symbol);
private:
  Quoter_Stock_i rhat_;
  Quoter_Stock_i msft_;
};

get_stock()方法實現很簡單,僅僅是比較符號名並返回適當的對象引用。

Quoter::Stock_ptr
Quoter_Stock_Factory_i::get_stock (const char *symbol)
{
  if (strcmp (symbol, "RHAT") == 0) {
    return this->rhat_._this();
  } else if (strcmp (symbol, "MSFT") == 0) {
    return this->msft_._this ();
  }
  throw Quoter::Invalid_Stock_Symbol ();
}

這裏的 _this()方法是幹什麼的呢?在POA的映射中,通過繼承,客戶端的Stub和服務端的skeleton是互不關聯的。你必須要麼用顯示的方式激活伺 (servant)(你實現的對象),要麼用_this(),通過默認的OA._this()RootPOA下面創建和注意CORBA對象,然後爲新 對象返回對象的引用。在後面我們還爲就顯式和隱式對象激活展開更多的討論,移除關於轉換對象到伺服再到對象引用或反過來都會使服務端不能正常工作,明確這 這一點上是很重要的。

實現工個CORBA服務端

現在在適當的位置我們已經有了對象的實現,下一步就是要創建服務端的可執行文件。我們從ORB的初始化開始。

int main (int argc, char* argv[])
{
  try {
    // First initialize the ORB, that will remove some arguments...
    CORBA::ORB_var orb =
      CORBA::ORB_init (argc, argv,
                       "" /* the ORB name, it can be anything! */);

一開始,ORB開始於 POA {holding state},直到POA激活前,所有收到的請求都不會被處理。同時,這些請求保存於一些實現的限制中。TAO把這個限制設爲0,因爲在實時系統中把請求放入隊列是過載和不可預測的重要來源。

對我們來說這意味着什麼呢?恩,那我們必須激活POA。這個過程有點繁瑣。首先我們需要得到RootROA:

CORBA::Object_var poa_object =
      orb->resolve_initial_references ("RootPOA");
    PortableServer::POA_var poa =
      PortableServer::POA::_narrow (poa_object.in ());

resolve_initial_references()用於啓動所有類型的服務,比如:Naming ServiceTrading Service,同時還用於獲取其它的ORB接口,比如 RootPOACurrent OjectPolicy Manager

既然我們獲得了Root POA,我們就必須取回它的POA管理器。POA管理器提供激活和休息一個或多個POA的接口。

PortableServer::POAManager_var poa_manager =
      poa->the_POAManager ();

那麼,我們激活POA:

poa_manager->activate ();

之後的處理過程與客戶端相似,但現在要記住的是銷燬POA,把所有的代碼合在一起,我們得到:

int main (int argc, char* argv[])
{
  try {
    // First initialize the ORB, that will remove some arguments...
    CORBA::ORB_var orb =
      CORBA::ORB_init (argc, argv,
                       "" /* the ORB name, it can be anything! */);
    CORBA::Object_var poa_object =
      orb->resolve_initial_references ("RootPOA");
    PortableServer::POA_var poa =
      PortableServer::POA::_narrow (poa_object.in ());
    PortableServer::POAManager_var poa_manager =
      poa->the_POAManager ();
    poa_manager->activate ();
    // The application code goes here!
    // Destroy the POA, waiting until the destruction terminates
    poa->destroy (1, 1);
    orb->destroy ();
  }
  catch (CORBA::Exception &ex) {
    std::cerr << "CORBA exception raised!" << std::endl;
  }
  return 0;
}

現在我們創建一個股票工廠實現的實例並用_this()來激活它。

Quoter_Stock_Factory_i stock_factory_i;
    Quoter::Stock_Factory_var stock_factory =
      stock_factory_i._this ();

接下來把對象引用轉換爲IOR字符串,客戶端會使用這個字符串。

    CORBA::String_var ior = orb->object_to_string (stock_factory.in ());
    std::cerr << ior.in () << std::endl;

這裏只有一個最後的細節,我們必須運行ORB的事件循環來開始處理來自客戶端的請求。

     orb->run ();

從這個服務端我們拿掉了去多細節,比如如何中止事件循,如何執行伺服的內豐是,按順序使伺服休眠,實際上這非常不靈活,但我們已經涵蓋了許多其它的事情,最重要一點是我們可以測試客戶端了。

練習 1

把實現豐滿直民來。你無須從0開始,我們提供了這些文件:Stock_i.h, Stock_i.cpp, Stock_Factory_i.h Stock_Factory_i.cpp, Quoter.idl 以及非常有用的MPC file

解決方案:

你的解決方案同Server.cpp文件作比較。

測試

我們需要客戶端來測試這個應用程序。我們只需要同時運行:

$ server > ior_file
$ client file://ior_file MSFT RHAT

再測試一下無效的符號!

 

三:介紹-改進服務端,通過POA策略之顯示激活和用戶自定義對象ID

3.介紹-改進服務端,通過POA策略之顯示激活和用戶自定義對象ID

介紹——改善服務端

在本節,我們將在之前提到的簡單服務器之上進行改進。我們將會討論如何使把POA策略賦給對象ID

先前的示例中,我們用了 Quoter_Stock_Factory_i 的兩個域來表示兩支股票。如果我們想創建上百支股票,那麼這種方法就缺少擴展性。我們需要用某個集合來保持對股票對象的跟蹤,比如爲股票符號建立索引。一 種解決辦法是使用STL map或與之差不多的集合類,但這樣明顯太浪費了。畢意,ORB已經以對象ID爲索引建立了對象的集合。如果僅能自己選擇ID,那麼我們的問題就能得已解 決。一個好消息是:POA允許這樣做;一個壞消息是我們必須創建子POA。那這是爲什麼呢?這是因爲RootPOAID是被ORB分配了的,我們不想要 與它們衝突。此外,創建一個獨立的POA比管理它更容易,因爲多個應用程序的組件可以獲得它們自己的POA,它們能被視爲私有的命字空間。

POA的創建

如前所述,我們可以訪問RootPOA:

   CORBA::Object_var poa_object =
      orb->resolve_initial_references ("RootPOA");
    PortableServer::POA_var poa =
      PortableServer::POA::_narrow (poa_object.in ());

現在我們爲子POA創建策略。在這個應用場景下,我們想使用USER_ID策略,這樣就可以把它賦自己的這些ID。我們還想使用 NO_IMPLICIT_ACTIVATION 策略,以便於對我們的POA有更多的控制。POA具有許多我們可以控制的策略,但我們在示例中只使用默認的策略。在$TAO_ROOT/examples/POA/目錄下有許多示例演示瞭如何使用這些策略。

策略存儲在序列中的,所以我們首先創建序列並初始化它的長度。

    CORBA::PolicyList policies (2);
    policies.length (2);

接下來我們創建策略:

    policies[0] =
      poa->create_id_assignment_policy (PortableServer::USER_ID);
    policies[1] =
      poa->create_implicit_activation_policy (PortableServer::NO_IMPLICIT_ACTIVATION);

再然後我們創建子POA:

    PortableServer::POA_var stock_factory_poa =
      poa->create_POA ("Stock_Factory_POA",
                       poa_manager.in (),
                       policies);

注意的是,我們與RootPOA共用了POA管理器,於是我們只需要用一個POA管理就可以控制這兩個POA的狀態。新的POA複製了策略,所以我們爲了避免內存泄漏,所以需要銷燬它們。

    for (CORBA::ULong i = 0; i != policies.length (); ++i)

    {
      policies[i]->destroy ();
    }

在子POA中激活對象

現在我們必須使用這個POA來激活股票對象了。爲了使示例簡化,我們假設我們可以從標準輸入設備(stdin)中讀入這些股票信息,比如這樣:

   while (!std::cin.eof () && std::cin.peek () != EOF)

  {
      const int max_symbol_length = 8;
      char symbol[max_symbol_length];
      const int max_full_name_length = 64;
      char full_name[max_full_name_length];
      double price;
      std::cin.getline (symbol, max_symbol_length, '/n');
      std::cin.getline (full_name, max_full_name, '/n');
      std::cin >> price;
      std::cin.ignore (1, '/n');
      // The interesting stuff goes here!
    }

爲每個符號、全稱和價格的三元組中, 我們創建股票實現的對象:

     PortableServer::ServantBase_var servant =
        new Quoter_Stock_i (symbol, full_name, price);

ServantBase_var 充當的角色很象自動指針,在發生異常的情況下,它可以小心的管理對象的回收操作。這次我們不能使用_this() 來激活對象,這是因數我們要想創建自己對象ID:

      PortableServer::ObjectId_var oid =
        PortableServer::string_to_ObjectId (symbol);

然後我們通過id來激活對象:

  stock_factory_poa->activate_object_with_id (oid.in (),
                                                  servant.in ());

請小心,不要在這些對象上調用_this(),如果這樣就會在RootPOA中激活它們,被激活了兩次!雖然在不同的POA中(甚於有時還在同一個POA)多次激活一個對象是完全合法的,它是在現在的應用情景中我們並不需要這樣做。

修改Stock Factory

現在我們必須實現Stock Factory的不同版本。我們傳一個子POA的引用到構造函數中並維護這個引用:

class Quoter_Stock_Factory_i : public POA_Quoter::Stock_Factory
{
public:
  Quoter_Stock_Factory (PortableServer::POA_ptr stock_factory_poa)
    : stock_factory_poa_ (PortableServer::POA::_duplicate (stock_factory_poa))
  {}
  Quoter::Stock_ptr get_stock (const char *symbol)
    throw (Quoter::Invalid_Stock_Symbol);
private:
  PortableServer::POA_var stock_factory_poa_;
};

注意,我們複製了POA,這樣才遵從慣用的CORBA對輸入參數的內存管理規則。儘管構造函數並不是CORBA操作,這樣做我們可以把這個規則使用得更廣泛,如果我們堅持使用CORBA這套規則時可以減少誤解。

get_stock 操作的實現更爲有趣。首先我們依據符號創建對象的ID

Quoter::Stock_ptr
Quoter_Stock_Factory_i::get_stock (const char *symbol)
    throw (Quoter::Invalid_Stock_Symbol)
{
  PortableServer::ObjectId_var oid =
    PortableServer::string_to_ObjectId (symbol);

接下來在POA中查找對象的ID:

try {
    CORBA::Object_var tmp =
      this->stock_factory_poa_->id_to_reference (oid.in ());

最後將對象的引用narrow到正常的類型並返回它:

     return Quoter::Stock::_narrow (tmp.in ());
  }

要是符號無效,POA就找不對正確的對象ID,只好拋出異常:

  catch (PortableServer::POA::ObjectNotActive &) {
    throw Quoter::Invalid_Stock_Symbol ();
  }
}

股票對象的內存管理

今爲止,我們還尚爲討論伺服代碼(servants)的內存管理問題。現在是討論它的最好時機了,因爲股票對象已完全授控於POA了。POA爲伺服代碼提 供了引用計數。你並不需要使用引用計數,如果你遵從了這一點,那絕大多數的內存管理就相當的簡單了。那爲什麼不是所有的時候都可以不使用引用計數呢?那是 因爲有的應用程序不需要它。舉例來說,我們先前的簡單服務不需要任何複雜的對象管理,於是所有的對象都是在棧(stack)上創建的。

如果想在POA中使用引用計數,你必須重載_add_ref() remove_ref() 這兩個方法用來增加或減少引用的數目。 一旦數目回到0,你就可以安全的刪除對象(但要記住,計數數目是從1開始的!)。

把這些方法實現爲線程安全是一件繁瑣的工作。爲了減化這個工作,我們混合使用了PortableServer::RefCountServantBase 這個作爲基類,像這樣:

class Quoter_Stock_i
    :  public virtual POA_Quoter::Stock,
       public virtual PortableServer::RefCountServantBase
{
public:
  Quoter_Stock_i (const char *symbol,
                  const char *full_name,
                  CORBA::Double price);
  // as before
};

TAOPortableServer::RefCountServantBase 的實現也是線程安全的,於是你可以用這個技術在你的多線程服務中動態的銷燬對象。您簡單的委託POA來控制,一旦所有線程調用了對象中止,當休眠該對象 時,POA將會調用 _remove_ref(),這樣如果這個對象還在使用中就不會被刪除。也請記住如果你要使用對象請增加它的引用記數。

練習

從簡單服務的文件作如下修改:

  • Stock_i.h: 使用引用計數控制伺服代碼的內存管理。
  • Stock_Factory_i.h: Apply the changes described above to use a child POA with the appropriate policies.
  • Stock_Factory_i.cpp: Apply the changes described above to use a child POA with the appropriate policies.
  • server.cpp: Create the child POA, initialize the stock objects from the stdin, and create the right stock factory class.

你能夠使用相同的Quoter.idl, Stock_i.cpp MPC 文件.

解決方案

在你的解決方案在同下面的文件作比較:

Does this solution scale when the number of stock symbols is in the thousands or millions? Find out about Servant Locators and Servant Activators in the POA!

Testing

有一個簡單的輸入文件可用. 你可以使用簡單客戶端來檢查結果:

$ server < stock_list.txt > ior_file
$ client file://ior_file AAAA BBBB MSFT RHAT CCCC

也測試一下無效的符號!

More Reading

The Henning and Vinoski CORBA book discusses POA policies in detail. Likewise, the Schmidt and Vinoski columns in C++ Report also include several articles about the POA. Finally, the TAO distribution includes examples that illustrate how to use the POA policies.

 

四:改進服務端之通過POA策略實現持久化的對象引用

改進服務端之通過POA策略實現持久化的對象引用

介紹-改進服務器

在本節中,我們將改進之前寫的簡單服務器(第二篇)。我們要利用POA策略來創建具有持久化對象引用的對象。

創建POA時爲其指定策略可以用於控制POA的特性。POA的策回略都具有相同的形式:在創建時使用枚舉類型爲它指定值。在我們的示例中,我們要使用 LifeSpanPolicy這個策略控制對象引用的生命期和創建它的對象的POA的生命期之間的關係;我們還要使用 IdAssignmentPolicy,它可以控制如何給對象ID賦值。

CORBA對象存在的進程如果與創建它或激活它的進程無關,我們稱這種對象爲持久化的對象。同樣地,如果CORBA對象的生命期被綁定在創建它的POA所在的進程中,這種對象被稱爲暫時對象 注意,這一點與對象的狀態沒有關係:一個應用程序可以創建暫時對象來訪問在數據庫中維護的持久化的信息。例如,有這樣的對象可以用來表示不同的連接會話或 數據的交易視圖。類似的,有的持久化對象可能沒有狀態或持久的狀態。例如,因爲日誌設備是持久化的,所以它總有效,但它可以不必維護狀態或只是簡單的爲當 前的活動緩存一些狀態而已。總而言之,具有持久化狀態的對象總是通過持久化對象的引用來訪問的。

RootPOA的標準生命期策略是短暫 (TRANSIEN)。這意味着如果應用程序試圖支持持久化對象必須至少創建另外一個支持持久生命期策略的POA。在我們的例子中,我們將爲子POA 建兩個策略。一個策略是LifeSpanPolicy,它會被設爲持久化的(PERSISTENT)。習慣上,創建持久對象引用的應用程序還會設置 IdAssignmentPolicy,所以這些應用程序可以以一種可預測的方式分配對象ID,與服務端的激活一致。使系統ID具有持久化對象引用雖然也 是可以的,但這樣用非常少見。

POA的創建

和以前一樣,我們先初始化ORB,再爲RootPOA找回引用。

    CORBA::ORB_var orb = CORBA::ORB_init (argc, argv);
    CORBA::Object_var poa_object =
      orb->resolve_initial_references ("RootPOA");
    PortableServer::POA_var poa =
      PortableServer::POA::_narrow (poa_object.in ());
 

接下來我們找回RootPOAPOA管理器好用它來激活RootPOA

    PortableServer::POAManager_var poa_manager =
      poa->the_POAManager ();
   
    poa_manager->activate ();
 

然後我們用PERSISTENT來創建LifeSpanPolicy 對象。

    // Create a PERSISTENT LifespanPolicy object
    PortableServer::LifespanPolicy_var lifespan =
      poa->create_lifespan_policy (PortableServer::PERSISTENT);

and next we create an IdAssignmentPolicy object with the USER_ID value:

   
    // Create a USER_ID IdAssignmentPolicy object
    PortableServer::IdAssignmentPolicy_var idassignment =
      poa->create_id_assignment_policy (PortableServer::USER_ID);
 

再一步,我們初始化策略的序列:

    CORBA::PolicyList polices (2);
    policies.length (2);
    policies[0] =
      PortableServer::IdAssignmentPolicy::_duplicate (idassignment);
    policies[1] =
      PortableServer::LifespanPolicy::_duplicate (lifespan);
 

在父POA上使用create_POA操作創建子POA

    PortableServer::POA_var child_poa =
      poa->create_POA ("childPOA",
                        poa_manager.in (),
                        policies);
 

傳給create_POA操作的參數是:子POA的名稱、子POA的管理器和CORBA策略列表(CORBA::PolicyList)。我們能夠 通過傳遞空引用(nil reference)創建一個被新的POA管理器(POAManager)控制的子POA,但通常情況下使用父POAPOA管理器。

最後一步,我們可以銷燬生命期策略和ID分配策略,因爲我們不必在用它們了。create_POA操作將在策略列表中複製對象,並且新創建POA將引用傳遞給create_POA的對象的拷貝。

    idassignment->destroy ();
    lifespan->destroy ();
 

激活在子POA的對象

既然我們已經創建了新的POA,那讓我們所用這個POA來激活股票對象。第一步將要創建股票工廠實現的實例。

    // Create a servant of class Quoter_Stock_Factory_i
    Quoter_Stock_Factory_i stock_factory_i;
 

可以使用activate_object_with_id () 顯示的激活對象。這個對象有兩個輸入參數:對象的ID和實現它的伺服代碼的指針。

    PortableServer::ObjectId_var oid =
      PortableServer::string_to_ObjectId ("Stock_Factory");
 

第二步,我們可以激活股票工廠對象:

    child_poa->activate_object_with_id (oid.in (),
                                        &stock_factory_i);
 

這個操作並不返回新對象的對象引用,但我們可以用id_to_reference 操作找該對象的引用:

    CORBA::Object_var stock_factory =
      child_poa->id_to_reference (oid.in ());
 

與之前一樣,我們把對象引用轉換成IOR字符串以便客戶端可以使用它。

    CORBA::String_var ior = orb->object_to_string (stock_factory.in ());
    std::cout << ior.in () << std::endl;
 

我們已經知道了,在能處理客戶端的請求的最後一步是要運行ORB事件循環。直到中止時析構時最後我們銷燬POA

    orb->run ();

    // Destroy the POA
    poa->destroy (1,1);
    orb->destroy ();
 

練習

修改在簡單服務器中的 server.cpp 文件來創建持久化的子POA。你還可以使用相同的Quoter.idl Stock_i.h Stcok_i.cpp Stock_Factory_i.h Stock_Factory_i.cpp 文件和使用MPC file文件。

解決方案

你的文件和server.cpp 文件作比較.

測試

你可以使用client.cpp 來檢查結果,如下操作:

    $ ./server -ORBEndPoint iiop://localhost:12345 > server.ref &
 

正常地,ORB隨機的選擇偵聽的終端。這一點對於具有持久化對象引用的應用程序就不適合了。這是因爲如果服務器重啓會選擇一個新的偵聽端口,原來的 引用就變爲無效了。在TAO中,我們可以使用 -ORBEndPoint選項來控制偵聽的端口。比如,對於IIOP協議,終端信息包括了主機的機器名字或IP地址和一個可用的TCP端口號。在下一節 中,我們將學習使用實現倉庫,它和持久化的對象一起工作,這樣就不必顯示地設置偵聽的端口了。

客戶端還是和以前那樣執行:

    $ ./client file://server.ref MSFT RHAT
 

爲了測試POA的持久化,讓我們關掉服務器,然後將對象引用轉化爲新的foo.ref

 
    $ kill %1
    $ ./server -ORBEndPoint iiop://localhost:12345 > foo.ref &
    [2] 6941
 

如果我們再次運行客戶端,我們必定會從服務器得到與先前一樣的結果。

    $ ./client file://server.ref MSFT RHAT
 

如果我們不告之服務器在相同的端口上偵聽會發生什麼呢?讓我們和之前一樣來運行新的服務端:

    $ ./server > server.ref &
        [1] 23897
    $ ./client file://server.ref MSFT RHAT
        The price of a stock in "RedHat, Inc." is $210
        The price of a stock in "Microsoft, Inc." is $91
    $ kill %1
    $ ./server > foo.ref &
        [2] 23908
    $ ./client file://server.ref MSFT RHAT
        CORBA exception raised!TRANSIENT (IDL:omg.org/CORBA/TRANSIENT:1.0)
 

一個CORBA TRANSIENT 異常拋出來了。這表明執行請求必須的某些資源不可用。在這種情況下客戶端的ORB不能在期望的端口號上找到服務端。沒有實現倉庫,客戶端ORB不能在新的 端口裏定位到服務器。必須假設服務端會臨時關掉或在後來重啓,這都會引發TRANSIENT異常。

More Reading

The Henning and Vinoski CORBA book discusses POA policies in detail. Likewise, the Schmidt and Vinoski columns in C++ Report also include several articles about the POA. Finally, the TAO distribution includes several examples that illustrate how to use the POA policies.

 

五:實現庫(Implementation Repository)

實現庫(Implementation Repository)

在先前的示例中,客戶端與服務端必須至少聯繫一次。如果服務器被移動到不同的主機或端口,或服務器已關掉,服務器與客戶端之間的綁就定會失敗。通過外在的定位代理間接綁定,像實現倉庫可以解決上述問題。

實現倉庫維護一個數據結構,這個數據結構作爲保存跟蹤服務器的服務表。它維護已知服務的註冊表、正在運行的服務器的記錄和對應的端口號,並且如果服務器已註冊,還可以根據需要啓動服務器。

服務器創建一個持久化的時候,它設置地址和端口號到IORprofile body,這是爲了實現響應服務器的實現倉庫。當客戶端使用這個IOR的時候,如果實現倉庫沒有關掉,它就與這個實現倉庫進行連接。倉庫解碼它的IOR 和使用根據對象key從服務表在索引到的POA的名稱。倉庫返回當前實際服務器的地址信息。

在這個示例中,讓我們繼續修改我們先前的股票工廠來支持通過實現倉庫的間接綁定。

我們必須做的唯一事情是把我們的子POA與實現倉庫註冊在註冊。

       orb->_tao_add_to_IOR_table ("childPOA", stock_factory.in ());
   

然後,我們和往常一樣將對象引用字符串化後打印出來。

       CORBA::String_var ior = orb->object_to_string (stock_factory.in ());
   

練習

修改在簡單服務器中的 server.cpp 文件用於創建持久化的子POA。您可以使用相同的 Quoter.idl Stock_i.h Stock_i.cpp Stock_Factory_i.h Stock_Factory_i.cpp Client.cpp ,您可以使用MPC 文件。.

解決方案

把您的server.cpp server.cpp 文件作比較。

測試

爲了測試您的修改,您需要運行四個程序。第一步是啓動TAO提供的實現倉庫。實現倉庫被包括至少一個定位器和一個激活器。定位品爲期望使用倉庫的應用程序(在這裏指客戶端)提供可見的接口,而激活器執行激活和維護服務器的實際工作。

首先,我們啓動定位器。我們需要輸出ImplRepo_ServiceIOR到一個文件中,以便於激活器和客戶端都能找到定位器。

       $ $TAO_ROOT/orbsvcs/ImplRepo_Service/ImplRepo_Service -o implrepo.ior -d 0 -ORBobjrefstyle URL &
   

其次,我們啓動激活器,指向定位器的IOR文件。

       $ $TAO_ROOT/orbsvcs/ImplRepo_Service/ImR_Activator -ORBInitRef ImplRepoService=file://implrepo.ior

          ImR Activator: Starting doc.ece.uci.edu
          ImR Activator: Registered with ImR.
   

再次,我們要用實現庫註冊我們的服務器。我們可以使用TAO提供應用程序 $TAO_ROOT/orbsvcs/ImplRepo_Service/tao_imr 來添加我們的服務器到實現倉庫中的服務器數據庫中。

       $ $TAO_ROOT/orbsvcs/ImplRepo_Service/tao_imr -ORBInitRef
         ImplRepoService=file://implrepo.ior add childPOA -c
         "./server -ORBUseIMR 1 -ORBobjrefstyle URL -ORBInitRef
         ImplRepoService=file://implrepo.ior"

          Successfully registered server
   

Windows平臺中,用下面的命令行往實現倉庫中註冊服務器。

    $ACE_ROOT/bin/tao_imr -ORBInitRef ImplRepoService=file://implrepo.ior
    add childPOA -c "./server -ORBUseIMR 1 -ORBobjrefstyle URL
    -ORBInitRef ImplRepoService=file://implrepo.ior"
   

我們指定-ORBInitRef選項來使用在implrepo.ior 文件中的IOR和指定-ORBUseIMR 選項分辯服務器啓動還是關閉的通知而使用的IMR

第二步是爲與IMR一起使用的我們的服務器生成簡單的IOR,使用ior選項,並寫進stock_factory.ior文件。

       $tao_imr -ORBInitRef ImplRepoService=file://implrepo.ior ior
       childPOA -f stock_factory.ior

         corbaloc:iiop:[email protected]:2690/childPOA
   

從現在開始,實現庫用於對我們服務器的跟蹤已全部設置好了並執行我們服務器必要的有關方法調用。

現在,向以前哪樣執行客戶端。

       ./client file://stock_factory.ior MSFT RHAT

       The price of a stock in "Microsoft, Inc." is $91
       The price of a stock in "RedHat, Inc." is $210
   

爲了測試POA的持久化,讓我們關掉服務器然後運行客戶端。

       $tao_imr -ORBInitRef ImplRepoService=file://implrepo.ior
       shutdown childPOA

         Successfully shutdown server

       ./client file://stock_factory.ior MSFT RHAT

         The price of a stock in "RedHat, Inc." is $210
         The price of a stock in "Microsoft, Inc." is $91
   

更多的閱讀資料

欲知更多的實現庫,請點這裏 here

The Henning and Vinoski CORBA book discusses POA policies in detail. Likewise, the Schmidt and Vinoski columns in C++ Report also include several articles about the POA. Finally, the TAO distribution includes examples that illustrate how to use the POA policies.

 

六:TAO的命名服務

TAO的命名服務

迄今爲止我們是用的string_to_object() object_to_string() 來引導客戶端啓動的。如果系統要用於真正的分佈式環境,我們就不能依靠共享分件系統或讓用戶輸入IOR來初始化客戶端了。爲了達到這個目的,CORBA 供了幾種定位服務。其中最簡單的服務是CORBA命名服務。有許多好的教程講述瞭如何使用命名服務,另外在Henning Vinoski 的書中還詳細討論了命名服務的問題。

在本節中,我們只會涉及到命名服務的簡單使用,會集中到如何配置和引導命名服務的引導啓動上。包括了TAO支持的可交互式命名服務(Interoperable Naming Service)

在命名服務中註冊對象

首先我們修改服務器以便於把股票工廠和命名服務註冊。這需要包含正確的頭文件:

#include "orbsvcs/CosNamingC.h"

我們使用下面的方法重新激活股票工廠:

    // Activate it to obtain the object reference
    Quoter::Stock_Factory_var stock_factory =
      stock_factory_i._this ();

我們需要找回命名對象的引用,這步操作使用resolve_initial_references() 來完成。

    CORBA::Object_var naming_context_object =
      orb->resolve_initial_references ("NameService");
    CosNaming::NamingContext_var naming_context =
      CosNaming::NamingContext::_narrow (naming_context_object.in ());

接下來我們初始化要爲對象賦值的名字。命名服務服爲名字使用結構的序列——可以想像爲把路徑分解爲目錄名。在本例中我們使用簡單的名字。在產品化的環境中使用有更好組織層次的命名服務可能是更需要的。第一步是創建和初始化序列: 

    CosNaming::Name name (1);
    name.length (1);

接下來初始化名字:

    name[0].id = CORBA::string_dup ("Stock_Factory");

現在我們準備把對象引用註冊到命名服務中:

    naming_context->bind (name, stock_factory.in ());

注意,如果在命名服務中已註冊了這個名字,那麼 bind() 會失敗。這時,您可以使用 rebind()覆蓋原來的值。

查找對象

現在我們可以通過命名服務來定位對象。用於替代對命令行的依賴,我們必須執行相同的調用來定位命名服務和初始化我們要查找的對象的名名字: 

    CORBA::Object_var naming_context_object =
      orb->resolve_initial_references ("NameService");
    CosNaming::NamingContext_var naming_context =
      CosNaming::NamingContext::_narrow (naming_context_object.in ());

    CosNaming::Name name (1);
    name.length (1);
    name[0].id = CORBA::string_dup ("Stock_Factory");

接下來我們可以解析這個名字:

    CORBA::Object_var factory_object =
      naming_context->resolve (name);
    Quoter::Stock_Factory_var factory =
      Quoter::Stock_Factory::_narrow (factory_object.in ());

到這一步我們就可以像以前那樣使用它了。

練習 1

完成對文件 server.cpp的變更。

爲了完成和測試你的實現,您可以使用下面的文件: Quoter.idl, Makefile, Stock_i.h, Stock_i.cpp, Stock_Factory_i.h Stock_Factory_i.cpp。爲了更有趣,您也可以修改原始的 client.cpp 文件。第一個參數怎麼樣?現在我們還需要IOR麼? 

解決方案

把您的解決方案與文件 client.cpp server.cpp作比較。它們應該很相似。

測試

爲了測試您的變更需要運行四個程序。第一個在您的局域網中配置TAO的命名服務查尋協議以便使用唯一的端口。這個端口基於您的用戶ID是一個好主意。例如: 

$ setenv NameServicePort `expr 10000 + $uid`

現在我們可以啓動TAO提供的命名服務了:

$ $TAO_ROOT/orbsvcs/Naming_Service/Naming_Service

以及您的服務端:

$ server

最後是您的客戶端:

$ client MSFT RHAT RHAT MSFT

找尋命名服務

那麼TAO怎麼找尋命名服務的呢?直到近期也沒有標準的方式來配置命名服務器如何啓動。在TAO中我們決定使用多播來定位服務端。多播協議雖然簡 單,但在不是有許多命名服務運行的小的局域網中工作還是工作得非常好。爲了避免當在同一個局域網中運行多個命名服務的問題時,像上面那樣,您必須爲每個服 務指定不同的多播端口。

不幸的是,上面的協議並不能在跨多個局域網中工作,並且很難保證在多播端口的分配上不衝突。TAO支持交互多命名服務規範,該規範提供了許多機制用 於控制resolve_initial_references() 調用的行爲。例如:您可以讓命名服務輸出它的IOR到文件中,像下面這樣。

$ $TAO_ROOT/orbsvcs/Naming_Service/Naming_Service -o ns.ior

然後用-ORBInitRef 用那個IOR文件代替多播協議:

$ server -ORBInitRef NameService=`cat ns.ior`

或者更好的方法使用file:scheme的方式直接讀取文件:

$ server -ORBInitRef NameService=file://ns.ior

但這依然假定在主機之間存在共享的文件系統,或者需要用戶跨過網絡複製文件。如果我們知道接受IIOP請求的命名服務使用的主機和端口,那麼我們就可以使用corbaloc: scheme 方式:

$ server -ORBInitRef NameService=corbaloc:iiop:ace.cs.wustl.edu:12345/NameService

實際上,對任務TAO程序(包括了命名服務)的主機和端口的控制很簡單。僅僅要求您使用 -ORBEndPoint option選項就行了。

$TAO_ROOT/orbsvcs/Naming_Service/Naming_Service -ORBEndPoint iiop://ace.cs.wustl.edu:12345

當然,它只能夠當您在主機ace.cs.wustl.edu 上運行您的程序,並且端口12345可用的前提下或以工作。您可以使用神奇的端口號0ORB爲您尋找一個可用的端口,實際上這正好是TAO默認這樣執行的。

最好,你可以使用多個-ORBEndPoint 選項在多個端點上偵聽。在對一有多個地址的主機非常有用。

練習2

試着使用不同的方法查尋命名服務。再試着爲命名服務指派一個無效的IOR運行服務器。如果服務器或客戶端通過多播協議選取不同的命名服務會怎麼樣呢?如果他們的配置與期望的命名服務器不一致會怎麼樣呢?

持久化問題

如果命名服務在對象註冊和查詢之間關掉了會怎麼樣呢?默認情況下TAO的命名服務不是持久化的,但解決辦法是使用標記把狀態存放文件中:

$ $TAO_ROOT/orbsvcs/Naming_Service/Naming_Service -f name_service.dat

注意:除非您的服務使用了持久化,或者它可以自動重啓,本節的示例就沒有多大的用處。解決持久化是實現倉庫的職責。

 

七:異步方法調用——針對急迫的( impatient )客戶端的CORBA解決方案

異步方法調用——針對急迫的( impatient )客戶端的CORBA解決方案

們的簡單服務詳細闡述瞭如何通過傳統CORBA同步方向調用來查詢股票的價格的。假定,舉例來說,一個複雜的市場分析工具的初始化時,我們必須對數百支股 票進行價格查詢。在這種情況下按順序發送請求會嚴重影響性能;由於在發送後一條查詢請求之前我們需要等待上一條查詢的返回,所以我們不能利用分佈式的系統 本身的並行性。針對這個問題傳統的解決方法是使用oneway調用或者使用多線程。這兩種方式都是可行的,但都有缺點:多線程編程非常困難並且容易出 錯,oneway不可靠並且需要回調接口返回股票的值。最近,OMG覈准了CORBA Messaging規範來擴展基本的調用模型以支持異步調用。這以之前的同步調用模型不同,新的模型使用IDL編譯器和SII來達到類型安全和提高的性 能,並且應用程序不會在等待影響時阻塞。該模型提供的ORB的一個引用來返回處理器,這個處理器將異步的接收響應。該規範還定義了投遞(polling) 接口,由於TAO未實現此接口,所以這裏不作討論。

爲了解決上面的問題我們擴展IDL接口來包含新的操作:

  interface Single_Query_Stock : Stock {
    double get_price_and_names (out string symbol,
                               out string full_name);
  };

這將使一些示例得以簡化。

第一步是生成支持回調AMIstubsskeletions。我們使用-GC標記:

$ $ACE_ROOT/TAO/TAO_IDL/tao_idl -GC Quoter.idl

您或許想簡單的查看生成的客戶端的接口。IDL編譯器添加新的方法到 Quoter::Stock 接口。請特別注意下面的方法:

  virtual void sendc_get_price_and_names (
      AMI_Single_Query_StockHandler_ptr ami_handler
    );

這就是用於發送異步請求的操作。處理器對象用於接收響應。下面是一個正規的CORBA對象,此對象帶有下面的IDL接口:

interface AMI_Single_Query_StockHandler {
  void get_price_and_names (in double ami_return_val,
                           in string symbol,
                           in string full_name);
};

你無須編寫IDL接口。該接口由IDL編譯器自動從原始的IDL中生成,所以也稱它爲隱式IDL。請注意參數是如何生成的,第一個參數是簡單的返回值,後面是輸出參數,由於處理器必須接收返回,所以設爲input

實現返回處理器

我們必須爲新的IDL接口實現伺服代碼(servant),以便於我們可以接收到返回值,這正如我們的服務端:

class Single_Query_Stock_Handler_i : public POA_Quoter::AMI_Single_Query_StockHandler
{
public:
  Single_Query_Stock_Handler_i (int *response_count)
    : response_count_ (response_count) {}

  void get_price_and_names (CORBA::Double ami_return_val,
                           const char *symbol,
                           const char *full_name)
  {
    std::cout << "The price of one stock in /""
              << full_name << "/" (" << symbol << ") is "
              << ami_return_val << std::endl;
    *this->response_count_++;
  }

private:
  int *response_count_;
};

response_count_ 字段用來當所有的應答都收到之後中止客戶端。

發送異步方向調用

和其它CORBA對象一樣激活處理器伺服器(servant)

    int response_count = 0;
    Single_Query_Stock_Handler_i handler_i (&response_count);
    Quoter::AMI_Single_Query_StockHandler_var handler =
      handler_i._this ();

然後我們修改循環用來立即發送所有的請求:

    int request_count = 0;
    for (int i = 2; i != argc; ++i) {
      try {
        // Get the stock object
        Quoter::Stock_var tmp =
          factory->get_stock (argv[i]);
        Quoter::Single_Query_Stock_var stock =
          Quoter::Single_Query_Stock::_narrow (tmp.in ());

        stock->sendc_get_price_and_names (handler.in ());
        request_count++;
      }

在循環結束後我們一直等待,直到收回所有的響應:

    while (response_count < request_count
           && orb->work_pending ()) {
      orb->perform_work ();
    }

練習1

完成client.cpp 文件。這個客戶端是否扮演服務端的角色?如果不是,該角色涉及到處理器伺服的什麼?如果您認爲這也是服務端,那麼關於POA您做了些什麼?

您可以使用下面的文件完成您的實現:Quoter.idl, Handler_i.h, Handler_i.cpp. 記住簡單客戶端的主函數(here))是一個不錯的開端。

解決方案

查看 client.cpp 文件。這與您的應該不會有太大的差異。

測試

在基於在介紹一節簡單服務端之上提供了簡單服務器。和以前一樣,您需要下面的文件 Stock_i.h, Stock_i.cpp, Stock_Factory_i.h Stock_Factory_i.cpp server.cpp.

配置

迄今爲止我們在TAO中使用的是默認的配置,但是通過好的調整能讓AMI可以工作得更好。例如,默認的TAO爲每個顯示的請求使用單獨的連接。這對 於同步方向調用(SMI)來說是一個好的策略,因爲用單獨的線程發送併發的請求不需要用到共享的資源,但這個方法對於異步方法調用(AMI)來說缺少可伸 縮性,這是因爲它需要創建過多的連接。本解決方案改變策略用於使連接可被共享。爲了達到這個目的,我們需要創建一個下面內容的svc.conf文件:

static Client_Strategy_Factory "-ORBTransportMuxStrategy MUXED"

還有許多其它的配置選項,這些都描述在文檔 Options.htmlconfigurations.html 和來自OCI的開發者指南中。 

 

八:按需激活

伺服(Servant)管理器

在先前的示例中,我們對使用同步方法調用的簡單客戶端進行了擴展,用於處理異步請求:通過使用回覆處理器的異步方向調用。

在應用程序中有許多對象,沒有必要在同時激活所有的對象,並且,如果同時激活所有對象會導致佔用過多的內存或過多的數據庫查詢。爲了解決這類應用程序面臨的問題,POA提供了一個選項讓應用程序提供伺服管理器,伺服管理器可以在每一個請求上動態的提供伺服。

服管理器是一個回調對象,應用程序把它和POA註冊在一起。當POA試圖確定與特定請求關聯的伺服是,它回調應用程序的伺服管理器來找回伺服。想要把伺服 管理器與POA註冊在一起,就要把控制請求與伺服關係的這個RequestProcessingPolicyValue設置爲 USE_SERVANT_MANAGER

根據POA是否把對象與伺服的關聯是否保存在它的主動對象映射表(Active Object Map),我們把伺服管理器分爲兩種類型。這取決於當POA創建時爲ServantRetentionPolicy設的值。如果設爲 RETAIN,那麼POA保留關聯,如果設爲NON_RETAINPOA則不在對象和伺服中保留任何關聯。

POA是具有RETAIN 的情況下伺服管理器必須激活與對象關聯的伺服。這需要伺服管理器對象支持ServantActivator 接口。在POA是具有NON_RETAIN 的情況下,我們的伺服管理器對象將能夠爲請求的對象找到伺服並調用它。

在本示例中,讓我們使用伺服的定位器來當在象被調用時來定位與我們的對Stock_Factory對象關聯的伺服。

The Stock Factory 定位器的實現

我們的Stock_Factory_Locator_i將幫助我們找到Quoter_Stock_Factory伺服。

服定位接口提供兩個操作:調用前和調用後(preinvoke postinvoke)。preinvoke 操作被調用時用於找回伺服來分發請求。preinvode返回的伺服僅用於單一的請求。postinvode操作後來被調用,用於銷燬被 preinvode操作創建的伺服。

      #include "tao/corba.h"
     
      class Quoter_Stock_Factory_Locator_i : public POA_PortableServer::ServantLocator
      {
       public:
        Quoter_Stock_Factory_Locator_i (CORBA::ORB_ptr orb);
   
        // Preinvoke function
        virtual PortableServer::Servant preinvoke (const PortableServer::ObjectId &oid,
                                                   PortableServer::POA_ptr poa,
                                                   const char * operation,
                                                   void * & cookie);
 
        // Postinvoke function
        virtual void postinvoke (const PortableServer::ObjectId & oid,
                                 PortableServer::POA_ptr poa,
                                 const char * operation,
                                 void * cookie,
                                 PortableServer::Servant servant);

       private:
        CORBA::ORB_var orb_;
      };
   

In the implementation of the preinvoke operation, we check if the object ID is valid and then check for the servant we want to create and create and return the requested servant.

preinvoke 操作的實現中,我們檢查對象的ID 是否有效,然後檢查我們想要創建的伺服和返回請求的伺服。

     PortableServer::Servant
     Quoter_Stock_Factory_Locator_i::preinvoke (const PortableServer::ObjectId &oid,
                                                 PortableServer::POA_ptr poa,
                                                 const char * operation,
                                                 void * & cookie)
     {

       try {
 
        // Get the ObjectID in string format
        CORBA::String_var oid_str =
          PortableServer::ObjectId_to_string (oid);

        // Check if the ObjectId is valid
        if (strcmp (oid_str.in (), "Quoter/Stock_Factory") != 0) {
          // Create the requested servant.
          PortableServer::Servant servant =
            new Quoter_Stock_Factory_i ();
       
         cookie = servant;

         return servant;
        }
        else {
          throw CORBA::OBJECT_NOT_EXIST ();
        }

      }catch (const CORBA::BAD_PARAM &) {
        throw CORBA::OBJECT_NOT_EXIST ();
      }
   

postinvoke 操作的實現很簡單。我們僅僅是銷燬在preinvoke 操作中創建的伺服。在這兩個操作中的參數 Cookie IDL type幫助把preinvoke的調用和postinvoke 操作關聯在一起。

      void
      Quoter_Stock_Factory_Locator_i::postinvoke (const PortableServer::ObjectId &oid,
                                                  PortableServer::POA_ptr poa,
                                                  const char * operation,
                                                  void * cookie,
                                                  PortableServer::Servant servant)
      {
 
        // Delete the servant as it is no longer needed.
        PortableServer::Servant my_servant = (PortableServer::Servant) cookie;
        if (servant == my_servant)
          delete servant;
      }
   

服務端的實現

第一平上從RootPOA創建一個新的POA,這個新的POA具有以下特性:  RequestProcessingPolicy的值設爲USE_SERVANT_MANAGER ,並且ServantRetentionPolicy的值設爲NON_RETAIN

        CORBA::PolicyList policies (3);
        policies.length (3);

        // Assign the polices
        policies [0] =
          poa->create_id_assignment_policy (PortableServer::USER_ID);

        policies [1] =
          poa->create_request_processing_policy
                                (PortableServer::USE_SERVANT_MANAGER);

        policies [2] =
          poa->create_servant_retention_policy (PortableServer::NON_RETAIN);

        // Create the POA with these policies
        PortableServer::POA_var child_poa =
          poa->create_POA ("childPOA",
                           poa_manager.in (),
                           policies);

        // Destroy the policy objects
        for (CORBA::ULong i = 0; i != policies.length (); ++i) {
          policies[i]->destroy ();
        }

   

 把策略賦值後,根據這些策略創建了childPOA,因爲這些策略對象在create_POA中複製了一個拷貝,所以之後我們就不必再使用它們了,因此把它們刪除。 

由於我們擁有支持伺服管理器的POA,所以接下來爲服務定位器對象創建伺服,激活它以找回它的引用, 然後給childPOA設置伺服管理器。

       // Create a Stock_Factory_Locator servant
       Quoter_Stock_Factory_Locator_i servant_locator_i(orb.in ());
  
       // Need to activate a servant_manager object in the Root POA
       PortableServer::ServantLocator_var servant_locator =
         servant_locator_i._this ();
   
       // Set the SM with the childPOA
       child_poa->set_servant_manager (servant_locator.in ());
   

由於我們給childPOA設置了伺服管理器,接下來通過在childPOA中創建的用戶自創建ID 來創建引用,

這個childPOA使用了Quoter_Stock_Factory_Locator_icreate_reference_with_id 操作

讓我們創建了需要要的對象而無須實際的創建它的伺服。應用程序提供 ObjectID,這個對象標識在應用程序領域更具有語義。

       // Get the Object Id
       PortableServer::ObjectId_var child_oid =
         PortableServer::string_to_ObjectId ("childFoo");

       //Create the Object without creating the servants
       CORBA::Object_var stock_factory =
         child_poa->create_reference_with_id (child_oid.in (),
                                              "IDL:Quoter/Stock_Factory:1.0");
   


在這之後,和以前一樣,我們把對角引用轉爲IOR字符串,並打印出來。
   

       // Put the object reference as an IOR string
       CORBA::String_var ior = orb->object_to_string (stock_factory.in ());
      
       // Print it out!
       std::cout << ior.in () << std::endl;
   

練習

Modify the server.cpp in
      the simple server to use servant managers and locators. Use
      these files to help complete the implementation.
Stock_Factory_locator_i.h Stock_Factory_locator_i.cpp
Makefile.

解決方案

Look at the server.cpp file. It should not
            be much different from yours.

測試

A client which uses request handlers is provided:
client.cpp. As before the
      following files are provided.
Quoter.idl Stock_i.h Stock_i.cpp Stock_Factory_i.h
Stock_Factory_i.cpp Handler_i.h and
Handler_i.cpp.

More Reading

The

The Henning and
Vinoski
CORBA book
      discusses POA policies in detail.  Likewise, the Schmidt and Vinoski
columns
      in C++ Report also include several articles about the POA.  Finally,
      the TAO
      distribution includes
      examples that illustrate how to use the POA policies.

 

九:TAOCOS事件服務

TAOCOS事件服務

不斷地找回股票的價格而僅僅是爲了檢查其價格是否發生了改變這樣做並不是有效和可伸縮的方案。當股價改變時我們就可以得到通知的話那麼我們就可以執行恰當的動作。儘管我們可以自己設計回調機制,但解決這樣的問題使用CORBA事件服務就可以輕鬆搞定。

定義事件類型

我們需要定義一個IDL結構體來承載我們的事件數據。自然而然,我們需要在事件中包括股票的價格,符號和全稱。

  struct Event {
    double price;
    string symbol;
    string full_name;
  };

我們也可以對Stock接口作一些擴展,這樣修改股票的價格:

  interface Modify_Stock : Stock {
    void set_price (in double new_price);
  };

獲取股價的變化

作消費建立連接

作爲消費者建立連接是個相似的過程,但是我們將使用更爲傳統的基於繼承的方法代替TIE。先讓我們定義消費者對象:

class Stock_Consumer : public POA_CosEventComm::PushConsumer {
public:
  Stock_Consumer ();

  void push (const CORBA::Any& data);
  void disconnect_push_consumer void);

  // details omitted
};

 

當事件服務斷開時,它將調用disconnect_push_consumer()方法,比如,因爲它被關閉前,消費者可以獲得一個時機來斷開連 接。 無論什麼時候提供者發出某個事件,事件服務都會調用push()方法。讓我們查看一下這個方法,我們先得從任意類型 any)中抽取事件數據:

void
Stock_Consumer::push (const CORBA::Any& data)
{
  Quoter::Event *event;
  if ((data >>= event) == 0)
    return; // Invalid event

注意,這個抽取操作可能失敗:任意對象(any)可以存儲所有的IDL數據類型,並且僅在抽取時才檢查數據類型。同時再注意我們使用指向事件的指 針,CORBA規則是不定長的結構,也就是說,結構體包含不寬長的元素,比如字符串,通過引用被抽取出來。我們不需要管理這個內存,ORB將爲我們消費 它。現在我們可以把新的股票價格打印出來:

  std::cout << "The new price for one stock in /""
            << event->full_name.in ()
            << "/" (" << event->symbol.in ()
            << ") is " << event->price << std::endl;
}

回顧我們的示例,當事件通道斷開時我們也會接收到回調。在這一點上我們需要忘掉原始的連接:

void
Stock_Consumer::disconnect_push_consumer void)
{
  this->supplier_proxy_ = CosEventChannelAdmin::ProxyPushSupplier::_nil ();
}

 

但是爲什麼在最先的地方我們需要有一個與事件通道的連接?我們所需要的所有就是接收事件。與事件通道的連日接將使你能友好的關閉連接,於是事件通道不必爲舊的消費者維護資源。例如,我們可以實現的方法如下:

void
Stock_Consumer::disconnect ()
{
  // Do not receive any more events...
  this->supplier_proxy_->disconnect_push_supplier ();
}

怎樣連接到事件通道

連接到事件通道分三個步驟:第一步我們獲取被所有想要連接的消費者使用的工廠。第二步我們獲取提供者代理,於是當不需要任何更多事件的時候我們可以作出報告。最後一上我們連接到代理並開始接收事件。

我們假定我們使用了命名服務或與之類似的服務可以兒取引用給事件服務:

    CORBA::Object_var tmp = naming_context->resolve (name);
    CosEventChannelAdmin::EventChannel_var event_channel =
      CosEventChannelAdmin::EventChannel::_narrow (tmp);

 

現在我們使用事件通道來獲取爲消費者連接使用的工廠:

    CosEventChannelAdmin::ConsumerAdmin_var consumer_admin =
      event_channel->for_consumers ();

並命名用工廠獲取代理:

void
Stock_Consumer::connect (CosEventChannelAdmin::ConsumerAdmin_ptr consumer_admin)
{
    this->supplier_proxy_ =
      consumer_admin->obtain_push_supplier ();

And finally we connect:

    CosEventComm::PushConsumer_var myself = this->_this ();
    this->supplier_proxy_->connect_push_consumer (myself.in ());
}

通告股價的變化

現在我們將要檢查提供者如何生成事件的。讓我們查看Modify_Stock接口的實現:

class Quoter_Modify_Stock_i : public POA_Quoter::Modify_Stock {
public:
  Quoter_Modify_Stock_i (const char *symbol,
                         const char *full_name,
                         CORBA::Double price);

  void set_price (CORBA::Double new_price);

private:
  Quoter::Event data_;

  CosEventChannelAdmin::ProxyPushConsumer_var consumer_proxy_;
};

注意我們是如何用IDL結構來維護數據的。這僅是使代碼更小更短。 consumer_proxy_ 對象正像上面討論的supplier_proxy_ 對象,除了我們也用它來發送事件。set_price()方法的開始像下面這樣:

void
Quoter_Stock_i::set_price (CORBA::Double new_price)
{
   this->data_.price = new_price;

接下來我們準備事件。COS事件服務使用CORBAany來發送所有的數據,如下:

   CORBA::Any event;
   event <<= this->data_;

最後我們向消費者發送事件:

  this->consumer_proxy_->push (event);
}

作爲提供者連接到事件服務

發送事件已很容易。作爲提供者連到事件通道與作爲消費者連接非常相似。我們將需要CosEventComm::PushSupplier 對象。這是一個TIE對象的好的應用程序:

class Quoter_Stock_i : public POA_Quoter::Modify_Stock {
public:
  // some details removed...

  void disconnect_push_supplier (void);

private:
  POA_CosEventComm::PushSupplier_tie < Quoter_Stock_i > supplier_personality_;
};

PushSupplier_tie是由IDL編譯器生成的模板。它實現了CosEventComm::PushSupplier 接口,但它實際上僅僅是轉發所有的連接到它的單一模板參數。例如,在這個情況下disconnect_push_supplier 調用實現如下:

template void
POA_CosEventComm::PushSupplier_tie < T >::disconnect_push_supplier ()
{
  this->ptr_->disconnect_push_supplier ();
}

ptr_成員變量實際上是指向模板參數的指針,因此我們不必實現一個單獨的類僅用於接收斷開連接的回調,我們可以使用相同的Modify_Stock_i類來處理它。

回過一建立連接的代碼,首先我們獲得對事件服務的訪問,比如使用命名服務:

    CORBA::Object_var tmp = naming_context->resolve (name);
    CosEventChannelAdmin::EventChannel_var event_channel =
      CosEventChannelAdmin::EventChannel::_narrow (tmp);

現在我們用事件通道獲取被提供者連接使用的工廠:

    CosEventChannelAdmin::SupplierAdmin_var supplier_admin =
      event_channel->for_suppliers ();

並且命名用這個工廠獲得代理:

    this->consumer_proxy_ =
      supplier_admin->obtain_push_consumer ();

然後我們使用提供者個性化地與消費者代理建立連接:

    CosEventComm::PushSupplier_var supplier =
      this->supplier_personality_._this ();
    this->consumer_proxy_->connect_push_supplier (supplier);

最後我們實現斷開連接的回調:

void
Quoter_Stock_i::disconnect_push_supplier (void)
{
  // Forget about the consumer.  It is not there anymore
  this->consumer_proxy_ =
    CosEventChannelAdmin::ProxyPushConsumer::_nil ();
}

練習 1

實現接收股價更新事件的消費者。

已提供了頭文件 header file , 一起的還有client.cpp. 還提供了這些文件Quoter.idl, Makefile, Stock_i.h, Stock_i.cpp, Stock_Factory_i.h, Stock_Factory_i.cpp, server.cpp.

解決方案

用您的方案與Stock_Consumer.cpp比較。

測試

要測試您的變化您需要運行四個程序,先要運行TAO的命名服務:

$ $TAO_ROOT/orbsvcs/Naming_Service/Naming_Service -m 1

接下來是CORBA 事件服務

$ $TAO_ROOT/orbsvcs/CosEvent_Service/CosEvent_Service

再然後運行您的客戶端

$ client

最後運行服務端

$ server AAAA MSFT RHAT < stock_list.txt

這是 stock_list.txt file.

練習 2

向上面那樣配置,但是這次運行多個客戶端和服務端:
$ client
$ client
$ server AAAA BBBB < stock_list1.txt
$ server ZZZZ YYYY < stock_list2.txt

客戶端從兩個服務端都接收所有的事件嗎?如果您不想接收所有的事件將怎麼樣?舉例來說,因爲您只對特定的某些股票感興趣。

這是stock_list1.txt stock_list2.txt 文件。

不用多播的方式啓動命令服務和事件服務的方式:

start %TAO_ROOT%/orbsvcs/Naming_Service/Naming_Service -ORBEndpoint iiop://localhost:2809

start %TAO_ROOT%/orbsvcs/CosEvent_Service/CosEvent_Service -ORBInitRef NameService=corbaloc::localhost:2809/NameService

啓動客戶端的方式

start client  -ORBInitRef NameService=corbaloc::localhost:2809/NameService

啓動服務端的方式

start server AAAA BBBB < stock_list1.txt -ORBInitRef NameService=corbaloc::localhost:2809/NameService

 

 

十:TAO的實時事件服務

TAO的實時事件服務

們已探研瞭如何使用TAOCOS事件服務來接收更新過的股票的價格,但是如果我們並不關心所有的股票又怎麼樣呢?一個方法是使用多個事件通道,每個通道 承載不同的消息容量。例如,每個事件通道僅攜帶股票其中的一部分。在本節中,我們將探討另一個廣案,即使用TAO實事事件服務來爲我們執行過濾。TAO 實時事件服務可做許多其它事情,像保存具有優先級的點對點,使用多播來節省網絡資源,產生超時和間隔的事件,以及它可以與TAO的調度服務協作用於分析您 系統的可調度性。

取回股價的變化

在本例中,我們將會使用我們前一章用過的相同的數據結構,也就是事件將是相同的。TAO的實時事件服務可被配置用於以類型安全的方式攜帶事件,或你可以使用自定義的編碼來發送非在事件中非IDL結構體,但第一點它可以像COS 事件服務一樣簡單的使用。

作爲消費者連接

作爲消費者連接是很相似的。一些基類和簽名變化了,但它基本上具有相同的思想:先讓我們定義消費者對象:

class Stock_Consumer : public POA_RtecEventComm::PushConsumer {
public:
  Stock_Consumer ();

  void push (const RtecEventComm::EventSet& data);
  void disconnect_push_consumer (void);

  // details omitted
};

注意的是我們接收一個事件集合代替單個事件。事件通道可以使用這個特徵來把多個事件放入隊列和把它們放進一個單個操作。首先我們需要從any中抽取事件數據:

void
Stock_Consumer::push (const RtecEventComm::EventSet &data)
{
  for (CORBA::ULong i = 0; i != data.length (); ++i) {
    RtecEventComm::Event &e = data[i];
    Quoter::Event *event;
    if ((e.data.any_value >>= event) == 0)
      continue; // Invalid event

注意事件有多個結構,它們有區分明顯的事件頭和事件數據體,並且事件數據體比一個any還多。事件頭用於提供過濾,事件數據體字段可配置爲在編譯時攜帶無論哪種您想要的IDL結構體。現在我們可以輸出新的股票價格了:

  std::cout << "The new price for one stock in /""
            << event->full_name.in ()
            << "/" (" << event->symbol.in ()
            << ") is " << event->price << std::endl;
}

我們也必須實現斷開連接的回調:

void
Stock_Consumer::disconnect_push_consumer (void)
{
  this->supplier_proxy_ = CosEventChannelAdmin::ProxyPushSupplier::_nil ();
}

As with the COS Event Channel we can voluntarily disconnect, too:

COS 事件通道一樣我們也可以自願斷開連接。

void
Stock_Consumer::disconnect ()
{
  // Do not receive any more events...
  this->supplier_proxy_->disconnect_push_supplier ();
}

如何連接到實時事件通道

連接到實時事件通道與連接到正規的事件通道非常的相似。只有一點不同,就是我們必須指定爲想要接收的事件。這一點使用相當複雜的IDL結構來描述,但是TAO提供了一個幫助類來生產它。我們將假定我們使用命名服務或其它相似的服務來獲取一個事件服務的引用:

    CORBA::Object_var tmp = naming_context->resolve (name);
    RtecEventChannelAdmin::EventChannel_var event_channel =
      RtecEventChannelAdmin::EventChannel::_narrow (tmp);

現在我們用事件通道來獲取消費都連接使用的工廠:

    RtecEventChannelAdmin::ConsumerAdmin_var consumer_admin =
      event_channel->for_consumers ();

再用工廠找回代理:

void
Stock_Consumer::connect (RtecEventChannelAdmin::ConsumerAdmin_ptr consumer_admin)
{
    this->supplier_proxy_ =
      consumer_admin->obtain_push_supplier ();

現在我們列出我們想接收的事件。我們用簡單的算法來給事件類型賦給每支股票:

    CORBA::ULong rhat_event_type =
      (int('R') << 24) | (int('H') << 16) | (int('A') << 8) | int('T');
    CORBA::ULong aaaa_event_type =
      (int('A') << 24) | (int('A') << 16) | (int('A') << 8) | int('A');

然後我們創建訂閱:

    ACE_ConsumerQOS_Factory subscriptions;
    subscriptions.insert_type (rhat_event_type, 0);
    subscriptions.insert_type (aaaa_event_type, 0);

和連接到代理:

    RtecEventComm::PushConsumer_var myself = this->_this ();
    this->supplier_proxy_->connect_push_consumer (
        myself.in (),
        subscriptions.get_ConsumerQOS ());
}

通知股價的變化

As with the COS Event Channel example we will make our implementation of the Modify_Stock interface generate events whenever the price changes:

COS事件通道示例一樣無論什麼時候只要股價變化我們都將創建Modify_Stock接口的實現來創建事件。

class Quoter_Modify_Stock_i : public POA_Quoter::Modify_Stock {
public:
  Quoter_Modify_Stock_i (const char *symbol,
                         const char *full_name,
                         CORBA::Double price);

  void set_price (CORBA::Double new_price);

  void disconnect_push_supplier (void);

private:
  Quoter::Event data_;

  RtecEventChannelAdmin::ProxyPushConsumer_var consumer_proxy_;

  POA_RtecEventComm::PushSupplier_tie < Quoter_Stock_i > supplier_personality_;
};

set_price()方法的實現非常相似。第一我們存儲新的價格:

void
Quoter_Stock_i::set_price (CORBA::Double new_price)
{
   this->data_.price = new_price;

下一步我們準備事件。這時我們必須創建一個序列,但我們僅有在裏面有一個元素:

   RtecEventComm::EventSet event (1);
   event.length (1);

We set the event type based on the stock symbol:

   RtecEventComm::Event &e = event[0];
   const char *symbol = this->data_.symbol;
   e.header.type =
      ((int(symbol[0]) << 24)
       | (int(symbol[1]) << 16)
       | (int(symbol[2]) << 8)
       | int(symbol[3]));
  e.header.source = 1;

在本示例中沒有使用事件源,但它必須是非0的。現在我們可以設置事件數據體:

   e.data.any_value <<= this->data_;

and send the event to the event channel:

  this->consumer_proxy_->push (event);
}

作爲提供者連接到事件服務

作爲在COS事件通道的情形下,我們需要提一個供者特徵與它連接。我們獲得事件服務的訪問方法,例如使用命名服務:

    CORBA::Object_var tmp = naming_context->resolve (name);
    RtecEventChannelAdmin::EventChannel_var event_channel =
      RtecEventChannelAdmin::EventChannel::_narrow (tmp);

接下來我們用事件通到來獲得提供者連接使用的工廠:

    RtecEventChannelAdmin::SupplierAdmin_var supplier_admin =
      event_channel->for_suppliers ();

和用工廠獲得一個代理:

    this->consumer_proxy_ =
      supplier_admin->obtain_push_consumer ();

我們構建發佈器因此事件通可以在基於他們通用的事件之的消費者和提供者之間進行匹配:

   const char *symbol = this->data_.symbol;
   CORBA::ULong type =
      ((int(symbol[0]) << 24)
       | (int(symbol[1]) << 16)
       | (int(symbol[2]) << 8)
       | int(symbol[3]));
   CORBA::ULong source = 1;
   ACE_SupplierQOS_Factory publications;
   publications.insert_type (type, source, 0, 1);

最後我們連接到消費者代理上:

    RtecEventComm::PushSupplier_var supplier =
      this->supplier_personality_._this ();
    this->consumer_proxy_->connect_push_supplier (supplier);

最後我們實現斷開連接的回調:

void
Quoter_Stock_i::disconnect_push_supplier (void)
{
  // Forget about the consumer it is not there anymore
  this->consumer_proxy_ =
    RtecEventChannelAdmin::ProxyPushConsumer::_nil ();
}

練習1

實現接收股價更新事件的消費者,

已提供了頭文件 header file , 一起的還有client.cpp. 還提供了這些文件Quoter.idl, Makefile, Stock_i.h, Stock_i.cpp, Stock_Factory_i.h, Stock_Factory_i.cpp, server.cpp.

解決方案

用您的方案與 Stock_Consumer.cpp比較。

測試

要測試您的變化您需要運行三個程序,先要運行TAO的命名服務:

$ $TAO_ROOT/orbsvcs/Naming_Service/Naming_Service

然後運行TAO的實時事件服務

$ $TAO_ROOT/orbsvcs/Event_Service/Event_Service

再運行您的客戶端:

$ client AAAA CCCC

最後運行服務端:

$ server MSFT BBBB CCCC < stock_list.txt

Here is the stock_list.txt file.

Exercise 2

向上面那樣配置,但是這次運行多個客戶端和服務端:

$ client AAAA MSFT
$ client PPPP
$ server AAAA < stock_list1.txt
$ server QQQQ < stock_list2.txt

客戶端能接收來來自兩個服務端的所有事件嗎?

這是 stock_list1.txt stock_list2.txt 文件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章