2010-01-10 6 views
17

Ich versuche, eine Anwendung zu schreiben, die Google-Protokollpuffer verwendet, um Daten (über eine Protokollverbindung von einer anderen Anwendung über eine TCP-Verbindung gesendet) zu deserialisieren. Das Problem besteht darin, dass es aussieht, als ob Protokollpuffer in Python nur Daten aus einer Zeichenfolge deserialisieren können. Da TCP keine wohldefinierten Nachrichtengrenzen hat und eine der Nachrichten, die ich zu empfangen versuche, ein wiederholtes Feld hat, weiß ich nicht, wie viele Daten zu versuchen und zu empfangen sind, bevor schließlich die zu deserialisierende Zeichenfolge übergeben wird.Wie verwende ich Python und Google Protocol Buffers zur Deserialisierung von Daten über TCP gesendet

Gibt es in Python irgendwelche guten Praktiken dafür?

Antwort

36

Schreiben Sie nicht nur die serialisierten Daten in den Socket. Senden Sie zuerst ein Feld fester Größe, das die Länge des serialisierten Objekts enthält.

Die Sendeseite ist ungefähr:

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

Und die recv'ing Seite wird so etwas wie:

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

Dies ist ein gemeinsames Design-Muster für die Socket-Programmierung. Die meisten Designs erweitern die Over-the-Wire-Struktur ein Typfeld als auch zu schließen, so dass Ihre Empfangsseite etwas wird wie:

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) 

Sie am Ende mit einem Over-the-Wire-Nachrichtenformat, das wie folgt aussieht:

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

Dies ist eine sinnvolle Aufgabe, das Kabelprotokoll gegen unvorhergesehene Anforderungen zukunftssicher zu machen. Es ist ein Type-Length-Value Protokoll, das Sie wieder und wieder und wieder in Netzwerkprotokollen finden.

+1

+1 für eine unglaublich detaillierte und tolle Antwort. Vielen Dank!! – jathanism

+2

Die Verwendung von 'struct.pack (" H ", len (data))' führt zu einer wichtigen Konsequenz: Die Daten müssen weniger als 65536 Bytes lang sein. Sie können die maximal zulässige Größe der Daten erhöhen, indem Sie ein unsigned long long anstelle von 'Q' (maximale Größe = 18000 Petabytes) verwenden. – Flimm

4

um auf J.J.s (völlig korrekte) Antwort zu erweitern, hat die Protobuf-Bibliothek keine Möglichkeit herauszufinden, wie lange Nachrichten allein sind, oder um herauszufinden, welche Art von Protobuf-Objekt gesendet wird *. Also muss die andere Anwendung, die Daten sendet, bereits etwas ähnliches tun.

Wenn ich dies tun musste, implementiert ich eine Lookup-Tabelle:

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

... und tat im Wesentlichen, was J. J. tat, aber ich hatte auch eine Hilfsfunktion:

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

... die ich rief, um die Zeichenkette in ein protobuf Objekt zu machen.

(*) Ich denke, dass es möglich ist, diese Runde zu erhalten, indem bestimmte Nachrichten in einem Container Nachricht

+0

Beide Antworten sind gut, aber Frymasters nicht auf Kapselung ist (nach mir) der Weg nach vorne. –

0

Einen weiteren Aspekts Einkapselung zu berücksichtigen (für einen einfacheren Fall allerdings) ist, wo Sie eine einzelne TCP-Verbindung für eine einzelne Nachricht verwenden . In diesem Fall können Sie, solange Sie wissen, was die erwartete Nachricht ist (oder Union Types verwenden, um den Nachrichtentyp zur Laufzeit zu bestimmen), die TCP-Verbindung als 'Start'-Trennzeichen öffnen und das Verbindungsereignis schließen endgültiger Begrenzer. Dies hat den Vorteil, dass Sie die gesamte Nachricht schnell erhalten (während in anderen Fällen der TCP-Stream für eine Zeit gehalten werden kann, verzögert sich der Empfang Ihrer gesamten Nachricht). Wenn Sie dies tun, benötigen Sie kein explizites In-Band-Framing, da die Lebensdauer der TCP-Verbindung selbst als Frame fungiert.

Verwandte Themen