1使用网络收发数据的最简单方式
在学习idl之前,先来看一下我们在开发中,通过网络收发数据时,常常怎么实现。
struct Student {char name[32];int age;char sex;// f 男,m 女
};//发送侧
struct Student s1 = {"xiaoming", 7, 'f'};
send(fd, (void *)(&s), sizeof(struct Student))//接收侧
struct Student s2;
recv(fd, (void *)(&s2), sizeof(struct Studen));
上边是一个c语言中经常使用的通过网络收发数据的例子,包括两个步骤:
(1)定义一个结构体来表示用户的数据
(2)收发的时候直接使用结构体的地址和大小
这种方式,有以下三点需要注意:
(1)适用于字节序相同的机器,如果发送端和接收端所在的机器字节序不同,那么可能会有问题。
(2)适用于相同语言之间的收发,不能跨语言。
(3)直接发送结构体大小的数据,没有进行显式的序列化和反序列化。序列化即将结构化的数据转化成字节流,反序列化则反之。结构体的每个成员的大小之和为32+4+1=37个字节,而sizeof(struct Student)的大小是40。不做显式的序列化和反序列化,只能适用于这种简单的场景,如果数据结构复杂度增大,比如在c++中,使用了std::string、std::vector等,那么就不适用了,因为对于std::string、std::vector这样的数据,不管sizeof返回的是对象的大小,并不是实际数据的大小,也就是说,不管std::string的字符串多长,sd::vector中的元素个数是1个还是100个,sizeof的返回值是不变的。
2idl
2.1idl支持的数据类型
fasdds idl官方文档:5. Defining a data type via IDL — Fast DDS 3.1.1 documentation
idl的全称是Interface Definition Language,接口定义语言。接口这个词真的在很多场景下都会使用,并且有不同的含义:一个函数可以叫做一个接口;在c++中,一个抽象类可以叫做接口;在使用uml画图的时候,一个类也可以叫做一个接口,接口表示一个软件组件给另一个软件组件提供的可以调用的函数。在idl中,接口说的就是通信的双方使用的通信数据的格式。
如下是fasdds例子hello world中的idl。定义一个idl,使用struct来定义。作为通信数据格式的定义,idl与我们使用结构体比较类似,但也有很大的区别,idl和c/c++中的结构体明显的区别有以下三点:
(1)支持注解,比如下边的@extensibility(APPENDABLE)就是一个注解
(2)支持sring,vector,sequence等数据类型,序列化和反序列化不需要用户关心
(3)idl支持跨语言,使用同一个idl,可以生成c++或者python的文件,使用c++开发的应用和使用python开发的应用之间可以通过同一个idl进行通信
idl支持的数据类型在官方文档有说明,有如下几类:
基本数据类型 | 常见的基本数据类型都支持:整数、复现数、布尔类型、字符、字符串 |
数组和序列 | 数组的大小在定义的时候就需要指定,序列的大小不需要指定,序列的大小不固定。数组对应c++中的std::array,序列对应c++中的std::vector |
map | map是<key, value>类型的数据,只支持key和value是基本数据类型的map。 |
结构体 | 结构体是经常使用的。 |
上表是常用的数据类型,idl另外还支持联合体、枚举、bit集等。
2.2注解:@id
id用来注解结构体中成员的序号,常常和@extensibility(MUTABLE)一起出现。如果没哟使用id进行注解,那么默认的id是从0开始。
如下两个结构体,index的id都是是0,message的id都是1,两者都是MUTABLE类型的。理论上来说,两者是可以互通的,但是在fastdds上实际测试,两者是不通的。
@extensibility(MUTABLE)
struct HelloWorld {
unsigned long index;
string message;
};@extensibility(MUTABLE)
struct HelloWorld
{
@id(1) string message;
@id(0) unsigned long index;
};
2.3注解:@extensibility
如果系统已经上线稳定运行,这个时候系统需要新增功能,需要在idl中新增字段,同时只需要新增的代码使用新的字段,已经运行的软件不使用新字段,新旧软件使用同一个idl。@extensibility就是为了满足这样的需求。
APPENDABLE:
APPENDABLE的意思是可以在后边追加数据。如下例子,一开始HelloWorld中只有两个数据成员,分别是index和message,如果我们想要新增一个数据name,那么可以新定义一个结构体HelloWorld1,这样,使用HelloWorld1的软件可以和HelloWorld的软件通信。
新:
@extensibility(APPENDABLE)
struct HelloWorld {
unsigned long index;
string message;
};@extensibility(APPENDABLE)
struct HelloWorld1
{
HelloWorld hello;
string name;
};旧:
@extensibility(APPENDABLE)
struct HelloWorld {
unsigned long index;
string message;
};
APPENDABLE追加,不是在原来的结构体中追加,比如下边两个idl是不能通信的。
@extensibility(APPENDABLE)
struct HelloWorld
{
unsigned long index;
string message;
};
@extensibility(APPENDABLE)
struct HelloWorld
{
unsigned long index;
string message;string name;
};2、如下使用继承的方式,也是可以通信的
旧:
@extensibility(APPENDABLE) struct HelloWorld {unsigned long index;string message; };
新:
@extensibility(APPENDABLE) struct HelloWorld {unsigned long index;string message; };@extensibility(APPENDABLE) struct HelloWorld1 : HelloWorld {string name; };
自己在测试中发下,如下两种使用FINAL进行注解的情况,新idl和旧idl也是可以通信的,这一点与FINAL的语义是不符合的。并且新idl做为publisher,旧idl作为subscriber,以及返过来,均可以通信。
1、组合方式,新idl和旧idl都使用FINAL类注解
新:
@extensibility(FINAL)
struct HelloWorld {
unsigned long index;
string message;
};@extensibility(FINAL)
struct HelloWorld1
{
HelloWorld hello;
string name;
};
旧:
@extensibility(FINAL)
struct HelloWorld {
unsigned long index;
string message;
};2、使用继承的方式,新idl和旧idl都使用FINAL类注解
新:
@extensibility(APPENDABLE)
struct HelloWorld {
unsigned long index;
string message;
};@extensibility(APPENDABLE)
struct HelloWorld1 : HelloWorld
{
string name;
};
旧:@extensibility(APPENDABLE)
struct HelloWorld
{
unsigned long index;
string message;
};
两个struct,数据成员完全一样,一个是FINAL,一个是APPENDABLE,两者不能通信。
@extensibility(FINAL)
struct HelloWorld
{
unsigned long index;
string message;
};
@extensibility(APPENDABLE)
struct HelloWorld
{
unsigned long index;
string message;
};
MUTABLE:
参考2.1
2.4fastddsgen
fastddsgen是用java开发的工具,使用idl定义的数据结构,要使用fastddsgen来将idl生成对应的.h和.cpp文件。命令格式是./fastddsgen xxx.idl。
3. Linux installation from sources — Fast DDS 3.1.1 documentation