Как я уже говорил в своем комментарии, упаковка двоичных данных в строковый формат (например, JSON) расточительна - если вы используете base64, вы увеличиваете размер передаваемых данных на 33%, что также затрудняет работу JSON-декодера.для правильного декодирования JSON, так как он должен проходить через всю структуру только для извлечения индексов.
Гораздо лучше отправить их отдельно - JSON как JSON, а затем содержимое файла прямо как двоичный файл.Конечно, вам понадобится способ различить эти два, и самый простой - это просто предварять данные JSON их длиной при отправке, чтобы сервер знал, сколько байтов нужно прочитать, чтобы получить JSON, а затем прочитал остальные.как содержимое файла.Это сделало бы это своего рода очень простым протоколом с пакетами, сформированными как:
[JSON LENGTH][JSON][FILE CONTENTS]
Предполагая, что JSON никогда не будет больше 4 ГБ (и если это так, у вас будут гораздо большие проблемы при разбореэто было бы кошмаром) более чем достаточно иметь JSON LENGTH
фиксированных 4 байта (32 бита) в качестве целого числа без знака (вы можете даже использовать 16-битное значение, если не ожидаете, что JSON превысит 64 КБ)поэтому вся стратегия будет работать на стороне клиента следующим образом:
- Создать полезную нагрузку
- Кодировать ее в JSON, а затем кодировать в
bytes
, используя кодировку UTF-8 - Получите длину вышеупомянутого пакета и отправьте его как первые 4 байта потока
- Отправьте пакет JSON
- Считайте и отправьте содержимое файла
И на стороне сервера вы выполняете тот же процесс
- Считайте первые 4 байта полученных данных, чтобы получить длину полезной нагрузки JSON
- Считайте следующее число байтов для соответствияэта длина
- расшифровать ихв строку, используя UTF-8, а затем декодируйте JSON, чтобы получить полезную нагрузку
- . Считайте оставшиеся потоковые данные и сохраните их в файле
. Или в коде, клиент:
import json
import os
import socket
import struct
BUFFER_SIZE = 4096 # a uniform buffer size to use for our transfers
# pick up an absolute path from the script folder, not necessary tho
file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "downloads", "cat.png"))
# let's first prepare the payload to send over
payload = {"id": 12, "filename": os.path.basename(file_path), "message": "So cute!"}
# now JSON encode it and then turn it onto a bytes stream by encoding it as UTF-8
json_data = json.dumps(payload).encode("utf-8")
# then connect to the server and send everything
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: # create a socket
print("Connecting...")
s.connect(("127.0.0.1", 1234)) # connect to the server
# first send the JSON payload length
print("Sending `{filename}` with a message: {message}.".format(**payload))
s.sendall(struct.pack(">I", len(json_data))) # pack as BE 32-bit unsigned int
# now send the JSON payload itself
s.sendall(json_data) # let Python deal with the buffer on its own for the JSON...
# finally, open the file and 'stream' it to the socket
with open(file_path, "rb") as f:
chunk = f.read(BUFFER_SIZE)
while chunk:
s.send(chunk)
chunk = f.read(BUFFER_SIZE)
# alternatively, if you're using Python 3.5+ you can just use socket.sendfile() instead
print("Sent.")
И сервер:
import json
import os
import socket
import struct
BUFFER_SIZE = 4096 # a uniform buffer size to use for our transfers
target_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "fileCache"))
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 1234)) # bind to the 1234 port on localhost
s.listen(0) # allow only one connection so we don't have to deal with data separation
while True:
print("Waiting for a connection...")
connection, address = s.accept() # wait for and accept the incoming connection
print("Connection from `{}` accepted.".format(address))
# read the starting 32 bits and unpack them into an int to get the JSON length
json_length = struct.unpack(">I", connection.recv(4))[0]
# now read the JSON data of the given size and JSON decode it
json_data = b"" # initiate an empty bytes structure
while len(json_data) < json_length:
chunk = connection.recv(min(BUFFER_SIZE, json_length - len(json_data)))
if not chunk: # no data, possibly broken connection/bad protocol
break # just exit for now, you should deal with this case in production
json_data += chunk
payload = json.loads(json_data.decode("utf-8")) # JSON decode the payload
# now read the rest and store it into a file at the target path
file_path = os.path.join(target_path, payload["filename"])
with open(file_path, "wb") as f: # open the target file for writing...
chunk = connection.recv(BUFFER_SIZE) # and stream the socket data to it...
while chunk:
f.write(chunk)
chunk = connection.recv(BUFFER_SIZE)
# finally, lets print out that we received the data
print("Received `{filename}` with a message: {message}".format(**payload))
ПРИМЕЧАНИЕ: имейте в виду, что это код Python 3.x - для Python 2.x вам придется иметь дело с управлением контекстомсамостоятельно вместо блока with ...
, чтобы открывать / закрывать свои розетки.
И это все, что нужно сделать.Конечно, в реальных условиях вам нужно иметь дело с отключениями, несколькими клиентами и т. Д. Но это основной процесс.