跳轉到內容

ACE+TAO 開源程式設計筆記/一個更有用的客戶端和伺服器示例

來自華夏公益教科書

在之前的客戶端伺服器示例中,編輯了 4 個檔案來演示一個支援 CORBA 的應用程式的基本示例,該應用程式以工廠型別的介面展示了一個 RMI 介面。這並不太有趣,也不太有用。理想情況下,這些應用程式之一將充當服務端,提供網路物件/實體,這些物件/實體可以回答問題並執行任務。它也應該能夠透過資料庫後端來執行這些任務。這種服務的目的是實現一個記憶體資料庫,其成員可以獨立地像例項化的物件一樣行動。

CORBA 提供了一個框架來掛載稱為可移植物件介面卡 (POA) 的網路物件。這個框架,而且它是一個相當複雜的框架,能夠儲存對你的物件的引用,執行你的物件的引用計數,以及對已使用的物件進行垃圾回收。對於這個示例,我們只使用 POA 來儲存對工廠物件的引用。POA 提供的其他服務,我們現在先忽略,並將自己執行物件生命週期操作。在以後的示例中,我們將探索使用更多 POA 功能的實用性,但是,由於這些功能不是由 IDL 編譯器生成的,並且需要了解你將使用的特定 ORB(在本例中為 TAO),因此我們將首先使用這個更簡單的示例,並在以後擴充套件它。

這個示例最初被用於證明某些效率低下的程式碼的效能改進,這些程式碼最初是用 Java 編寫的並在 Tomcat 例項下執行。我的工作是讓客戶從他們的伺服器中獲得更多價值,並且順便解決他們在 Tomcat 中遇到的垃圾回收問題。我的解決方案是將他們現有的瞬時記憶體實體從 Tomcat 中分離出來,並放到一個記憶體 CORBA 伺服器中。你將看到的伺服器當然不是最有效的設計,但是,它旨在精確地模擬客戶的設計。這樣做是為了以客戶能夠識別的方式證明 Java 設計人員可以實現的效能改進。在最初的演示和第一波尷尬之後,添加了後續改進以進一步完善可用的功能。

這個客戶的問題是,客戶框架消耗了過多的資源,在 GC 操作生效時會凍結,而且總體上沒有達到他們的預期擴充套件性。為了證明他們可以實現的改進,我到網上下載了一個免費的、隨機生成的地址/信用卡資料庫。然後,我使用這個資料庫來填充一個基於 Boost 的 Multi-Index 容器的記憶體 DBMS,然後使用 CORBA 工廠慣用法將這些物件提供給客戶端。當客戶端完成對這些物件的處理時,他們會呼叫一個 destroy 方法來銷燬這些物件,而伺服器的 POA 會清理它們。這很容易理解,但不是最快的設計。

原始檔 Actor.idl

[編輯 | 編輯原始碼]
// Forward declare
interface Actor;
struct Person{
  unsigned long id;
  string fname;
  string mname;
  string lname;
  unsigned long address;
};

// = TITLE
//   A factory class for Actor interfaces
interface My_Factory
{
  typedef unsigned long uint;
  typedef sequence<uint> People;
  //Get an interface to an actor via its ID
  Actor get_actor (in uint actor_id);
  //Get one or more People by searching on the first name. Returns a sequence of actor_ids.
  People get_sloppy_match_fname(in string fname);
  //Get one or more People by searching on the first and last names, returns a sequence of actor_ids.
  People get_sloppy_match_flname(in string fname, in string lname);
  uint add_actor(in Person person); //Add a person to the in-memory db and the backing store, return person_id
  uint delete_actor(in uint actor_id); //Delete from in-memory and backing store, associated addresses will likely be cascaded on the backend.
};

interface Actor
{
  readonly attribute Person person;
  typedef unsigned long uint;
  typedef sequence<uint> Addresses;
  uint get_identity(); 
  string get_fname();
  string get_mname();
  string get_lname();
  Addresses get_addresses();
  void setPersonId(in uint ps);
  void destroy();
};

現在,執行你的 IDL 透過編譯器來獲得你生成的機器程式碼。

tao_idl -GI Actor.idl

