Как использовать Python и буфер протокола Google для десериализации данных, отправляемых по TCP - PullRequest
17 голосов
/ 10 января 2010

Я пытаюсь написать приложение, которое использует буферы протокола Google для десериализации данных (отправленных из другого приложения с использованием буферов протокола) по TCP-соединению. Проблема в том, что похоже, что буферы протокола в Python могут десериализовать только данные из строки. Поскольку в TCP нет четко определенных границ сообщений, а одно из сообщений, которые я пытаюсь получить, имеет повторяющееся поле, я не буду знать, сколько данных нужно попытаться получить, прежде чем окончательно передать строку для десериализации.

Есть ли хорошие практики для этого в Python?

Ответы [ 3 ]

36 голосов
/ 10 января 2010

Не просто записывайте сериализованные данные в сокет. Сначала отправьте поле фиксированного размера, содержащее длину сериализованного объекта.

Отправляющая сторона примерно:

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

И возвращающаяся сторона становится чем-то вроде:

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

Это общий шаблон проектирования для программирования сокетов. Большинство проектов расширяют проводную структуру, чтобы включить поле типа, так что ваша принимающая сторона становится чем-то вроде:

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 , который вы найдете снова и снова и снова в сетевых протоколах.

4 голосов
/ 11 января 2010

Чтобы раскрыть (полностью правильный) ответ JJ, библиотека 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. * ​​1011 *

(*) Я думаю, что можно обойти это, заключив конкретные сообщения в контейнерное сообщение

0 голосов
/ 28 октября 2013

Другой аспект, который следует учитывать (хотя и в более простом случае), - это использование одного TCP-соединения для одного сообщения. В этом случае, если вы знаете, что такое ожидаемое сообщение (или используете Типы объединения для определения типа сообщения во время выполнения), вы можете использовать открытое соединение TCP в качестве разделителя «start», и событие закрытия соединения в качестве окончательного разделителя. Преимущество этого заключается в том, что вы получите все сообщение быстро (тогда как в других случаях поток TCP может удерживаться какое-то время, что задерживает получение всего вашего сообщения). Если вы сделаете это, вам не понадобится никакого явного внутриполосного кадрирования, поскольку время жизни соединения TCP действует как сам фрейм.

...