2010-01-10 23 views
17

我正在嘗試編寫一個應用程序,它使用Google協議緩衝區通過TCP連接反序列化數據(從另一個使用協議緩衝區的應用程序發送)。問題在於,它看起來好像Python中的協議緩衝區只能反序列化字符串中的數據。由於TCP沒有明確定義的消息邊界,並且我嘗試接收的消息中有一個消息具有重複的字段,所以在最終傳遞要反序列化的字符串之前,我不知道要嘗試接收多少數據。如何使用Python和Google協議緩衝區來反序列化通過TCP發送的數據

在Python中這樣做有什麼好的做法嗎?

回答

36

不要只將序列化的數據寫入套接字。首先發送包含序列化對象長度的固定大小字段。

發送側大致是:

socket.write(struct.pack("H", len(data)) #send a two-byte size field 
socket.write(data) 

而且recv'ing一側變成類似:

dataToRead = struct.unpack("H", socket.read(2))[0]  
data = socket.read(dataToRead) 

這是socket編程一個常用的設計模式。大多數設計延長了過度的線結構,包括類型字段一樣,所以你的接收端變得像:

type = socket.read(1)         # get the type of msg 
dataToRead = struct.unpack("H", socket.read(2))[0] # get the len of the msg 
data = socket.read(dataToRead)      # read the msg 

if TYPE_FOO == type: 
    handleFoo(data) 

elif TYPE_BAR == type: 
    handleBar(data) 

else: 
    raise UnknownTypeException(type) 

你,看起來像一個以上的非線消息格式結束:

struct { 
    unsigned char type; 
    unsigned short length; 
    void *data; 
} 

這對於未來防線的協議來說是合理的,可以防範未預見到的需求。這是一個Type-Length-Value協議,你會一次又一次地在網絡協議中找到它。

+1

+1令人難以置信的詳細和真棒的答案。謝謝!! – jathanism 2010-01-11 15:30:50

+2

使用struct.pack(「H」,len(data))會產生一個重要結果:數據長度必須小於65536字節。你可以通過使用一個無符號long long而不是'Q'(最大大小= 18000 PB)來增加允許的最大數據大小。 – Flimm 2013-02-06 16:59:48

4

爲了擴大J.J.(完全正確)的答案,protobuf庫有沒有辦法來計算消息的長度,或者計算出發送什麼類型的protobuf對象*。因此,向您發送數據的其他應用程序必須已經做了這樣的事情。

當我不得不這樣做,我實現了一個查找表:

messageLookup={0:foobar_pb2.MessageFoo,1:foobar_pb2.MessageBar,2:foobar_pb2.MessageBaz} 

...並基本上就是J·J·做沒有,但我也有一個幫手功能:

def parseMessage(self,msgType,stringMessage): 
     msgClass=messageLookup[msgType] 
     message=msgClass() 
     message.ParseFromString(stringMessage) 
     return message 

...我打電話把字符串變成一個protobuf對象。

(*),我認爲這是可能通過封裝特定消息的容器消息

+0

這兩個答案都很好,但沒有封裝的炸爐匠(根據我)是前進的道路。 – 2013-10-15 14:46:37

0

內另一個方面考慮(雖然對於簡單的情況下),以避開這是你使用單條信息的單個TCP連接。在這種情況下,只要您知道預期的消息是什麼(或使用Union Types來確定運行時的消息類型),則可以將TCP連接打開爲「開始」分隔符,並將連接關閉事件作爲最後的分隔符。這樣做的好處是您可以快速收到整個郵件(而在其他情況下,TCP流可以暫時保留一段時間,延遲收到您的整個郵件)。如果你這樣做,你不需要任何明確的帶內成幀,因爲TCP連接的生命週期本身就是一個幀。