protobuf反序列化多條消息問題


Protocol buffergoogle開源的又一利器,主要用於結構化數據存儲與數據交換,類似於XML,但相比XML,它更小、更快、也更簡單,只需使用protobuf對數據結構進行一次描述,即可利用各種不同的語言(包括C++javapython等,同時還包括很多種語言的綁定插件)從各種不同的數據流(文件、字符串流等)對結構化數據輕松讀寫。但由於其使用二進制存儲,相比XML,其可讀性差。

 

Protocol buffergoogle code上的頁面,源碼及文檔都可以從中獲取。

http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/overview.html

 

運行了源碼中附帶的AddressBook的例子,發現protobuf的確很強大,使用起來也很方便,只需在.proto文件中描述數據結構,然后用protoc生成對應的cpp文件(包含描述結構對應的類),便可通過SerializeToOstream將結構數據寫到輸出流中,通過ParseFromIstream從輸入流中提取(還有相應的字符串流讀寫方法)。

 

Protobuf通過repeated提供類似於數組的結構。

 

在一個key/value存儲的系統中,我為每個key/value的位置建了一項索引,索引的內容包括魔數、標志、keykey對應的偏移及大小,在.proto文件中定義如下:

message Index {

         required int32 magic = 1;

         required int32 flag = 2;

         required uint64 key = 3;

         required uint64 offset = 4;

         required uint64 size = 5;

}

由於索引項有多個,故應在定義一個Message,里面包含多個Index,如:

message IndexArray {

         repeated Index idx = 1;

}

 

但這樣就存在一個問題,當對個index被添加到IndexArray之后,每次讀取其中一個index都需要先從文件流中分析出一個IndexArray,再獲取某一項index的信息,但某個index都應該能被快速隨機訪問,而不需要先讀出所有的index

 

於是我想protobuf應該是根據流的當前位置來向輸出流中序列化結構的,同時在parse的時候也是根據輸入流的當前位置起開始分析;於是只要依次向輸出流Serialize多個結構,在提取時,先將輸入流定位到指定偏移就能Parse出指定的index。於是做了個小測試:

 

/* 依次向文件流中序列化10index */

fstream output(argv[1], ios::out | ios::trunc | ios::binary);

int i;

for(i = 0; i < 10; i++) {

         Index idx;

         idx.set_flag(0);

         idx.set_magic(1);

         idx.set_key(0);

         idx.set_offset(i);

         idx.set_size(1);

         idx.SerializeToOstream(&output);

}

output.close();

        

/* 依次從文件流中提取10index */       

fstream input(argv[1], ios::in | ios::binary);

int I;

for(i = 0; i < 10; i++) {

         Index idx;

         idx.ParseFromIstream(&input);

         cout << idx.offset() << endl;

}

 

測試后發現,再向流種序列化10index后,文件大小100B(因sint采用vint格式存儲的,每個index10B,所以總的大小跟預想是一樣的),但在提取時,每次只能提取到最后一個結構(offset9),之后提示9次不能parse對象,即parse依次后,輸入流就到了末尾,而且得到的是最后一個結構的信息。將測試例子改編了多個版本,得出的結論都是一樣的,說明ParseFromIstream並不像預想的一樣從流的當前偏移提取一個對象。

 

對於protocol在數據轉換與存儲上的表現我很滿意,而且將其應用於網絡數據的交換可以極大的減小編程工作量。但其序列化過程與提取過程為何不能對應起來,難道每次就只能向流種序列化一個結構?對於我剛說到的應用應該很普遍(即使同種結構可以使用repeated,想流中序列化多種不同的結構還是會遇到上述問題),為何protocol buffer不能處理呢,我忽略了些什么?



2011年12月16日補充:

http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/techniques.html

Streaming Multiple Messages

If you want to write multiple messages to a single file or stream, it is up to you to keep track of where one message ends and the next begins. The Protocol Buffer wire format is not self-delimiting, so protocol buffer parsers cannot determine where a message ends on their own. The easiest way to solve this problem is to write the size of each message before you write the message itself. When you read the messages back in, you read the size, then read the bytes into a separate buffer, then parse from that buffer. (If you want to avoid copying bytes to a separate buffer, check out the CodedInputStream class (in both C++ and Java) which can be told to limit reads to a certain number of bytes.)


從上述描述可以看出,PB的格式是非自描述性的,ParseFromIstream會讀完整個流,parse里面的key-value對,並依次根據key的值(key由字段號標識)設置value。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM