理解了序列化,再回到ROS。我們發(fā)現(xiàn),ROS沒(méi)有采用第三方的序列化工具,而是選擇自己實(shí)現(xiàn),代碼在roscpp_core項(xiàng)目下的roscpp_serialization中,見(jiàn)下圖。這個(gè)功能涉及的代碼量不是很多。
為什么ROS不使用現(xiàn)成的序列化工具或者庫(kù)呢?可能ROS誕生的時(shí)候(2007年),有些序列化庫(kù)可能還不存在(protobuf誕生于2008年),更有可能是ROS的創(chuàng)造者認(rèn)為當(dāng)時(shí)沒(méi)有合適的工具。
1.2.1 serialization.h
核心的函數(shù)都在serialization.h里,簡(jiǎn)而言之,里面使用了C語(yǔ)言標(biāo)準(zhǔn)庫(kù)的memcpy函數(shù)把消息拷貝到流中。
下面來(lái)看一下具體的實(shí)現(xiàn)。
序列化功能的特點(diǎn)是要處理很多種數(shù)據(jù)類型,針對(duì)每種具體的類型都要實(shí)現(xiàn)相應(yīng)的序列化函數(shù)。
為了盡量減少代碼量,ROS使用了模板的概念,所以代碼里有一堆的template。
從后往前梳理,先看Stream這個(gè)結(jié)構(gòu)體吧。在C++里結(jié)構(gòu)體和類基本沒(méi)什么區(qū)別,結(jié)構(gòu)體里也可以定義函數(shù)。
Stream翻譯為流,流是一個(gè)計(jì)算機(jī)中的抽象概念,前面我們提到過(guò)字節(jié)流,它是什么意思呢?
在需要傳輸數(shù)據(jù)的時(shí)候,我們可以把數(shù)據(jù)想象成傳送帶上連續(xù)排列的一個(gè)個(gè)被傳送的物體,它們就是一個(gè)流。
更形象的,可以想象磁帶或者圖靈機(jī)里連續(xù)的紙帶。在文件讀寫(xiě)、使用串口、網(wǎng)絡(luò)Socket通信等領(lǐng)域,流經(jīng)常被使用。例如我們常用的輸入輸出流:
cout<<"helllo"; 由于使用很多,流的概念也在演變。想了解更多可以看這里。
struct Stream
{
// Returns a pointer to the current position of the stream
inline uint8_t* getData() { return data_; }
// Advances the stream, checking bounds, and returns a pointer to the position before it was advanced.
// throws StreamOverrunException if len would take this stream past the end of its buffer
ROS_FORCE_INLINE uint8_t* advance(uint32_t len)
{
uint8_t* old_data = data_;
data_ += len;
if (data_ > end_)
{
// Throwing directly here causes a significant speed hit due to the extra code generated for the throw statement
throwStreamOverrun();
}
return old_data;
}
// Returns the amount of space left in the stream
inline uint32_t getLength() { return static_cast< uint32_t >(end_ - data_); }
protected:
Stream(uint8_t* _data, uint32_t _count) : data_(_data), end_(_data + _count) {}
private:
uint8_t* data_;
uint8_t* end_;
};
注釋表明Stream是個(gè)基類,輸入輸出流IStream和OStream都繼承自它。
Stream的成員變量data_是個(gè)指針,指向序列化的字節(jié)流開(kāi)始的位置,它的類型是uint8_t。
在Ubuntu系統(tǒng)中,uint8_t的定義是typedef unsigned char uint8_t;
所以u(píng)int8_t就是一個(gè)字節(jié),可以用size_of()函數(shù)檢驗(yàn)。data_指向的空間就是保存字節(jié)流的。
輸出流類OStream用來(lái)序列化一個(gè)對(duì)象,它引用了serialize函數(shù),如下。
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用來(lái)反序列化一個(gè)字節(jié)流,它引用了deserialize函數(shù),如下。
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函數(shù)和deserialize函數(shù)就是改變數(shù)據(jù)形式的地方,它們的定義在比較靠前的地方。它們都接收兩個(gè)模板,都是內(nèi)聯(lián)函數(shù),然后里面沒(méi)什么東西,只是又調(diào)用了Serializer類的成員函數(shù)write和read。所以,serialize和deserialize函數(shù)就是個(gè)二道販子。
// 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< T >::write(stream, t);
}
// Deserialize an object. Stream here should normally be a ros::serialization::IStream
template< typename T, typename Stream >
inline void deserialize(Stream& stream, T& t)
{
Serializer< T >::read(stream, t);
}
所以,我們來(lái)分析Serializer類,如下。我們發(fā)現(xiàn),write和read函數(shù)又調(diào)用了類型里的serialize函數(shù)和deserialize函數(shù)。
頭別暈,這里的serialize和deserialize函數(shù)跟上面的同名函數(shù)不是一回事。
注釋中說(shuō):“Specializing the Serializer class is the only thing you need to do to get the ROS serialization system to work with a type”(要想讓ROS的序列化功能適用于其它的某個(gè)類型,你唯一需要做的就是特化這個(gè)Serializer類)。
這就涉及到的另一個(gè)知識(shí)點(diǎn)——模板特化(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 >::param_type t)
{
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 >::reference t)
{
t.deserialize(stream.getData());
}
// Determine the serialized length of an object.
inline static uint32_t serializedLength(typename boost::call_traits< T >::param_type t)
{
return t.serializationLength();
}
};
接著又定義了一個(gè)帶參數(shù)的宏函數(shù)ROS_CREATE_SIMPLE_SERIALIZER(Type),然后把這個(gè)宏作用到了ROS中的10種基本數(shù)據(jù)類型,分別是:uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t, float, double。
說(shuō)明這10種數(shù)據(jù)類型的處理方式都是類似的。看到這里大家應(yīng)該明白了,write和read函數(shù)都使用了memcpy函數(shù)進(jìn)行數(shù)據(jù)的移動(dòng)。
注意宏定義中的template<>語(yǔ)句,這正是模板特化的標(biāo)志,關(guān)鍵詞template后面跟一對(duì)尖括號(hào)。
關(guān)于模板特化可以看這里。
#define ROS_CREATE_SIMPLE_SERIALIZER(Type)
template< > struct Serializer Type >
{
template< typename Stream > inline static void write(Stream& stream, const Type v)
{
memcpy(stream.advance(sizeof(v)), &v, sizeof(v) );
}
template< typename Stream > inline static void read(Stream& stream, Type& v)
{
memcpy(&v, stream.advance(sizeof(v)), sizeof(v) );
}
inline static uint32_t serializedLength(const Type&)
{
return sizeof(Type);
}
};
ROS_CREATE_SIMPLE_SERIALIZER(uint8_t)
ROS_CREATE_SIMPLE_SERIALIZER(int8_t)
ROS_CREATE_SIMPLE_SERIALIZER(uint16_t)
ROS_CREATE_SIMPLE_SERIALIZER(int16_t)
ROS_CREATE_SIMPLE_SERIALIZER(uint32_t)
ROS_CREATE_SIMPLE_SERIALIZER(int32_t)
ROS_CREATE_SIMPLE_SERIALIZER(uint64_t)
ROS_CREATE_SIMPLE_SERIALIZER(int64_t)
ROS_CREATE_SIMPLE_SERIALIZER(float)
ROS_CREATE_SIMPLE_SERIALIZER(double)
對(duì)于其它類型的數(shù)據(jù),例如bool、std::string、std::vector、ros::Time、ros::Duration、boost::array等等,它們各自的處理方式有細(xì)微的不同,所以不再用上面的宏函數(shù),而是用模板特化的方式每種單獨(dú)定義,這也是為什么serialization.h這個(gè)文件這么冗長(zhǎng)。
對(duì)于int、double這種單個(gè)元素的數(shù)據(jù),直接用上面特化的Serializer類中的memcpy函數(shù)實(shí)現(xiàn)序列化。
對(duì)于vector、array這種多個(gè)元素的數(shù)據(jù)類型怎么辦呢?方法是分成幾種情況,對(duì)于固定長(zhǎng)度簡(jiǎn)單類型的(fixed-size simple types),還是用各自特化的Serializer類中的memcpy函數(shù)實(shí)現(xiàn),沒(méi)啥太大區(qū)別。
對(duì)于固定但是類型不簡(jiǎn)單的(fixed-size non-simple types)或者既不固定也不簡(jiǎn)單的(non-fixed-size, non-simple types)或者固定但是不簡(jiǎn)單的(fixed-size, non-simple types),用for循環(huán)遍歷,一個(gè)元素一個(gè)元素的單獨(dú)處理。
那怎么判斷一個(gè)數(shù)據(jù)是不是固定是不是簡(jiǎn)單呢?這是在roscpp_traits文件夾中的message_traits.h完成的。
其中采用了萃取Type Traits,這是相對(duì)高級(jí)一點(diǎn)的編程技巧了,筆者也不太懂。
對(duì)序列化的介紹暫時(shí)就到這里了,有一些細(xì)節(jié)還沒(méi)講,等筆者看懂了再補(bǔ)。
-
機(jī)器人
+關(guān)注
關(guān)注
213文章
29475瀏覽量
211532 -
操作系統(tǒng)
+關(guān)注
關(guān)注
37文章
7081瀏覽量
124940 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4368瀏覽量
64181 -
ROS
+關(guān)注
關(guān)注
1文章
284瀏覽量
17556
發(fā)布評(píng)論請(qǐng)先 登錄
如何使用Serde進(jìn)行序列化和反序列化
Java序列化的機(jī)制和原理
c語(yǔ)言序列化和反序列化有何區(qū)別
SpringMVC JSON框架的自定義序列化與反序列化
java序列化和反序列化范例和JDK類庫(kù)中的序列化API
static屬性為什么不會(huì)被序列化
C#實(shí)現(xiàn)對(duì)象序列化的三種方式是什么

python序列化對(duì)象
ROS機(jī)器人操作系統(tǒng)的實(shí)現(xiàn)原理(上)

ROS機(jī)器人操作系統(tǒng)的實(shí)現(xiàn)原理(下)
什么是序列化 為什么要序列化

評(píng)論