DDS-RPC 规范规定了如何在 DDS 之上构建 RPC。下面以一个 IDL 为例,讲述 RPC 的基本构成。
在下面的叙述中,将使用 RPC 服务的称为 Request/Client/Caller,将提供 RPC 服务的称为 Replay/Server/Callee。
共有类型
下面定义了一些公有类型:
RPC 共有类型
module dds { typedef octet GuidPrefix_t[12]; struct EntityId_t { octet entityKey[3]; octet entityKind; }; struct GUID_t { GuidPrefix_t guidPrefix; EntityId_t entityId; }; struct SequenceNumber_t { long high; unsigned long low; }; struct SampleIdentity { GUID_t writer_guid; SequenceNumber_t sequence_number; }; module rpc { typedef octet UnknownOperation; typedef octet UnknownException; typedef octet UnusedMember; enum RemoteExceptionCode_t { REMOTE_EX_OK, REMOTE_EX_UNSUPPORTED, REMOTE_EX_INVALID_ARGUMENT, REMOTE_EX_OUT_OF_RESOURCES, REMOTE_EX_UNKNOWN_OPERATION, REMOTE_EX_UNKNOWN_EXCEPTION }; typedef string<255> InstanceName; struct RequestHeader {(1) SampleIndentity_t requestId; InstanceName instanceName; }; struct ReplyHeader {(2) dds::SampleIdentity relatedRequestId; dds::rpc::RemoteExceptionCode_t remoteEx; }; } // module rpc } // module dds
RequestHeader 用于 Client。
ReplyHeader 用于 Server。
IDL 接口文件
假设现在有这样一个 RPC IDL:
module robot {
exception TooFast {};
enum Command { START_COMMAND, STOP_COMMAND };
struct Status {
string msg;
};
@DDSService
interface RobotControl {
void command(Command com);
float setSpeed(float speed) raises (TooFast);
float getSpeed();
void getStatus(out Status status);
};
}; //module robot
Request 接口
通过上面的类型可以生成下面这样用于 Client 的类型:
module robot {
struct RobotControl_command_In {
Command com;
};
struct RobotControl_setSpeed_In {
float speed;
};
struct RobotControl_getSpeed_In {
dds::rpc::UnusedMember dummy;
};
struct RobotControl_getStatus_In {
dds::rpc::UnusedMember dummy;
};
}
这样的一个 IDL 描述了每个接口需要传入的参数。 由于是 Request 侧,因此只有入参会被写入 。如果没有入参,就以 dummy 代替。
通过上面的 IDL 可以生成一个 Request 接口的 IDL 文件:
module robot {
const long RobotControl_command_Hash= HASH(“command”);
const long RobotControl_setSpeed_Hash= HASH(“setSpeed”);
const long RobotControl_getSpeed_Hash= HASH(“getSpeed”);
const long RobotControl_getStatus_Hash = HASH(“getStatus”);
union RobotControl_Call switch(long) {(1)
default
dds::rpc::UnknownOperation unknownOp;
case RobotControl_command_Hash:
RobotControl_command_In command;
case RobotControl_setSpeed_Hash:
RobotControl_setSpeed_In setSpeed;
case RobotControl_getSpeed_Hash:
RobotControl_getSpeed_In getSpeed;
case RobotControl_getStatus_Hash:
RobotControl_getStatus_In getStatus;
};
}
module robot {
struct RobotControl_Request {(2)
dds::rpc::RequestHeader header;
RobotControl_Call data;
};
}
用于 Request 的 Body。
用于传入 DDS 的数据。
可以看到,IDL 将 IDL 接口文件 中定义的接口首先映射到四个枚举常量中,然后通过一个 TaggedUnion 来判断调用的是哪个接口。通过对应的接口就能够序列化相应的数据。最后连同 RPC Header 传递给 DDS 中。
Replay 接口
通过 IDL 接口文件 可以生成下面这样用于 Server 的类型:
Replay 类型
module Robot { struct RobotControl_command_Out { dds::rpc::UnusedMember dummy; }; struct RobotControl_setSpeed_Out { float return_; }; struct RobotControl_getSpeed_Out { float return_; }; struct RobotControl_getStatus_Out { Status status; }; } module robot { const long TooFast_Ex_Hash = HASH (“TooFast”); union RobotControl_command_Result switch(long) { case dds::RETCODE_OK: RobotControl_Command_Out result; }; union RobotControl_setSpeed_Result switch(long) { case dds::RETCODE_OK: RobotControl_setSpeed_Out result; case TooFast_Ex_Hash: TooFast toofast_ex; }; union RobotControl_getSpeed_Result switch(long) { case dds::RETCODE_OK: RobotControl_getSpeed_Out result; }; union RobotControl_getStatus_Result switch(long) { case dds::RETCODE_OK: RobotControl_getStatus_Out result; }; };// module robot
Server 类型定义特点如下:
Server 仅需要关注出参。如果函数没有返回值,则使用 dummy 代替。
Server 中每个参数的结果使用 TaggedUnion 标识,使用 Tag 表示函数调用结果是否正确(类似 Rust 中的 Result)。 - Server 生成的接口如下:
Replay 接口
union RobotControl_Return switch (long) { default: dds::rpc::UnknownOperation unknownOp; case RobotControl_command_Hash: RobotControl_command_Result command; case RobotControl_setSpeed_Hash: RobotControl_setSpeed_Result setSpeed; case RobotControl_getSpeed_Hash: RobotControl_getSpeed_Result getSpeed; case RobotControl_getStatus_Hash: RobotControl_getStatus_Result getStatus; }; struct RobotControl_Reply { dds::rpc::ReplyHeader header; RobotControl_Return reply; };
和 Client 相同,Server 也使用 TaggedUnion 传达函数调用结果。
错误码
RPC 在 DDS 原有的错误码(称为 LocalError) 上拓充了一部分错误码(称为 RemoteError)。这些错误码由下面进行定义:
module dds {
module rpc {
enum RemoteExceptionCode_t {
REMOTE_EX_OK,
REMOTE_EX_UNSUPPORTED,
REMOTE_EX_INVALID_ARGUMENT,
REMOTE_EX_OUT_OF_RESOURCES,
REMOTE_EX_UNKNOWN_OPERATION,
REMOTE_EX_UNKNOWN_EXCEPTION
};
} // module rpc
} // module dds
Remote Exception Code | 含意 |
---|---|
REMOTE_EX_OK | The request was executed successfully. |
REMOTE_EX_UNSUPPORTED | Operation is valid but it is unsupported (a.k.a. not implemented). |
REMOTE_EX_INVALID_ARGUMENT | The value of the parameter passed has an illegal value (e.g., two options active in a @Choice structure). |
REMOTE_EX_OUT_OF_RESOURCES | The remote service ran out of resources while processing the request. |
REMOTE_EX_UNKNOWN_OPERATION | The operation called is unknown. |
REMOTE_EX_UNKNOWN_EXCEPTION | A generic, unspecified exception was raised by the service implementation. |
QoS
QoS 的默认值如下,这些值可以在 IDL 中通过 @QoS 的方式进行修改:
QoS Policy | Value |
---|---|
Reliability | DDS_RELIABLE_RELIABILITY_QOS |
History | DDS_KEEP_ALL_HISTORY_QOS |
Durability | DDS_VOLATILE_DURABILITY_QOS |
API 风格
RPC over DDS 规定了两种调用风格:
Request/Reply 对 RPC 进行了最小实现。IDL 甚至只需要所需的结构体即可。但是这种风格不具备 RPC 的外观。
Function-call 风格提供了类似函数调用的实现,但是它无法胜任某些具备持续返回值的调用(比如进度条)。
即使是 Server/Client 使用不同的调用风格,两者之间也是能够正常通信的。一般而言,可以使用 Request/Reply 来实现 Function-call 风格。
由于 DDS 本身是一个 pub-sub 网络,是天然无状态的,因此需要 [共有类型] 中的 SampleIdentity(GUID, SN) 来标明函数调用。Client 需要用它来识别得到的响应是哪一次得到的,Server 需要使用它来表示这次回复是给谁的。