简介

CycloneDDS是航空航天中最为常见的分布式通信系统,目前在工作中也会涉及该框架,本文简单介绍了Cyclone DDS的核心API与QoS策略,涵盖参与者创建、主题定义、发布者配置等关键接口,并通过IDL工具展示通信协议生成过程。同时详细探讨数据可用性、交付可靠性等QoS机制及其兼容性规则

基础API介绍

dds_create_participant创建参与者

DDS_EXPORT dds_entity_t
dds_create_participant(
  const dds_domainid_t domain,
  const dds_qos_t *qos,
  const dds_listener_t *listener);

为某一个域创建参与者,domain表示域ID,不同域的参与者在同一物理网络中互不可见(笔者将其理解为虚拟局域网中的VLAN ID);qos表示质量指标,listener为设置的监听

dds_create_topic创建topic

DDS_EXPORT dds_entity_t
dds_create_topic(
  dds_entity_t participant,
  const dds_topic_descriptor_t *descriptor,
  const char *name,
  const dds_qos_t *qos,
  const dds_listener_t *listener);

为参与者participant创建一个topic,name为topic的名称。这里需要着重介绍的是descriptor,这是通过CycloneDDS的idlc工具创建的

idlc工具

idlc的主要任务就是即将.idl文件转换成目标语言代码,这和Protobuf中的protoc有着异曲同工之妙。.idl中定义了发布者和订阅者之间的通信协议,idlc将会基于此协议自动生成序列化和反序列化的代码,例如笔者定义了如下.idl协议文件

module Chat {
  @final
  struct Message {
    string content; // 消息内容
    long sender_id; // 发送者ID
  };
};

.idl的语法笔者这里不再介绍。idlc基于这个.idl文件,会生成Chat.hChat.c两个文件(默认生成C语言的代码)

typedef struct Chat_Message
{
  char * content;
  int32_t sender_id;
} Chat_Message;

extern const dds_topic_descriptor_t Chat_Message_desc;

#define Chat_Message__alloc() \
((Chat_Message*) dds_alloc (sizeof (Chat_Message)));

#define Chat_Message_free(d,o) \
dds_sample_free ((d), &Chat_Message_desc, (o))

这是头文件中的主要内容,Chat_Messagestruct是对应的结构体
dds_topic_descriptor_t 用于描述每种通信协议的元信息,这其中包括结构的字段名称、类型、大小、内存对齐方式等,Chat_Message_desc的具体定义在Chat.c

前面通过dds_create_topic创建topic时,就需要知道这个topic通信协议的元数据,这个导出的Chat_Message_desc就是需要传递给dds_create_topic的对象

Chat_Message__alloc宏和Chat_Message_free宏用于方便的创建和释放通信对象,这里值得注意的是,Chat_Message__alloc只会创建对象本身,对于指针类型,它不会为指针分配内存;所以在使用Chat_Message_free释放对象时尤其需要注意,它有多种释放模式,宏的参数o即表示使用哪种释放模式

  • DDS_FREE_KEY:只释放对象本身,如果某个字段指向一个堆内存,这块堆内存不会被释放。适用于对象由Chat_Message__alloc创建,但是字段指向的资源需要由外部管理的场景,如地址在栈上,不需要主动释放
  • DDS_FREE_CONTENTS:只释放字段指向的资源,但是不会释放对象本身。适用于对象本身在栈上或者由外部管理,但是字段指向的资源由dds_string_dup或者dds_alloc分配的场景
  • DDS_FREE_ALL:释放这个对象的所有资源,包括对象本身和内部字段指向的资源。适用于对象本身和内部字段指向的资源都是由dds_string_dup或者dds_alloc分配的场景

这是Chat_Message_free释放资源的三种模式

dds_create_publisher创建发布者

DDS_EXPORT dds_entity_t
dds_create_publisher(
  dds_entity_t participant,
  const dds_qos_t *qos,
  const dds_listener_t *listener);

这里的listener可以设置有如下

  • dds_lset_publication_matched_argdds_lset_publication_matched:当有参与者订阅或者取消订阅时,会回调设置的callback,在callback中会有详细的连接信息,例如总连接数、当前连接数、最后一个订阅者的信息;前者可以为callback设置userdata,后者不可以;同时,前者可以设置reset_on_invoke参数,后者默认设置为true
  • dds_lset_offered_incompatible_qos_argdds_lset_offered_incompatible_qos:当有参与者订阅,但是QoS策略和发布者不兼容时,callback被回调

QoS策略

在DDS中,QoS用于控制数据的可用性、交付方式、时效性及资源使用等,这是发布者和订阅者之间的一种“契约”,只有当发布者和订阅者之间的QoS策略兼容时,通信才能建立,如果不兼容则会触发设置了相关回调的函数

CycloneDDS中,使用dds_qos_t对QoS进行描述,通过dds_create_qos()创建一个dds_qos_t。QoS的设置主要有以下

数据可用性Durability

数据可用性包括多个方面,对于历史数据是否需要保留的配置通过dds_qset_durability进行配置,选项如下

  • DDS_DURABILITY_VOLATILE:不保留历史数据
  • DDS_DURABILITY_TRANSIENT_LOCAL:这种类型和紧跟的两种都表示需要保留历史数据,只是保留历史数据的方式不同。其中最常用的就是DDS_DURABILITY_TRANSIENT_LOCAL,表示将历史数据保留在发布者所在进程管理的内存队列中,另外两种则需要额外的独立进程来保存历史数据
  • DDS_DURABILITY_TRANSIENT
  • DDS_DURABILITY_PERSISTENT

如果发布者选择了保留历史数据,那么可以通过dds_qset_durability_service设置保留数据的策略

  • DDS_HISTORY_KEEP_LAST:保留最新的N条数据
  • DDS_HISTORY_KEEP_ALL:保留所有的历史数据,这会受到到硬件资源限制

数据交付Reliability

数据交付质量通过dds_qset_reliability进行设置

  • DDS_RELIABILITY_BEST_EFFORT:尽力而为,可能丢包
  • DDS_RELIABILITY_RELIABLE:可靠传输,支持重传

笔者试验下来,如果订阅者想要开始订阅时能够收到一些历史数据,那么需要发布者和订阅者的Reliability都设置为DDS_RELIABILITY_RELIABLE

QoS的兼容性

Reliability中,DDS_RELIABILITY_RELIABLE的能力最强,所以该指标一定兼容订阅者
Durability中,DDS_DURABILITY_VOLATILE的能力最弱,其只能兼容同为DDS_DURABILITY_VOLATILE的订阅者

总结

CycloneDDS中的接口设计有点繁琐,网上的资料也相对较少,笔者正在通过试验的方式继续深入了解该框架中关于事件回调通知、底层通信原理以及消息处理相关的内容,后续将继续更新

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