当前位置: 首页 > 工具软件 > ACE > 使用案例 >

ACE编程

公孙向荣
2023-12-01

ACE编程

ACE历时20年,耗费数千万美金,在中间件领域享有盛誉。更可贵的是,ACE彻底的开源,且没有任何限制条件,完全是共产主义的精神,让人钦佩。

ACEWrapper Facade模式解决了夸平台的c++编程问题。虽然我们现在有标准C++,但是目前的C++标准库对于多线程、网络通信、并发处理、进程管理以及小内存分配、共享内存、内存映射文件等诸多实际运用中的问题没有支持。相信随着时间的推移,C++标准库会越来越多的关心我们程序员实际编程中的问题,但是现在,我需要ACE.

ACE的文档在http://www.dre.vanderbilt.edu/Doxygen/Stable/ace/index.html

,你可以从www.riverace.com下载最新的ACE安装包。

UBuntu7.10下安装ACE开发环境

首先要确保UBuntu已经安装了g++和openssl。
apt-get install g++来安装g++
通过新立得软件查找libssl来安装。我安装的是libssl0.9.8,libssl0.9.8-dbg,libssl-dev。
然 后将ACE压缩包解压,并放到自己的目录下,仔细阅读install.html中关于unix的部分,按照上面的做,我没有使用传统方式。如果执行 configure命令或者make命令时报错,通常是缺少相应的库。安装指定的库以后,一定要重新执行../configure命令。
目录/build/ace/.libs下的libACE-5.6.so文件就是我们的动态库文件。


使用ACE编写第一个程序

代码编写需要了解ACE的一些基本知识,所有的ace头文件都在ace目录下,我的机器上ace目录安装在/software/ACE_wrappers

下面的代码并不复杂,主要是编译参数要注意:

g++ -I$ACE_ROOT -L$ACE_ROOT/ace test.cpp -lACE

-l后面指定头文件目录/software/ACE_wrappers

-L后面指定ACE库文件路径/software/ACE_wrappers/ace

-l后面指定库文件名称ACE



为了确保动态连接库运行时加载正确,需要如下设置:

Fedora7的配置方法--在动态库的配置文件子目录(/etc/ld.so.conf.d)里增加一个文件ace.conf

内容为:/software/ACE_wrappers/ace 这是动态链接库的目录路径,执行ldconfig命令修改全局缓冲文件


如果别的linux系统没有/etc/ld.so.conf.d目录,就需要直接将/software/ACE_wrappers/ace加到/etc/ld.so.conf文件中,然后执行ldconfig命令





#include "ace/Log_Msg.h"

#include "ace/OS_main.h"

#include "ace/INET_Addr.h"

#include "ace/SOCK_Connector.h"

#include "ace/SOCK_Stream.h"


int ACE_TMAIN(int argc, ACE_TCHAR* argv[])

{

ACE_DEBUG((LM_DEBUG,ACE_TEXT("freebird./n")));

ACE_SOCK_Connector connector;

ACE_SOCK_Stream peer;

ACE_INET_Addr peer_addr;

if(peer_addr.set(80,"192.168.22.26")==-1)

return 1;

else if(connector.connect(peer,peer_addr)==-1)

return 1;

}


ACE开发网络通信程序

该程序将演示如何将一个简单结构序列化后发送到网络上,如何从网络上接收到数据后反序列化回结构。

ACEC++ WRAPPER FACADE层将网络通信分成三种角色:连接者(ACE_SOCK_Connector)、等待者(ACE_SOCK_Acceptor)和传输者(ACE_SOCK_Stream)


建立连接

首先使用ACE_SOCK_Connector::connect连接某个服务器(使用ip地址和端口号),该服务器上使用ACE_SOCK_Acceptor::accept等待外部的连接请求。

ACE_INET_Addr类进行管理SOCKET通信使用的IP地址和端口号。

当连接建立的时候,连接者和等待者都初始化一个传输者用于通信。

下面就是连接者如何连接本机的7777端口的服务程序代码:


#include <iostream>

using namespace std;

