注釋表明Stream是個基類,輸入輸出流IStream和OStream都繼承自它。
Stream的成員變量data_是個指針,指向序列化的字節流開始的位置,它的類型是uint8_t。
在Ubuntu系統中,uint8_t的定義是typedef unsigned char uint8_t;
所以uint8_t就是一個字節,可以用size_of()函數檢驗。data_指向的空間就是保存字節流的。
輸出流類OStream用來序列化一個對象,它引用了serialize函數,如下。
struct OStream : public Stream
{
static const StreamType stream_type = stream_types::Output;
OStream(uint8_t* data, uint32_t count) : Stream(data, count) {}
/* Serialize an item to this output stream*/
template<typename T>
ROS_FORCE_INLINE void next(const T& t)
{
serialize(*this, t);
}
template<typename T>
ROS_FORCE_INLINE OStream& operator<<(const T& t)
{
serialize(*this, t);
return *this;
}
};
輸入流類IStream用來反序列化一個字節流,它引用了deserialize函數,如下。
struct ROSCPP_SERIALIZATION_DECL IStream : public Stream
{
static const StreamType stream_type = stream_types::Input;
IStream(uint8_t* data, uint32_t count) : Stream(data, count) {}
/* Deserialize an item from this input stream */
template<typename T>
ROS_FORCE_INLINE void next(T& t)
{
deserialize(*this, t);
}
template<typename T>
ROS_FORCE_INLINE IStream& operator>>(T& t)
{
deserialize(*this, t);
return *this;
}
};
自然,serialize函數和deserialize函數就是改變數據形式的地方,它們的定義在比較靠前的地方。它們都接收兩個模板,都是內聯函數,然后里面沒什么東西,只是又調用了Serializer類的成員函數write和read。所以,serialize和deserialize函數就是個二道販子。
// Serialize an object. Stream here should normally be a ros::serialization::OStream
template<typename T, typename Stream>
inline void serialize(Stream& stream, const T& t)
{
Serializer
所以,我們來分析Serializer類,如下。我們發現,write和read函數又調用了類型里的serialize函數和deserialize函數。
頭別暈,這里的serialize和deserialize函數跟上面的同名函數不是一回事。
注釋中說:“Specializing the Serializer class is the only thing you need to do to get the ROS serialization system to work with a type”(要想讓ROS的序列化功能適用于其它的某個類型,你唯一需要做的就是特化這個Serializer類)。
這就涉及到的另一個知識點——模板特化(template specialization)。
template<typename T> struct Serializer
{
// Write an object to the stream. Normally the stream passed in here will be a ros::serialization::OStream
template<typename Stream>
inline static void write(Stream& stream, typename boost::call_traits
{
t.serialize(stream.getData(), 0);
}
// Read an object from the stream. Normally the stream passed in here will be a ros::serialization::IStream
template<typename Stream>
inline static void read(Stream& stream, typename boost::call_traits
{
t.deserialize(stream.getData());
}
// Determine the serialized length of an object.
inline static uint32_t serializedLength(typename boost::call_traits
{
return t.serializationLength();
}
};
接著又定義了一個帶參數的宏函數ROS_CREATE_SIMPLE_SERIALIZER(Type),然后把這個宏作用到了ROS中的10種基本數據類型,分別是:uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t, float, double。
說明這10種數據類型的處理方式都是類似的。看到這里大家應該明白了,write和read函數都使用了memcpy函數進行數據的移動。
注意宏定義中的template<>語句,這正是模板特化的標志,關鍵詞template后面跟一對尖括號。
關于模板特化可以看這里。
#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函數實現序列化。
對于vector、array這種多個元素的數據類型怎么辦呢?方法是分成幾種情況,對于固定長度簡單類型的(fixed-size simple types),還是用各自特化的Serializer類中的memcpy函數實現,沒啥太大區別。
對于固定但是類型不簡單的(fixed-size non-simple types)或者既不固定也不簡單的(non-fixed-size, non-simple types)或者固定但是不簡單的(fixed-size, non-simple types),用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最核心的地帶。
-
機器人
+關注
關注
212文章
29419瀏覽量
211261 -
操作系統
+關注
關注
37文章
7057瀏覽量
124870 -
ROS
+關注
關注
1文章
284瀏覽量
17529
發布評論請先 登錄
ROS讓機器人開發更便捷,基于RK3568J+Debian系統發布!
【IntoRobot Atom試用體驗】解決Atom搭建機器人操作系統問題(二)
請問能分享ROS機器人操作系統的一些資料嗎?
【ROS RIKIBOT基礎--使用系列 第一章節】ROS機器人硬件系統 精選資料分享
ROS讓機器人開發更便捷,基于RK3568J+Debian系統發布!
ROS是什么?機器人操作系統ROS的介紹

評論