這會生成一堆檔案,我們第一個要編輯的是 ActorI.h 檔案。這裡儲存了 IDL 的介面。由於我們在工廠類中需要一個數據庫連線來執行一些操作,所以我們的第一個編輯是將一個數據庫連線物件新增到 My_Factory_i 類中。只需將其放在頂部附近,我們將在工廠啟動時對其進行初始化。在這個示例中,我使用 PostgreSQL 作為資料庫。下面列出了我進行的程式碼編輯的示例。關於這個檔案就這些了。

原始檔 ActorI.h

[編輯 | 編輯原始碼]
/*! \class My_Factory_i
*   \brief Factory class for creating instances of Actor and Location
*
 *   Code generated by The ACE ORB (TAO) IDL Compiler v1.8.2
*/
class  My_Factory_i
  : public virtual POA_My_Factory
{
public:
  /*! \var pqxx::connection *c
  *   \brief Postgres Connectin variable
  *
  *   This variable gets used throughout the class for the duration of its life
  *   as the variable responsible for enabling writes back to the database
  */
  pqxx::connection *c;
...

現在開始 ActorI.cpp 檔案。實現只是填寫 IDL 生成的框架。這個原始碼需要建立一個活動資料庫連線,連線到多索引記憶體資料庫,執行搜尋,並將某些內容寫入資料庫。最難弄清楚的事情是如何管理建立的 CORBA 物件。來自開源支援社群的文件非常少,而且一般來說對於開發來說不是很有用。但是,OCI 有一本出售的書可以幫助提供對開發很有用的示例。這個示例的靈感來自 Henning 關於使用 CORBA 與 STL 容器結合的書。所以,以下是介面檔案。

原始檔 Actor.cpp

[編輯 | 編輯原始碼]
#include "ActorI.h"

// Implementation skeleton constructor
My_Factory_i::My_Factory_i (void)
{
  c = new pqxx::connection("dbname=ecarew user=ecarew");
}

// Implementation skeleton destructor
My_Factory_i::~My_Factory_i (void)
{
}

::Actor_ptr My_Factory_i::get_actor (
  ::My_Factory::uint actor_id)
{
  Actor_i *actorInterface = new ::Actor_i();
  PortableServer::ServantBase_var actor_safe (actorInterface);	// NEW
  actorInterface->setPersonId(actor_id);
  return actorInterface->_this();
}

::My_Factory::People * My_Factory_i::get_sloppy_match_fname (
  const char * fname)
{
  pqxx::work w(*c);
  std::string sql = "SELECT id from persons where fname = '";
  sql += fname;
  sql += "'";
  pqxx::result r = w.exec(sql);
  ::My_Factory::People* Pseq = new ::My_Factory::People(r.size());
  Pseq->length(r.size());
  int rnum = 0;
  for(pqxx::result::const_iterator row = r.begin(); row != r.end(); ++row){
    int id = 0;
    row[0].to(id);
    (*Pseq)[rnum++] = id;
  }
  return Pseq;
}

::My_Factory::People * My_Factory_i::get_sloppy_match_flname (
  const char * fname,
  const char * lname)
{
  pqxx::work w(*c);
  std::string sql = "SELECT id from persons where fname = '";
  sql += fname;
  sql += "' and lname = '";
  sql += lname;
  sql += "'";
  pqxx::result r = w.exec(sql);
  ::My_Factory::People*  Pseq = new ::My_Factory::People(r.size());
  Pseq->length(r.size());
  int rnum = 0;
  for(pqxx::result::const_iterator row = r.begin(); row != r.end(); ++row){
    int id = 0;
    row[0].to(id);
    (*Pseq)[rnum++] = id;
  }
  return Pseq;
}

::My_Factory::uint My_Factory_i::add_actor (
  const ::Person & person)
{
  _person iTarget(pers.size() - 1, std::string(person.fname), std::string(person.mname), std::string(person.lname), 0);
  pers.insert(iTarget);
  return pers.size();
}

::My_Factory::uint My_Factory_i::delete_actor (
  ::My_Factory::uint actor_id)
{
  person_set::nth_index<0>::type& person_index = pers.get<0>();
  person_index.erase(_person(actor_id, "", "", "", 0));
  pers.insert(_person(actor_id, "", "", "", 0));
  return actor_id;
}

// Implementation skeleton constructor
Actor_i::Actor_i (void)
{
  ps = new ::Person;
}

// Implementation skeleton destructor
Actor_i::~Actor_i (void)
{
}

::Person * Actor_i::person (void)
{
  return ps._retn();
}

::Actor::uint Actor_i::get_identity (void)
{
  return ps->id;
}

char * Actor_i::get_fname (void)
{
  return CORBA::string_dup(ps->fname);
}

char * Actor_i::get_mname (void)
{
  return CORBA::string_dup(ps->mname);
}

char * Actor_i::get_lname (void)
{
  return CORBA::string_dup(ps->lname);
}

::Actor::Addresses * Actor_i::get_addresses (void)
{
  // Add your implementation here
}

void Actor_i::setPersonId (
  ::Actor::uint ps)
{
  person_set::nth_index<0>::type::iterator 
      it=pers.get<0>().find(_person((unsigned int)pss, "", "", "", 0));
  ps->id = (*it).id;
  ps->fname = (*it).fname.c_str();
  ps->mname = (*it).mi.c_str();
  ps->lname = (*it).lname.c_str();
  ps->address = (*it).address;
}

void Actor_i::destroy (void)
{
  // Get the id of this servant and deactivate it from the default POA.
  PortableServer::POA_var poa = this->_default_POA();
  PortableServer::ObjectId_var oid = poa->servant_to_id(this);
  poa->deactivate_object(oid);
}

我們來看一下程式碼。工廠的建構函式建立了 postgres 連線物件。工廠解構函式什麼也不做。工廠的 get_actor 成員負責例項化一個新的 Actor_i 介面物件。弄清楚如何編寫這段程式碼對我來說是一個相當大的挑戰。因為我沒有 OCI 文件 的副本,所以我只能使用更通用的文件,比如 Henning 的書。雖然這些書會讓你瞭解如何架構某些東西,但具體的實現完全取決於你作為開發人員。上面 getter 中顯示的這段程式碼的問題在於,它在建立物件時會對該物件進行額外引用計數。我找不到任何可以遞減它計數的方法,因此其效果是在我們在客戶端完成對物件的處理後,它會遞減一次,但不足以使物件被垃圾回收。為了解決這個問題,建立的物件有一個內建的解構函式,我們將在後面討論。關於工廠目前就這些了。

actor 類使用 Person 物件訪問記憶體資料庫。你可以看到它是在 Actor_i 的建構函式中建立的。

原始檔 ActorI.h

[編輯 | 編輯原始碼]
class  Actor_i
  : public virtual POA_Actor
{
public:
  Person_var ps;
  // Constructor

應用程式

[編輯 | 編輯原始碼]

記憶體資料庫在標頭檔案 person.h 中定義,並且如前所述,是基於 Boost 的 multi-index 類。這允許設計人員建立一個可以包含多個索引的物件容器。該檔案如下所示

原始檔 person.h

[編輯 | 編輯原始碼]
/***************************************************************************
 *   Copyright (C) 2010 by Evan Carew   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
#ifndef PERSON_H
#define PERSON_H
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/member.hpp>
struct _person
{
  int         id;
  std::string fname;
  std::string mi;
  std::string lname;
  int address;

  _person(int id,const std::string& fname, const std::string mi, const std::string lname, int address):id(id),fname(fname), mi(mi), lname(lname), address(address){}
  
  _person(const _person&p){id = p.id; fname=p.fname; mi=p.mi; lname=p.lname; address=p.address;}

  bool operator<(const _person& p)const{return id<p.id;}
};

// define a multiply indexed set with indices by id and name
typedef boost::multi_index_container< _person, boost::multi_index::indexed_by<    // sort by employee::operator<
    boost::multi_index::ordered_unique<boost::multi_index::identity<_person> >,
          // sort by less<string> on name
    boost::multi_index::ordered_non_unique<boost::multi_index::member<_person,std::string,&_person::lname> >
        > > person_set;

extern person_set pers;
#endif

如果你檢視 ActorI.cpp,你會在 void Actor_i::setPersonId (::Actor::uint ps) 中看到如何使用多索引物件來搜尋資料。為了瞭解這段程式碼是如何工作的,以及它與 Boost 容器的關係,請檢視他們關於 Boost::Multi-index 庫的文件。

現在,開始伺服器程式碼。這段程式碼非常簡單,主要是由樣板程式碼驅動的。IDL 編譯器不會生成這些程式碼,儘管它可能可以生成。在本例中,伺服器建立了一個 ORB,連線到命名服務,並從 postgreSQL 資料庫初始化記憶體資料庫。

原始檔 objectserver.cpp

[編輯 | 編輯原始碼]
/***************************************************************************
 *   Copyright (C) 2010 by Evan Carew   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <cstdlib>
#include <string>
#include <pqxx/pqxx>
#include <ace/streams.h>
#include <tao/PortableServer/PortableServer.h>
#include <orbsvcs/CosNamingC.h>
#include "ActorI.h"
#include "ActorS.h"
#include "person.h"

using namespace std;
using namespace pqxx;
using namespace ::boost;
using namespace ::boost::multi_index;
using namespace CORBA;

person_set pers;

int main(int argc, char *argv[])
{
  connection c("dbname=ecarew user=ecarew");
  work w(c);
  int id, address;
  string fname, lname, mi;
  
  extern person_set pers;
  
  result r = w.exec("SELECT id, fname, mi, lanem, addr from persons");
  for(pqxx::result::const_iterator row = r.begin(); row != r.end(); ++row){
    row[0].to(id);
    row[1].to(fname);
    row[2].to(mi);
    row[3].to(lname);
    row[4].to(address);
    pers.insert(_person(id, fname, mi, lname, address));
  }
  w.commit();
  
  // First initialize the ORB, that will remove some arguments...
  ORB_var orb = ORB_init (argc, argv);

  // Get a reference to the RootPOA
  Object_var poa_object = orb->resolve_initial_references ("RootPOA");
  // narrow down to the correct reference
  PortableServer::POA_var poa = PortableServer::POA::_narrow (poa_object.in ());
  // Set a POA Manager
  PortableServer::POAManager_var poa_manager = poa->the_POAManager ();
  // Activate the POA Manager
  poa_manager->activate ();
// Create the servant
  My_Factory_i my_factory_i;
 
  // Activate it to obtain the object reference
  My_Factory_var my_factory = my_factory_i._this ();
 
  // Put the object reference as an IOR string
  char* ior = orb->object_to_string (my_factory.in ());
  //Now we need a reference to the naming service
  Object_var naming_context_object = orb->resolve_initial_references ("NameService");
  CosNaming::NamingContext_var naming_context = CosNaming::NamingContext::_narrow (naming_context_object.in ());
  //Then initialize a naming context
  CosNaming::Name name (1);
  name.length (1);
  //store the name in the key field...
  name[0].id = string_dup ("Actors");
  //Then register the context with the nameing service
  naming_context->rebind (name, my_factory.in ());
  //By default, the following doesn't return unless there is an error
  orb->run ();
 
  // Destroy the POA, waiting until the destruction terminates
  poa->destroy (1, 1);
  orb->destroy ();

  return EXIT_SUCCESS;
}

現在我們已經討論了構建伺服器所需的所有程式碼,值得注意的是該應用程式中的一些部分使其成為一個有用的示例。例如,請注意工廠和實體類 Actor 中的一些方法返回複雜型別。我發現很少有(如果有的話)線上示例討論如何做到這一點。許多其他示例沒有展示這一點的主要原因是,這是一個相當深入的主題,需要開發人員首先了解 CORBA 規範如何處理其獨特的記憶體管理方式。我不會在這裡詳細介紹所有選項,只是說我所參與的大多數應用程式要麼使用整型,在這種情況下沒有問題,要麼使用 CORBA 容器(如 sequencestring 和奇怪的 struct)的簡單用法。前面描述的程式碼使用所有這些容器並展示瞭如何處理記憶體。關鍵在於從 IDL 編譯器生成的自動變數名的名稱。以 _var 結尾的名稱是 CORBA 對 STL::auto_ptr 的概念。這意味著它們擁有所指向的物件,並且像典型的 stl 容器一樣,當它們超出作用域時,它們會清理自己的記憶體以及客戶端和伺服器 ORB 中包含的記憶體。字串值得特別提及,因為它們似乎有自己獨立的處理方式。請注意,在程式碼中,CORBA 使用 string_dup() 函式,而不是使用直接賦值、new 或其他特殊運算子。不要去想它,直接做就行了。這是規範的要求。至於 sequence 和 struct,使用 _var 變數就足夠了。

客戶端

[編輯 | 編輯原始碼]

大多數客戶端程式碼都是模板程式碼,即 CORBA 連線的設定。對 CORBA 物件的實際使用看起來就像函式呼叫一樣。如上所述,唯一需要注意的是三種物件型別的使用:struct、sequence 和 string。檢視下面的示例客戶端,瞭解其工作原理。

原始碼 objectclient.cpp

[編輯 | 編輯原始碼]
/***************************************************************************
 *   Copyright (C) 2010 by Evan Carew                                      *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <cstdlib>
#include <string>
#include <ace/streams.h>
#include <tao/PortableServer/PortableServer.h>
#include <orbsvcs/CosNamingC.h>
#include <sys/time.h>
#include "ActorI.h"
#include "ActorC.h"
#include "person.h"

using namespace std;
using namespace ::boost;
using namespace CORBA;
person_set pers;

int main(int argc, char **argv){
  struct itimerval t_new, t_old, t_result;
  // First initialize the ORB, that will remove some arguments...
  ORB_var orb = ORB_init (argc, argv);

  // Obtain Naming Service reference.
  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 = string_dup ("Actors");

  Object_var factory_object =  naming_context->resolve (name);

  // Now downcast the object reference to the appropriate type
  My_Factory_var factory = My_Factory::_narrow (factory_object.in ());
  t_new.it_interval.tv_sec = 1;
  t_new.it_interval.tv_usec = 0;
  t_new.it_value.tv_sec = 1;
  t_new.it_value.tv_usec = 0;
  setitimer(ITIMER_PROF, &t_new, &t_old);
  // Get the person object
  for(int i = 1; i < 1000; i++){
    Actor_var actor = factory->get_actor (i);
    string actor_fname = actor->get_fname();
    //cout << actor_fname << endl;
    actor->destroy();
  }
  getitimer(ITIMER_PROF, &t_result);
  
  orb->shutdown(1);
  orb->destroy();
  //cout <<  1000000 - t_result.it_value.tv_usec << " microseconds"
  //    << endl;
 
  return 0;
}

部署應用程式

[編輯 | 編輯原始碼]

TAO 網站在幫助 ACE+TAO 新手完成編譯其新應用程式的任務方面做得不錯。儘管有這些說明,但他們的郵件列表中經常會有一些關於應用程式無法編譯或連結的問題。使用 5.8.x 或更高版本的 ACE+TAO 時,您需要的編譯命令可能需要以下本地物件:

ActorC.cpp ActorI.cpp ActorS.cpp objectserver.cpp

應用程式的連結部分可能需要以下物件:

-lpqxx -lTAO_PortableServer -lTAO_CosNaming -lTAO_AnyTypeCode -lACE -lTAO

直到最近,ACE+TAO 都沒有一個值得一提的部署計劃。我不確定他們是在哪個版本中獲得了針對其平臺的 make install 目標,可以肯定地說,您需要使用他們更新的版本,例如 5.8.x 及更高版本。如果您使用的是 Red Hat 風格的 Linux 發行版,他們甚至提供預編譯的 RPM 包。安裝 ACE+TAO 將把庫放在您的系統通用目錄中,並將 tao 二進位制檔案放在一些通用的 bin 位置,例如 /usr/bin。完成此先決條件後,安裝您的應用程式非常簡單。只需將您的二進位制檔案複製到您可以執行它的目錄中。還有一點,請記住,您可能需要在網路上的某個位置執行名稱伺服器副本,並將其配置為在啟動時載入。

華夏公益教科書