跳转到内容

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。完成此先决条件后,安装您的应用程序非常简单。只需将您的二进制文件复制到您可以运行它的目录中。还有一点,请记住,您可能需要在网络上的某个位置运行名称服务器副本,并将其配置为在启动时加载。

华夏公益教科书