3. 构建一个应用程序
eProsima Fast RPC能让用户很容易的使用远端程序调用生成他们的发布程序,而且不用担心请求和应答是如何产生的以及如何通过网络发送的。本章更详细介绍上述四步开发顺序。经过实验的用户还需要参考第 4 节,其中涵盖了库的更高级主题。
3.1 定义一个远端程序集
IDL 用于定义服务器提供的远程程序,以及用作它们使用的参数数据类型。IDL 的结构基于 OMG IDL,在以下图中进行了描述:
eProsima Fast RPC 提供一个Java工具rpcddsgen, 用于从IDL文件为定义的远端程序集生成C++代码。使用选项和参数在第 3.2节(生成特定的远端程序调用支持代码)中指定。
3.1.1 IDL语法和映射到C++
用户通过IDL定义的类型遵循以下规则映射到C++11本地类型。
3.1.1.1 简单类型
eProsima Fast RPC 支持多种简单类型,开发者可将它们用作参数、返回值和复杂类型的成员。下表显示支持的简单类型如何在IDL中定义的,以及fastrpcgen是怎样将它们映射到C++11的。
TablE 1: Specifying Simple Types in IDL for C++ USING C++11 NATIVE TYPES
IDL Type | Sample in IDL File | Sample Output Generated by rpcddsgen |
char | char char_member | char char_member |
wchar | wchar wchar_member | wchar_t wchar_member |
octet | octet octet_member | uint8_t octet_member |
short | short short_member | int16_t short_member |
unsigned short | unsigned short ushort_member | uint16_t ushort_member |
long | long long_member | int32_t long_member |
unsigned long | unsigned long ulong_member | uint32_t ulong_member |
long long | long long llong_member | int64_t llong_member |
unsigned long long | unsigned long long ullong_member | uint64_t ullong_member |
float | float float_member | float float_member |
double | double double_member | double double_member |
boolean | boolean boolean_member | bool boolean_member |
bounded string | string<20> string_member | std::string string_member |
unbounded string | string string_member | std::string string_member |
3.1.1.2 复杂类型
复杂类型可通过结合简单类型创建。这些复杂类型可用作参数和返回值。下图显示支持的复杂类型是如何在IDL中定义的,以及fastrpcgen是如何将它们匹配到C++11的。
Table 2: Specifying Complex Types in IDL for C++ USING C++11 NATIVE TYPES
IDL Type | Sample in IDL File | Sample Output Generated by rpcddsgen |
enum | enum PrimitiveEnum { enum PrimitiveEnum { | enum PrimitiveEnum : uint32_t { enum PrimitiveEnum : uint32_t { |
struct | struct PrimitiveStruct { | class PrimitiveStruct { public: /** Constructors **/ ... /** Getter and Setters **/ char char_member(); void char_member(char x); ... private: char m_char_member; |
union | union PrimitiveUnion switch(long) { | class PrimitiveUnion { public: /** Constructors **/ ... /** Discriminator **/ int32_t _d(); void _d(int32_t x); ... /** Getter and Setters **/ int32_t long_member(); ... private: int32_t m__d; int16_t m_short_member; int32_t m_long_member; |
typedef | typedef short TypedefShort; | typedef int16_t TypedefShort; |
array | struct OneDArrayStruct { struct TwoDArrayStruct { | class OneDArrayStruct { ... private: m_short_array; class TwoDArrayStruct { ... private: |
bounded sequence | struct SequenceStruct { | class SequenceStruct { ... private: m_short_sequence; |
unbounded sequence | struct SequenceStruct { | class SequenceStruct { private: m_short_sequence; |
Note: 这些复杂类型不能直接用作程序的参数,若要作为参数必须用typedef重新定义他们。
3.1.1.3 参数定义
三个保留字用来定义程序的参数。必须在每个参数中使用其中的一个。下表中展示了这些保留字和它们的含义。
Reserved word | Meaning |
in | 参数是入参。 |
inout | 参数既是入参也是出参。 |
output | 参数是出参。 |
例如,假定支持的类型T定义为参数。如果参数使用保留字in并且T是简单类型或枚举类型,则该类型作为T匹配到C++。如果类型 T 是复杂类型,则该类型在 C++ 中映射为 const T&.
如章节3.1.1.2 (复杂类型)提到的那样,数组和序列类型不能直接定义成参数类型。为了这样使用,必须事先用typedef重定义。
3.1.1.4 程序定义
程序的定义由两个或更多的元素组成:
-
返回值的类型。允许使用void类型。
-
程序名。
-
参数列表,可以为空。
下面展示了应如何定义程序的示例:
long funcName(in short param1, inout long param2); |
rpcddsgen按照下列规则匹配程序:
-
C++返回值的类型与 IDL 文件中定义的类型相同,使用 3.1.1.1 (简单类型) 和 3.1.1.2(复杂类型)章节中描述的表进行映射。
-
C++函数的名称与 IDL 文件中定义的函数的名称相同。
-
C++函数中的参数顺序与IDL文件中的参数顺序相同。C++中匹配的参数在章节3.1.1.3(参数定义)中描述。
遵循这些规则,之前的示例将产生如下的C++函数:
int32_t funcName(int16_t param1, int32_t& param2); |
3.1.1.5 异常定义
IDL函数可以引发用户定义的异常,以指示错误的发生。一个异常结构可以包括若干个字段。下面展示了一个异常结构的示例:
exception ExceptionExample |
该示例产生如下的C++异常:
class ExceptionExample: public eprosima::rpc::exception::UserException { public: ExceptionExample(); ExceptionExample(const ExceptionExample &ex); ExceptionExample(ExceptionExample&& ex); ExceptionExample& operator=(const ExceptionExample &ex); ExceptionExample& operator=(ExceptionExample&& ex); virtual ~ExceptionExample() throw(); virtual void raise() const; /** Getters and Setters **/ int32_t count() const; int32_t& count(); void count(int32_t _count); ... private: /** Exception members **/ int32_t m_count; std::string m_msg; }; |
若要指定操作可以引发一个或多个用户定义的异常,必须定义异常,并且必须将 IDL 引发子句添加到操作定义中。示例如下所示:
exception Exception1 exception Exception2 string msg; void exceptionFunction() raises(Exception1, Exception2); |
3.1.1.6 接口定义
服务端提供的远端程序集必须在 IDL 接口中定义。下面显示了如何定义接口的示例:
interface InterfaceExample { // Set of remote procedures. }; |
IDL接口匹配三个类:
-
InterfaceExampleProxy:本地服务端代理为客户端应用提供远端程序。客户端应用程序创建此类的一个对象并调用远端程序。
-
InterfaceExampleServerImpl:服务端使用,包含远端程序的定义。这些定义必须被用户实现。eProsima Fast RPC 创建此类的一个对象。
-
InterfaceExampleServer: 服务端的实现。用与执行实例。
如下例所示,eProsima RPC over DDS 支持接口继承:IDL接口ChildInterface有两个函数,从ParentInterface继承一个,它自己一个。
interface ParentInterface { void function1(); }; interface ChildInterface : ParentInterface { void function2(); }; |
3.1.1.7模块定义
为了将例如复杂类型、异常、函数和接口等相关定义合并成组,开发者可使用模块:
module ModuleExample { // Set of definitions }; |
模块作为命名空间映射到C++,其中的每个定义都将被定义。
3.1.1.8 局限性
rpcddsgen 对 IDL 语法有一些限制:
-
两个程序不能同名。
-
程序中定义的复杂类型(数组和序列)必须事先用关键字typedef定义,如 CORBA IDL 2.0 规范强制的那样。
3.1.2示例
此示例将用作随后章节其他示例的基础。上一节中描述的 IDL 语法通过一个示例显示:
// file Bank.idl enum ReturnCode { SYSTEM_ERROR, ACCOUNT_NOT_FOUND, AUTHORIZATION_ERROR, NOT_MONEY_ENOUGH, OPERATION_SUCCESS }; struct Account { string AccountNumber; string Username; string Password; }; // @top level false interface Bank { ReturnCode deposit(in Account ac, in long money); }; |
3.2生成特定的远端程序调用支持代码
描述API接口的IDL文件完成后,将产生客户端代理和服务端框架代码。fastrpcgen匹配IDL文件并产生相应的支持代码。
3.2.1RPCDDSGEN命令语法:
使用以下语法调用该工具:
rpcddsgen [options] <IDL file> <IDL file> ... |
Options:选项:
Option | Description |
-help | 显示帮助信息。 |
-version | 显示当前eProsima RPC over DDS的版本。 |
-ppPath <directory> | C/C++预处理器的位置。 |
-ppDisable | 禁用 C/C++ 预处理器。在不使用宏或包含时很有用。 |
-replace | 替换已有的生成文件。 |
-example <platform> | 为特定平台创建解决方案。开发人员将使用此解决方案来编译客户端和服务器。 可能的值: i86Win32VS2010, x64Win64VS2010, i86Linux2.6gcc4.4.5, x64Linux2.6gcc4.4.5 |
-d <path> | 为生成文件设置输出目录。 |
-t <temp dir> | 将特定的目录设置为临时目录。 |
-transport <transport> | 选择生成代码将使用的DDS传输协议。可能的值:rtps, rti。默认值:rpts |
-topicGeneration <option> | 定义DDS主题是如何产生的。 可能的值: byInterface, byOperation. 默认值:byInterface |
rpcddsgen产生若干个文件,如本节所述。它们的名字匹配 IDL 文件名,在本手册中表示为<IDLName>标记。
3.2.2服务端
rpcddsgen产生C++头文件和源文件,其中包括远端程序的声明和定义。这些文件是实现定义的接口的框架。开发者可使用这些源文件中的定义去实现他们远端程序的行为。这些文件包括<IDLName>ServerImpl.h和<IDLName>ServerImpl.cxx。fastrpcgen同样产生C++源文件并包含一个服务端程序和实例的示例。文件是<IDLName>ServerExample.cxx。
3.2.3 客户端
rpcddsgen同样产生C++源文件并带有一个客户端应用程序示例,以及该客户端应用程序怎样从服务端调用远端程序。文件是<IDLName>ClientExample.cxx。
3.3 服务端实现
运行rpcddsgen后,产生两个名为<IDLName> ServerImpl.cxx和<IDLName> ServerImpl.h的文件。这些文件与服务端提供的接口的框架相符合。远端程序定义在这些文件中,并且它们的每个行为都需要开发者去实现。对于 3.1.2(示例)中看到的远端程序deposit函数,可能的实现是:
ReturnCode BankServerImplExample::deposit(/*in*/ const Account& ac, /*in*/ int32_t money) { ReturnCode return_ = ::SYSTEM_ERROR; return return_; } |
在执行这些程序实现时,应考虑以下几点
-
开发者可以使用 in 参数,但无法释放其分配的内存,也无法释放其任何成员的。
-
开发者可以修改inout参数。但是在给它们的成员分配内存时,以前分配的内存必须被释放。
-
out参数不能被初始化。开发者需要初始化它们。
fastrpcgen生成的代码也包含服务端使用的类。这些类在文件<IDLName>Server.h和<IDLName>Server.cxx中实现。
当创建一个类<IDLName>Server的对象时,可使用代理与其建立连接。创建连接的方式以及代理查找服务器的方式取决于所选的网络传输,如4.1 所述。
3.3.1API
使用3.1.2中的IDL示例,为这个类产生的API如下:
class BankServer: public eprosima::rpc::server::Server { public: BankServer(
); virtual ~BankServer(); ... }; |
服务端提供了一个四个参数的构造函数。strategy 参数是一种服务策略,该策略定义服务端如何管理传入请求。服务策略在章节4.4(线程服务策略)中描述。
第二个参数是用于连接客户端代理的网络传输类型。第三个参数是传输协议。它由fastrpcgen生成,它的类负责反序列化收到的数据并将其转发给用户实现。最后,第四个参数是用户实现的服务端框架。
3.3.2 异常和错误管理
在服务端,开发者可以通知远端程序执行中的错误。可在程序代码中抛出 eprosima::rpc::exception::ServerInternalException异常。然后,此异常将传递到代理,并将在客户端抛出。章节3.4.2解释了在客户端怎样处理异常。下面显示了异常如何工作的示例:
ReturnCode BankServerImpl::deposit(/*in*/const Account& ac, /*in*/ int32_t money) { ReturnCode returnedValue = SYSTEM_ERROR; throw eprosima::rpc::exception::ServerInternalException(“Error in deposit procedure”); return returnedValue; } |
3.3.3 示例
使用3.1.2 示例章节建议的IDL,开发者可以创建如下的服务端:
unsigned int threadPoolSize = 5; ThreadPoolStrategy *pool = NULL; BankServer *server = NULL; try { pool = new ThreadPoolStrategy(threadPoolSize);
server->serve(); } catch(eprosima::rpc::exception::InitializeException &ex) { std::cout << ex.what() << std::endl; } |
3.4 客户端实现
由fastrpcgen生成的代码包含充当远程服务端代理的类。这些类在文件<IDLName>Proxy.h和<IDLName>Proxy.cxx中实现。这些代理提供服务资源以便客户端应用程序能直接调用它的远端程序。
3.4.1 API
使用3.1.2 章节建议的IDL,类的API如下:
class BankProxy : public eprosima::rpc::proxy::Proxy { public: BankProxy(eprosima::rpc::transport::ProxyTransport &transport, eprosima::rpc::protocol::BankProtocol &protocol); virtual ~BankProxy(); ReturnCode deposit(/*in*/ const Account& ac, /*in*/ int32_t money); void deposit_async(Bank_depositCallbackHandler &obj, /*in*/ const Account& ac, /*in*/ int32_t money); }; |
代理程序提供了一个构造函数并期望网络传输类对象作为它的第一个参数。第二个参时是协议对象。与服务端计数部分相同,它也是由fastrpcgen生成,它的职责是序列化和反序列化协议数据信息。
代理程序提供向客户端公开远端程序。在示例 IDL 中,是deposit。
3.4.2 异常
当执行远端程序调用时,可能会发生错误。这时,异常用来报告错误。当远端程序被调用时,下列异常可能会抛出:
Exception | Description |
eprosima::rpc::exception::ClientInternalException | 当客户端存在问题时此异常将会抛出。 |
eprosima::rpc::exception::ServerTimeoutException | 当超过等待服务端回复的最长时间时,将抛出此异常。 |
eprosima::rpc::exception::ServerInternalException | 当服务端存在问题时此异常将会抛出。 |
eprosima::rpc::exception::ServerNotFoundException | 当代理找不到服务端时,此异常将会抛出。 |
所有的异常来自同一个基类: eprosima::rpc::exception::Exception.
3.4.3 示例
使用3.1.2 章节建议的IDL,开发者可按照以下方式访问deposit远端程序:
BankProtocol *protocol = NULL; RTPSProxyTransport *transport = NULL; BankProxy *proxy = NULL; try { protocol = new BankProtocol(); transport = new RTPSProxyTransport("BankService", "Instance"); proxy = new BankProxy(*transport, *protocol); } catch(eprosima::rpc::exception::InitializeException &ex) { std::cout << ex.what() << std::endl; } Account ac; int32_t money; ReturnCode depositRetValue; try { depositRetValue = proxy->deposit(ac, money); } catch(eprosima::rpc::exception::Exception &ex) { std::cout << ex.what() << std::endl; } |