#include "ace/INET_Addr.h"

#include "ace/SOCK_Stream.h"

#include "ace/SOCK_Connector.h"

int main(void)

{

ACE_INET_Addr address("127.0.0.1:7777");

ACE_SOCK_Connector connector;

ACE_SOCK_Stream stream;

if(connector.connect(stream,address)==-1)

{

cout<<strerror(errno)<<endl;

}

}

如果连接成功,connect方法返回0,如果连接失败,返回-1,线程专有的errno变量将被设置对应的错误码,你可以通过strerror函数获取错误信息描述字符串。ACE不使用异常报错,原因之一是早些时候异常并不被所有的C++编译器支持,原因之二是异常对性能仍然有影响,作为高性能底层库ACE仍然采用了C风格进行错误处理。但是你仍然可以在自己的应用逻辑中使用异常,并不会和ACE发生冲突。

下面是等待者的示例:

#include <iostream>

using namespace std;

#include "ace/INET_Addr.h"

#include "ace/SOCK_Stream.h"

#include "ace/SOCK_Acceptor.h"

int main(void)

{

ACE_SOCK_Acceptor acceptor;

//本地端口7777ACE_INET_Addr对象

ACE_INET_Addr address;

address.set(7777);

//绑定本地端口,并且设置为监听状态

if(acceptor.open(address)==-1)

{

cout<<strerror(errno)<<endl;

}

ACE_SOCK_Stream stream;

if(acceptor.accept(stream)==-1)

{

cout<<strerror(errno)<<endl;

}

}


注意,ACE_SOCK_Acceptor::acceptACE_SOCK_Connector::connect方法都可以接收一个ACE_TIME_Value*参数。该参数缺省直为NULL,就像上面的两个示例,表示除非建立连接,否则不会返回;如果我们创建ACE_TIME_Value time(0,0)对象作为参数,则表示方法不会阻塞,如果不能立刻建立连接,就返回-1,并且errnoEWOULDBLOCK;如果我们创建ACE_TIME_Value time(5,0)对象作为参数,就表示方法会最多等待5秒钟,如果5秒钟内还没有建立连接,就返回-1,并且errnoETIME.

ACE_SOCK_Acceptor对象没有状态,因此多线程可以在不锁定的情况下共享该对象。

数据传输

通常数据传输的过程是将对象中的数据按照某种格式序列化成连续的字节流,然后发送到网络上,当另一端接收到字节流后,按照此格式反序列化成对象。

当连接建立好后,通信双方都有两个可以发送和接收数据的ACE_SOCK_Stream对象。该对象提供了发送和接收的方法。send_n/recv_n用于发送和接收确定数量的字节流,如果没有发送或者接收完,该方法将阻塞。而send/recv就不保证这一点,可能实际发送或者接收的数据比参数指定的少,该方法不会阻塞,而是返回实际发送或者接收的数据大小。send/recv方法实际是从父类ACE_SOCK_IO继承而来的。

网络传输的一种高效的方法是集中写和分散读。不同缓冲区的数据没有必要拷贝到一起,就可以直接按照次序一次型的发送出去。从网络另一端收到后,有可以分散的写到不同的缓冲区中。这就避免了数据复制的开销。ACE_SOCK_Stream的方法recvv_n/sendv_n方法就提供了这个机制。我们后面的示例将严实这个方法的使用。

如果我们使用TCP/IP协议发送数据,TCP/IP协议有一个Nagle算法。该算法将缓存小数据,减少网络发送的次数,从而避免过多通信的开销。在某些情况下,我们需要关闭该算法,让我们的数据能够立刻发送出去。ACE_SOCK_Streamset_option方法使用参数TCP_NODELAY可以关闭这个算法。另一个方法是当我们使用sendv_n方法时,也会强制数据立刻发送。

下面的示例将一个结构SHMRecord初始化,并序列化到ACE_OutputCDR对象中。然后使用sendv_n方法将数据发出。


#include <iostream>

using namespace std;

#include "ace/INET_Addr.h"

