2010-01-10 17 views
17

Estoy intentando escribir una aplicación que utiliza búferes de protocolo de Google para deserializar datos (enviados desde otra aplicación utilizando búferes de protocolo) a través de una conexión TCP. El problema es que parece que los búferes de protocolo en Python solo pueden deserializar los datos de una cadena. Dado que TCP no tiene límites de mensajes bien definidos y uno de los mensajes que estoy tratando de recibir tiene un campo repetido, no sabré cuántos datos debo tratar de recibir antes de pasar la cadena a deserializar.Cómo usar Python y los búferes de protocolo de Google para deserializar los datos enviados a través de TCP

¿Hay alguna buena práctica para hacer esto en Python?

Respuesta

36

No solo escriba los datos serializados en el zócalo. Primero envíe un campo de tamaño fijo que contenga la longitud del objeto serializado.

El lado emisor es aproximadamente:

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

Y el lado recv'ing se convierte en algo así como:

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

Este es un patrón de diseño común para la programación del zócalo. La mayoría de los diseños se extienden la estructura sobre el alambre para incluir un campo de tipo, así, por lo que su lado receptor se convierte en algo así como:

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) 

Se termina con un formato de mensaje sobre el alambre que se parece a:

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

Esto hace un trabajo razonable de futuro para proteger el protocolo de cable contra imprevistos. Es un protocolo Type-Length-Value, que encontrará una y otra vez en los protocolos de red.

+1

+1 para una respuesta increíblemente detallada y sorprendente. ¡¡Gracias!! – jathanism

+2

Usar 'struct.pack (" H ", len (data))' conduce a una consecuencia importante: los datos deben tener menos de 65536 bytes de longitud. Puede aumentar el tamaño máximo permitido de los datos utilizando una longitud larga sin signo en lugar de 'Q' (tamaño máximo = 18000 petabytes). – Flimm

4

para expandir la respuesta de J.J. (totalmente correcta), la biblioteca protobuf tiene de ninguna manera para calcular cuánto tiempo son los mensajes por sí mismos, o para determinar qué tipo de objeto protobuf se está enviando *. Entonces la otra aplicación que te está enviando datos ya debe estar haciendo algo como esto.

Cuando tuve que hacer esto, yo implementado una tabla de búsqueda:

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

... y lo hizo esencialmente lo que JJ sí, pero también tenía una función auxiliar:

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

... que llamé para convertir la cadena en un objeto protobuf.

(*) Creo que es posible obtener esta ronda por encapsulación de mensajes específicos dentro de un mensaje contenedor

+0

Ambas respuestas son buenas, pero los frymasters que no están encapsulados son (según yo) el camino a seguir. –

0

Otro aspecto a tener en cuenta (aunque por un caso más simple) es el que utiliza una única conexión TCP para un solo mensaje . En este caso, siempre que sepa cuál es el mensaje esperado (o use Union Types para determinar el tipo de mensaje en tiempo de ejecución), puede usar la conexión TCP abierta como el delimitador 'inicio' y el evento de cierre de conexión como el delimitador final. Esto tiene la ventaja de que recibirá todo el mensaje rápidamente (mientras que en otros casos, la transmisión TCP se puede mantener durante un tiempo, lo que retrasa la recepción de su mensaje completo). Si hace esto, no necesita ningún marco en banda explícito ya que la vida útil de la conexión TCP actúa como un marco en sí mismo.

Cuestiones relacionadas