该注解表明Stream是一个基类,输入流IStream和OStream都继承自它。
Stream的成员变量data_是一个指针,指向序列化字节流的开头,它的类型是uint8_t。
在Ubuntu系统中,uint8_t的定义为typedef unsigned char uint8_t;
所以uint8_t 是一个字节,可以使用size_of() 函数检查。 data_指向的空间就是保存字节流的地方。
输出流类OStream用于序列化对象,它引用了serialize函数,如下所示。
结构OStream :公共流{静态常量StreamTypestream_type=stream_types:Output; OStream(uint8_t* data, uint32_t count) : Stream(data, count) {} /* 将一个项目序列化到此输出流*/templateROS_FORCE_INLINE void next(const T t) { serialize(*this, t); } 模板ROS_FORCE_INLINE OStream 运算符(const T t) { 序列化(*this, t);返回*这个; }};输入流类IStream用于反序列化字节流。反序列化函数引用如下。
结构ROSCPP_SERIALIZATION_DECL IStream : 公共流{ 静态常量StreamTypestream_type=stream_types:Input; IStream(uint8_t* data, uint32_t count) : Stream(data, count) {} /* 反序列化此输入流中的项目*/template ROS_FORCE_INLINE void next(T t ){ deserialize(*this, t); } 模板ROS_FORCE_INLINE IStream 运算符(T t) { 反序列化(*this, t);返回*这个; }};当然,序列化函数和反序列化函数是改变数据形式的地方。的定义比较早。它们都收到两个模板,都是内联函数,里面什么也没有。他们只是调用Serializer类的成员函数write和read。因此,序列化和反序列化功能只是二流经销商。
//序列化一个对象。这里的Stream一般应该是一个ros:serialization:OStreamtemplateinline void serialize(Stream stream, const T t){ Serializer 那么,我们来分析一下Serializer类,如下。我们发现写入和读取函数在类型中也调用了序列化函数和反序列化函数。
别晕了,这里的序列化和反序列化函数和上面的同名函数并不一样。
评论说:“为了让ROS 序列化系统能够处理某个类型,您唯一需要做的就是专门化Serializer 类。”是专门化这个Serializer 类)。
这就涉及到另一个知识点——模板特化。
template struct Serializer{ //将对象写入流。一般情况下这里传入的流会是一个ros:serialization:OStream template inline static void write(Streamstream, typename boost:call_traits{ t.serialize(stream.getData(), 0); } //从流中读取一个对象,一般情况下传入的流这里将是一个ros:serialization:IStream 模板inline static void read(Stream stream, typename boost:call_traits{ t.deserialize(stream.getData()); } //确定对象的序列化长度inline static uint32_t serializedLength(typename boost:call_traits{ return t 。然后定义一个带参数的宏函数ROS_CREATE_SIMPLE_SERIALIZER(Type),然后将这个宏应用到ROS中的10种基本数据类型,它们是:uint8_t、int8_t、uint16_t、int16_t、uint32_t、int32_t, uint64_t、int64_t、浮点、双精度。
可见这10种数据类型的处理方法是相似的。看到这里大家应该明白了,write和read函数都是通过memcpy函数来移动数据的。
注意宏定义中的模板语句。这是模板专业化的标志。关键字模板后面跟着一对尖括号。
您可以在此处阅读有关模板专业化的信息。
#define ROS_CREATE_SIMPLE_SERIALIZER(Type) \\ template struct Serializer \\ { \\ template 对于其他类型的数据,如bool、std:string、std:vector、ros:Time、ros:Duration、boost:array等,各自的处理略有不同,所以没有不使用上述宏函数,而是使用模板专门化单独定义每个宏函数。这就是serialization.h 文件如此冗长的原因。
对于int、double等单元素数据,直接使用上面专门的Serializer类中的memcpy函数来实现序列化。
对于向量、数组等具有多个元素的数据类型怎么办?该方法分为几种情况。对于固定大小的简单类型,在各自的专用Serializer 类中使用memcpy 函数没有太大区别。
对于固定但非简单类型(固定大小非简单类型)或既不固定也不简单(非固定大小、非简单类型)或固定但不简单(固定大小、非简单类型),请使用for 循环遍历并单独处理每个元素。
那么如何判断一条数据是否固定呢?这是在roscpp_traits 文件夹中的message_traits.h 中完成的。
它使用了Type Traits的提取,这是一种比较高级的编程技术,我不太理解。
暂时就介绍到这里了。有一些细节我还没有谈到。等作者理解后我再补充。
2.消息订阅与发布
2.1ROS的本质
如果问ROS的本质是什么,或者用一句话概括ROS的核心功能。好吧,作者认为ROS是一个通信库,可以让不同的程序节点互相对话。
许多文章和书籍在介绍ROS是什么时经常使用“ROS是一个通信框架”的描述。
但我觉得这个描述不太恰当。 “框架”是一个抽象的术语,对于初学者来说非常不友好。用一个比较抽象、比较难的概念来解释一个本来就不清楚的概念,对初学者没有任何帮助。
而且作者严重怀疑大多数作者对机器人或软件框架的本质有深刻的理解。他们的见解并不比你我深刻多少。
说完了本质,我们就来说说最基本的问题。
在深入探讨无穷无尽的细节之前,我们不妨先做一些哲学思考。
即ROS为什么要解决通信问题呢?
机器人涉及机械、电子、软件、人工智能等数千种事物。为什么底层设计是一套用于通信的程序而不是别的东西。
目前我还没有看到有人讨论过这个问题。这可以追溯到机器人或智能的本质。
当我们谈论机器人时,最重要的问题不是硬件设计,而是信息的处理。机器人需要什么信息、信息从哪里来、如何传递以及谁使用这些信息,是最重要的问题。
人不能像鸟一样飞,不能像鱼一样游,不能像马一样快,也不能像牛一样强壮,为什么称自己为万物之灵呢?
因为人有大脑,而且人脑处理的信息越来越复杂。
抛开物质不谈,从信息的角度来看,人类、动物、机器人之间有很多相似之处。
机器人由许多功能模块组成,它们之间需要协作才能形成一个有用的整体。机器人之间还需要协作才能形成有用的系统。协作离不开沟通。
需要什么样的信息、从哪里来并不是ROS首先关心的问题,因为这取决于机器人的应用场景。
因此,ROS首先需要解决的就是通信问题,即如何建立通信、如何通信、通信格式是什么等一系列具体问题。
带着这些问题,我们来看看ROS是如何设计的。
2.2 客户端库
实现通信的代码在ros_comm包中,如下。
clients文件夹里一共有127个文件,看起来是最大的一个包了。
现在我们来到了ROS的核心区域。