Как получать и собирать массивы байтов переменной длины в сокетах Python?

#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 , нет? Я реализовал аналогичный цикл, и он работает хорошо. Спасибо! 😉