Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

чтобы разложить на 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.

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

Ответ 3

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