#include "ace/SOCK_Stream.h"

#include "ace/SOCK_Connector.h"

#include "ace/CDR_Stream.h"


class SHMRecord

{

public:

SHMRecord():pData_(NULL){}

ACE_UINT16 type_;

ACE_UINT32 offset_;

void* pData_;

ACE_UINT32 dataLength_;


size_t size() const

{

return 2+4+4+dataLength_;

}

~SHMRecord()

{

if(pData_!=NULL)

delete[] static_cast<char*>(pData_);

}

};


int operator<<(ACE_OutputCDR & cdr,SHMRecord const& record)

{

cdr<<record.type_;

cdr<<record.offset_;

cdr<<record.dataLength_;

cdr.write_char_array(static_cast<char*>(record.pData_),record.dataLength_);

return cdr.good_bit();

}


int operator>>(ACE_InputCDR & cdr,SHMRecord & record)

{

cdr>>record.type_;

cdr>>record.offset_;

cdr>>record.dataLength_;

record.pData_=new char[record.dataLength_]();

cdr.read_char_array(static_cast<char*>(record.pData_),record.dataLength_);

return cdr.good_bit();

}




int main(void)

{

ACE_INET_Addr address("127.0.0.1:7777");

ACE_SOCK_Connector connector;

ACE_SOCK_Stream stream;

if(connector.connect(stream,address)==-1)

{

cout<<strerror(errno)<<endl;

}


SHMRecord record;

record.type_=1;

record.offset_=2;

record.pData_=new char[4]();

record.dataLength_=4;

strcpy(static_cast<char*>(record.pData_),"hih");


const size_t size=record.size()+ACE_CDR::MAX_ALIGNMENT;

ACE_OutputCDR payload(size);

payload<<record;


//create cdr header for this data

ACE_OutputCDR header(ACE_CDR::MAX_ALIGNMENT+8);

header<<ACE_OutputCDR::from_boolean(ACE_CDR_BYTE_ORDER);

header<<ACE_CDR::ULong(size);

iovec iov[2];

iov[0].iov_base=header.begin()->rd_ptr();

iov[0].iov_len=8;

iov[1].iov_base=payload.begin()->rd_ptr();

iov[1].iov_len=size;


stream.sendv_n(iov,2);

cout<<record.type_<<endl;

cout<<record.offset_<<endl;

cout<<static_cast<char*>(record.pData_)<<endl;

cout<<record.dataLength_<<endl;

}

ACE提供了ACE_OutputCDRACE_InputCDR类,是针对网络程序经常遇到的将对象数据序列化到字节流和从字节流中反序列化到对象的情况。你可以提供自己的operator<<operator>>操作,就像上面的例子一样。

这种方式和支持标准C++流的方式是一样的。那么,为什么不直接使用标准C++流呢?因为ACE所支持的平台很多,有些编译器不支持标准C++流。并且据我个人的体验,标准C++流在内存管理上是封装的,你不可能通过公有方法获得内部关里的缓冲区的指针,除非自己定义自己的派生类,这并不容易。还有一个原因是不同编译器和不同的硬件使用了不同的字节对齐方式(大尾数法和小尾数法)。使用ACEcdr类就可以保证各种环境下都能使用,因为它在内部使用了CORBA公共数据表示的格式。

对于基本的数值类型,各个平台也有可能有长度的差异,比如int究竟是1632还是64。所以这里使用了ACE提供的基本数值类型,比如ACE_UINT32

在这个示例程序里,我们实际上创建了两个ACE_OutputCDR对象,一个用来表示数据头,一个存发实际结构中的数据。数据头中前4个字节存放了一个布尔值,表示本机的字节顺序,后面四个字节表示第二个对象的实际长度。

因此,接收数据时首先接收固定长度的头对象,取得字节顺序标志后,调整字节顺序,然后获取实际长度,根据该长度接收第二个ACE_OutputCDR对象存放的实际数据。

下面的例子演示了如何接收发送来的数据。



int main(void)

