#python #arrays #sockets #protocol-buffers
#python #массивы #сокеты #протокол-буферы
Вопрос:
Я пытаюсь отправить большие массивы байтов класса Protobuf с клиента Java на сервер Python. Однако они имеют переменную длину, потому что иногда я отправляю байты объекта из ClassA
, а иногда из ClassB
.
У меня есть сервер сокетов Python со следующим кодом внутри функции, которая прослушивает сокет:
byte_array = bytearray()
# receive the data in small chunks and print it
while True:
data = connection.recv(64)
if data:
# output received data
logger.debug("Data: %s" % data)
byte_array.extend(data)
else:
# no more data -- quit the loop
logger.debug("no more data.")
break
logger.info("Generating response...")
connection.send(generate_response(byte_array))
logger.info("Sent response.")
Я собираю большой массив байтов, который я получаю, объединяя 64 байта по мере их получения.
Однако, когда массив байтов полностью передан и отправлять больше нечего, сервер зависает на connection.recv
линии.
Я читал, что это происходит потому, что recv
блокируется до тех пор, пока либо он что-то не получит, либо соединение не будет закрыто. Однако я не хочу закрывать соединение, потому что я хочу отправить свой ответ обратно клиенту после обработки всего массива байтов.
Я хочу знать, когда массив байтов, который я получаю, был полностью передан, чтобы я мог избежать этой блокировки.
Я могу придумать три варианта:
- Установите предварительно определенный «конечный» байт, ограничивающий конец массива байтов.
- Отправьте размер массива байтов заранее, а затем вместо
while True
у меня естьwhile bytes_read < expected_bytes
цикл. - Установите тайм-аут для соединения, и я предполагаю, что когда наступает тайм-аут, это означает, что все уже отправлено.
Я склоняюсь к первому варианту, однако я не знаю, какой символ мне следует использовать для завершения массива байтов и как его прочитать в моем коде Python.
Есть какие-нибудь предложения?
Спасибо.
Комментарии:
1. мне пришлось сделать то же самое, но наоборот, в итоге я отправил небольшой пакет «header» с размером и другими метаданными, которые имели постоянный размер, а затем получил переменную длину, используя данные из заголовка, и он хорошо работал даже для большого количества пакетов
Ответ №1:
Я бы лично выбрал второй вариант (в сочетании с разумным таймаутом для обслуживания злых клиентов, которые отправляют только половину файла и зависают там навсегда). Символ-разделитель хорош, если вы можете абсолютно гарантировать, что он уникален в вашем потоке (но вам все равно нужен тайм-аут).
Если вы не можете гарантировать уникальность вашего разделителя, отправка размера, который должен ожидать клиент, решает проблему. Если ваши метаданные дополнены до фиксированной длины, вам не нужно беспокоиться о разделителях и их обнаружении.
Комментарии:
1. Спасибо! Да, я согласен, что лучшим вариантом является второй, потому что, поскольку это данные, сгенерированные Protobuf, я понятия не имею, какие там байты. Я поддержал ваш ответ, но поскольку в нем есть код @AshishGhodake, я объявлю его как правильный ответ. Приветствия.
2. Это нормально, если ваша проблема решена. Возможно, вы также захотите взглянуть на модуль zeromq. Он предлагает более высокий уровень абстракции — например, заботясь о доставке сообщений в полном объеме. Его интерфейс очень похож на сокет, но он заботится обо всех рутинных вещах, которые вам нужно учитывать при использовании сокетов — например, о сборке частичных сообщений и обеспечении того, чтобы они действительно доставлялись полностью.
Ответ №2:
Вариант 1 :
Итак, для первого варианта вы могли бы установить конечный байт, который нигде не будет встречаться в вашем реальном сообщении. Вы можете создать строку, например, для «END», преобразовать ее в массив байтов и отправить через вашу Java-программу. После получения вы могли бы использовать decode (), чтобы преобразовать его в строку и сравнить. :
Примечание: Конечный байт, который вы будете отправлять, должен быть меньше или равен размеру чанка для декодирования и получения точного конечного байта.
byte_array = bytearray()
# receive the data in small chunks and print it
while True:
data = connection.recv(64)
command = data.decode()
if command != "END":
# output received data
logger.debug("Data: %s" % data)
byte_array.extend(data)
else:
# no more data -- quit the loop
logger.debug("no more data.")
break
logger.info("Generating response...")
connection.send(generate_response(byte_array))
logger.info("Sent response.")
Вариант 2 :
Для второго варианта вам нужно будет изменить цикл while, чтобы он выполнялся в соответствии с метаданными. Я рассмотрел, что метаданные будут состоять из первого фрагмента, который будет представлять собой количество отправленных фрагментов.Это может выглядеть примерно так :
byte_array = bytearray()
# receive the data in small chunks and print it
loop_count = 0
count = 1
meta = 1
while loop_count >= count:
data = connection.recv(64)
if(meta):
count = int(data.decode()) # first chunk is the number of chunks that will be sent
meta = 0
logger.debug("Data: %s" % data)
byte_array.extend(data)
loop_count = loop_count 1
else:
# no more data
logger.debug("no more data.")
logger.info("Generating response...")
connection.send(generate_response(byte_array))
logger.info("Sent response.")
Вариант 3 :
Это также будет работать нормально, если вы уверены, что не будет задержки в сети, и единственной проблемой будет то, что вашей Java-программе придется ждать ответа от сервера python, пока не истечет время ожидания
Вариант 4 :
Вы могли бы использовать неблокирующий сокет, который будет работать до тех пор, пока он не будет получен в течение заранее определенного периода времени. Хотя я не рекомендую это для вашей ситуации, вы можете прочитать об этом и посмотреть, соответствует ли это вашим потребностям.
Комментарии:
1. Неблокирующий сокет на самом деле не соответствует тому, что мне нужно, но я согласен с другими вариантами. Как я упоминал в комментарии, я считаю, что вариант 2 подходит мне лучше всего. В начале цикла вы хотели написать
loop_count <= count
, нет? Я реализовал аналогичный цикл, и он работает хорошо. Спасибо! 😉