Как использовать буферы Python и Google Protocol для десериализации данных, передаваемых по TCP
Я пытаюсь написать приложение, которое использует буферы протокола Google для десериализации данных (отправленных из другого приложения с использованием буферов протокола) по TCP-соединению. Проблема в том, что похоже, что буферы протокола в Python могут только десериализовать данные из строки. Поскольку TCP не имеет четко определенных границ сообщений, и одно из сообщений, которые я пытаюсь получить, имеет повторяющееся поле, я не буду знать, сколько данных нужно пытаться и получать, прежде чем, наконец, передать десериализуемую строку.
Есть ли хорошие методы для этого в Python?
Ответы
Ответ 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-соединения действует как сам кадр.