{

ACE_SOCK_Acceptor acceptor;

ACE_INET_Addr address;

address.set(7777);

if(acceptor.open(address)==-1)

{

cout<<strerror(errno)<<endl;

}

ACE_SOCK_Stream stream;

if(acceptor.accept(stream)==-1)

{

cout<<strerror(errno)<<endl;

}


auto_ptr<ACE_Message_Block>

spBlock(new ACE_Message_Block(ACE_DEFAULT_CDR_BUFSIZE));

ACE_CDR::mb_align(spBlock.get());

if(stream.recv_n(spBlock->wr_ptr(),8)==8)//receive the header of CDR

{

//parse the cdr header

spBlock->wr_ptr(8);

ACE_InputCDR cdr(spBlock.get());

ACE_CDR::Boolean byte_order;

cdr>>ACE_InputCDR::to_boolean(byte_order);

cdr.reset_byte_order(byte_order);

ACE_CDR::ULong length;

cdr>>length;

//receive the data from master

spBlock->size(length+8+ACE_CDR::MAX_ALIGNMENT);

if(stream.recv_n(spBlock->wr_ptr(),length)==length)

{

spBlock->wr_ptr(length);

//必须重新创建一个CDR对象,否则解析不正确

ACE_InputCDR cdr2(spBlock.get());

ACE_CDR::Boolean byte_order;

cdr2>>ACE_InputCDR::to_boolean(byte_order);

cdr2.reset_byte_order(byte_order);

ACE_CDR::ULong length;

cdr2>>length;


auto_ptr<SHMRecord> spRecord(new SHMRecord);

cdr2>>*spRecord;

cout<<spRecord->type_<<endl;

cout<<spRecord->offset_<<endl;

cout<<static_cast<char*>(spRecord->pData_)<<endl;

cout<<spRecord->dataLength_<<endl;

}

}

}


ACE_Message_Block类用来管理数据,内部有一个指向ACE_Data_Block对象的指针,ACE_Data_Block类管理实际的缓冲区数据。这种设计允许多个ACE_Message_Block对象共享同一个ACE_Data_Block对象,对于效率的提高很有帮助。多个ACE_Message_Block对象可以组成一个链表(双向或者单向)。

在上面的例子中,我们 创建了一个默认大小的ACE_Message_Block对象,然后将接收的数据写入ACE_Data_Block的缓冲区中,并且移动写指针的位置。ACE_InputCDR通过和ACE_Message_Block对象关联来读取缓冲区的数据。


内存映射文件

多个操作系统平台都提供了内存映射文件,这是一个简单的将数据结构保存到文件中的机制。同时由于32位操作系统的进程虚拟内存最大只能4GB,用内存映射文件的方式就可以突破这个限制,可以用来打开超过4GB的大文件。

而且,内存映射文件其实就是一种共享内存机制,进程间可以通过共享内存直接访问数据。

ACE提供了ACE_MMAP_Memory_Pool类,该类代表了为内存映射文件分配内存的内存池。和ACE_Malloc模板类配合,我们就可以以平台无关的方式操纵内存映射文件了。比如:ACE_Malloc<ACE_MMAP_Memory_Pool,ACE_SYNCH_MUTEX> 。第二个参数是并发锁的策略类。

下面的例子实现了write函数,该函数负责将结构SHMRecord的数据写到内存映射文件snapshot中,并且以HashMap的方式保存。keyoffset的字符串表示,value是结构的指针。

注意,结构中的pData_成员指向的内存也应该由ACE_Malloc在内存映射文件中分配。这种模式下,千万用常规思路管理内存回收,比如智能指针,有时候内存不需要回收。

#include <iostream>

#include <sstream>

using namespace std;

#include "ace/MMAP_Memory_Pool.h"

#include "ace/Malloc_T.h"




class SHMRecord

{

public:

SHMRecord():pData_(NULL){}

ACE_UINT16 type_;

ACE_UINT32 offset_;

void* pData_;

ACE_UINT32 dataLength_;


size_t size() const

{

return 2+4+4+dataLength_;

}

};



typedef

