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

17

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

Есть ли хорошие методы для этого в Python?

    
задан Jack Edmonds 10.01.2010 в 19:45
источник

3 ответа

36

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

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

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

И сторона recving станет чем-то вроде:

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

ответ дан J.J. 10.01.2010 в 20:06
источник
  • +1 за невероятно подробный и устрашающий ответ. Спасибо!! –  jathanism 11.01.2010 в 16:30
  • Использование struct.pack («H», len (data)) приводит к важному последствию: данные должны быть меньше 65536 байтов. Вы можете увеличить максимально допустимый размер данных, используя unsigned long long вместо Q (максимальный размер = 18000 петабайт). –  Flimm 06.02.2013 в 17:59
4

, чтобы развернуть (полностью правильный) ответ JJ, библиотека protobuf имеет no way , чтобы определить, как долго сообщения сами по себе, или определить, какой тип объекта 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.

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

    
ответ дан frymaster 11.01.2010 в 16:26
источник
  • Оба ответа хороши, но frymasters не на инкапсуляции (по мне) путь вперед. –  Sven Almgren 15.10.2013 в 16:46
0

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

    
ответ дан meowsqueak 27.10.2013 в 22:22
источник