Ошибка при распаковке структуры строки переменной длины через сокет - PullRequest
1 голос
/ 28 сентября 2019

У меня есть серверная и клиентская программы, и я пытаюсь отправлять сообщения между ними на основе указанного протокола и фиксированного размера буфера в 16 байтов.Процедура работает следующим образом:

  1. Для клиента определены два отдельных строковых выражения.
  2. Клиент упаковывает два выражения в одну структуру на основе протокола (используя кодировку UTF-8).
  3. Клиент отправляет упакованное выражение на сервер.
  4. Сервер получил упакованное выражение.
  5. Сервер распаковывает упакованное выражение.
  6. Сервер отправляет обратно (повторяет) первое выражение.
  7. Клиент получает первое выражение и печатает его (декодируется UTF-8).

По сути, это простая процедура эха, хотя я только отправляю обратно первое выражение длясейчас (я изменю это позже).

Вот картина, нарисованная картина протокола, который я должен использовать.Я упаковываю свою структуру в соответствии с этим.То есть первые два значения - это двухбайтовые int s, за которыми следуют выражение / строка переменной длины, затем еще один int, затем второе выражение / строка переменной длины.Таким образом, в соответствии с структурной документацией , это соответствует 2h{len(exp1)sh{len(exp1)}s.

Я также использую endianness сети .

Когда оба моих выраженияменьше 16 байт, программа работает нормально:

ВХОД КЛИЕНТА:

exp1 = "123456"
exp2 = "0000"

ВЫХОД СЕРВЕРА :

Waiting for connection...
Connected from ('127.0.0.1', 51150).
Server received 16 bytes from ('127.0.0.1', 51150). The message was b'\x00\x02\x00\x06123456\x00\x040000'.
The length of the first expression is 6. The length of the second expression is 4.
Bye!

ВЫХОД КЛИЕНТА:

Client received 123456.  # Correct

Но проблема в том, что когда клиент имеет вход больше 16 байт , я получаю ошибку распаковки на стороне сервера:

ВХОД КЛИЕНТА:

exp1 = "123456"
exp2 = "00001"  # <---- I've now added an extra "1" here (but it could be any character).

ВЫХОД СЕРВЕРА :

Waiting for connections...
Connected from ('127.0.0.1', 51345).
Server received 16 bytes from ('127.0.0.1', 51345). The message was b'\x00\x02\x00\x06123456\x00\x050000'.
The length of the first expression is 6. The length of the second expression is 5.
Exception in thread Thread-1:
Traceback (most recent call last):
  File "D:\Program Files\Python\Python37\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "D:\Program Files\Python\Python37\lib\threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "temp-server.py", line 39, in connection_func
    unpacked_response = unpack_data(data)
  File "temp-server.py", line 21, in unpack_data
    exp2 = struct.unpack(f'!{exp2_length}s', packed_data[4 + exp1_length + 2:4 + exp1_length + 2 + exp2_length])[0]
struct.error: unpack requires a buffer of 5 bytes

Поскольку эта ошибка структуры для любого вводакогда общая длина превышает 16 байтов, я подозреваю, что это связано с размером моего буфера. Но мне нужно оставить размер буфера для socket.rev() установленным в 16 байтов как требование.Но я не знаю, как читать по 16 байт за раз и распаковывать структуру по 16 байт за раз. Мне нужно иметь возможность обрабатывать выражения (exp1 и exp2) любой длины (т.е.строки переменной длины), однако;вот почему мое форматирование структуры должно быть динамическим в зависимости от длины выражения.

Я прочитал эту ветку здесь , но все еще безуспешно, когда пытался реализовать некоторые изупомянутые идеи;а именно, у меня все еще были проблемы с распаковкой структуры, и у меня нет никакого символа «конец» в протоколе, чтобы указать, когда все данные были отправлены через сокет.

Вот мой код:

server.py

import socket
import struct
import threading

HOST = "127.0.0.1"
PORT = 65432
BUFFER_SIZE = 16
NUM_RESPONSES = 2


def unpack_data(packed_data):
    exp1_length = int(struct.unpack('!h', packed_data[2:4])[0])
    exp2_length = int(struct.unpack('!h', packed_data[4 + exp1_length: 4 + exp1_length + 2])[0])
    print(f"The length of the first expression is {exp1_length}. The length of the second expression is {exp2_length}.")

    # Extract the variable-length expressions (according to their locations in the protocol)
    exp1 = struct.unpack(f'!{exp1_length}s', packed_data[4: 4 + exp1_length])[0]
    exp2 = struct.unpack(f'!{exp2_length}s', packed_data[4 + exp1_length + 2:4 + exp1_length + 2 + exp2_length])[0]

    return exp1, exp2


def connection_func(conn, addr):
    with conn:
        while True:
            data = conn.recv(BUFFER_SIZE)
            if not data:
                print("Bye!")
                break
            print(f"Server received {len(data)} bytes from {addr}. The message was {data}.")

            # Unpack the received expression.
            unpacked_response = unpack_data(data)

            # Send back the first expression.
            conn.sendall(unpacked_response[0])


def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        print("Waiting for connections...")

        conn, addr = s.accept()
        print(f"Connected from {addr}.")
        threading.Thread(target=connection_func, args=(conn, addr)).start()


main()

client.py

import socket
import struct

HOST = "127.0.0.1"
PORT = 65432
BUFFER_SIZE = 16
NUM_EXPRESSIONS = 2


def pack_expression(exp1, exp2):
    encoded_exp1 = exp1.encode('utf-8')
    encoded_exp2 = exp2.encode('utf-8')
    struct_format = f'!2h{len(encoded_exp1)}sh{len(encoded_exp2)}s'  # According to protocol

    packed_exp = struct.pack(struct_format, NUM_EXPRESSIONS, len(encoded_exp1), encoded_exp1, len(encoded_exp2), encoded_exp2)
    return packed_exp


def main():
    exp1 = "123456"
    exp2 = "0000"

    # Pack the two expressions before sending to the server.
    packed_exp = pack_expression(exp1, exp2)

    # Open a socket and send the packed info to the server.
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        s.sendall(packed_exp)
        data = s.recv(BUFFER_SIZE)

    print(f"Client received {data.decode('utf-8')}.")


main()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...