ACE_Malloc<ACE_MMAP_Memory_Pool,ACE_SYNCH_MUTEX> MAllocator;


void write()

{

MAllocator allocator("/opt/ace/freebird/snapshot");


void * pMemory=allocator.malloc(sizeof(SHMRecord));

if(pMemory==NULL)

{

cout<<"malloc failed"<<endl;

return;

}

SHMRecord* pRecord=new(pMemory) SHMRecord();

pRecord->type_=9;

pRecord->offset_=2;

pRecord->dataLength_=4;

pRecord->pData_=allocator.malloc(4);

ACE_OS::strcpy(static_cast<char*>(pRecord->pData_),"hel");


stringstream stream;

stream<<pRecord->offset_;

if(allocator.bind(stream.str().c_str(),pRecord)==-1)

{

cout<<"bind failed"<<endl;

return;

}


allocator.sync();

}


void read()

{

}


int main(void)

{

write();

read();

return 0;

}


ACE_Malloc模板类的malloc成员负责分配内存,free负责释放内存,bind负责将数据添加到HashMap中,sync负责将数据从内存写到文件中。构造函数接收文件名作为参数。

现在我们来完成read函数,该函数将负责从内存映射文件中读取数据,并且显示出来。

void read()

{

MAllocator allocator("/opt/ace/freebird/snapshot");


ACE_Malloc_LIFO_Iterator<ACE_MMAP_Memory_Pool,ACE_SYNCH_MUTEX> iter(allocator);


for(void* pData=0;iter.next(pData)!=0;iter.advance())

{

SHMRecord* pRecord=ACE_reinterpret_cast(SHMRecord*,pData);

cout<<pRecord->type_<<endl;

cout<<pRecord->offset_<<endl;

cout<<static_cast<char*>(pRecord->pData_)<<endl;

cout<<pRecord->dataLength_<<endl;

}

}

这里使用了一个后进先出的迭代器,可以遍历HashMap中的数据。当然也可以使用find方法查找指定的数据。

多个进程因而可以使用同一块共享内存,但是问题是这些内存在每个进程中的基地址可能不同。虽然大多数操作系统能够保证,但是万一遇到意外情况怎么办?在这种情况下直接保存的指针将毫无意义。


ACE提供了解决方案:

1)使用ACE_Malloc_T<ACE_MMAP_MEMORY_POOL,

ACE_NUll_Mutex,ACE_PI_Control_Block>代替前面的分配器。关键在于ACE_PI_Control_Block类,该类使我们获得了于位置无关的分配内存功能。

2)在我们的结构中使用了普通的C++指针,现在要替换成ACE_Based_Pointer_Basic<char>类型。 该类会针对不同的基地址重新计算指针。

3)迭代器也应该使用父类:

ACE_Malloc_LIFO_Iterator_T<ACE_MMAP_Memory_Pool,ACE_SYNCH_MUTEX,ACE_PI_Control_Block> iter(allocator);

线程管理

ACE Wrapper Facade层提供了ACE_Thread_Manager类,该类可以创建和管理线程的生存期、同步和属性。

ACE_Thread_Manger类实现了singletom模式,因此我们需要通过instance函数来创建或者获取对象。spawn方法用来创建一个新的线程,我们需要传递线程函数和函数的参数给该方法。示例如下:

class SnapShotReqWaiter

{

public:

struct Thread_Args

{

Thread_Args(SnapShotReqWaiter* lsp):this_(lsp)

{

}

SnapShotReqWaiter* this_;

};

.....

void run()//throw std::runtime_error

{

Thread_Args* thread_args=new Thread_Args(this);

if(ACE_Thread_Manager::instance()->spawn(run_svc,

ACE_static_cast(void*,thread_args),

THR_DETACHED|THR_SCOPE_SYSTEM,

&threadID_)==-1)

{

throw

std::runtime_error("can't create a new thread in SnapShotReqWiater::run method");

}

}


static void* run_svc(void* arg)

{

}

 
http://tb.blog.csdn.net/TrackBack.aspx?PostId=1694958
 类似资料: