Отправить словарь, содержащий файл через сокет (python) - PullRequest
0 голосов
/ 02 июня 2018

Можно ли отправить через файл dict, содержащий файл (изображение или документ) в качестве значения?

Я пробовал что-то вроде ниже, но мне не удалось ..

with open("cat.jpeg", "rb") as f:
    myFile = f.read(2048)

data = {"id": "1283", "filename": "cat.jpeg", "file": myFile}

dataToSend = json.dumps(data).encode("utf-8")

Это приводит к ошибке json, myFile, будучи байтовым массивом, не может быть сериализован.

Я попытался скрыть myFile в строку, используя кодирование base64, но это не сработало.

ЧтоЧастично сработало - преобразование myFile в строку, например str (myFile).Сериализатор json сработал, я отправляю его через сокет, диктовка была в порядке, но данные myFile были повреждены, поэтому я не мог воссоздать картинку.

Так возможно ли использовать этот подход или как я должен отправитьфайл и данные через сокет, который будет легко проанализирован на другой стороне?

LE:

Все еще не работает с использованием кодировки base64, myFile по-прежнему является "байтовым" форматом, и json дает этоошибка: Ошибка типа: Объект типа 'байты' не поддерживает сериализацию JSON

Клиент

import os
import base64
import json
import socket

currentPath = os.path.dirname(os.path.abspath(__file__)) + "\\downloads\\"

with open(currentPath + "cat.png", "rb") as f:
    l = f.read()

print(type(l))   #prints <class 'bytes'>

myFile = base64.b64encode(l)

print(type(myFile))    #prints <class 'bytes'>

data = {"id": "12", "filename": "cat.png", "message": "So cute!", "file": myFile}

dataToSend = json.dumps(data).encode("utf-8")   #prints TypeError: Object of type 'bytes' is not JSON serializable

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 1234))
s.sendall(dataToSend)
s.close()

И Сервер:

import socket
import json
import os
import sys
import time
import base64

currentPath = os.path.dirname(os.path.abspath(__file__)) + "\\fileCache\\"
tempData = bytearray()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 1234))
s.listen(5)
conn, addr = s.accept()

while True:
    dataReceived = conn.recv(2048)
    if sys.getsizeof(dataReceived) > 17:
        tempData = tempData + dataReceived
    else:
        data = json.loads(tempData.decode("utf-8"))
        break
    time.sleep(1)

print(data)

myFile = base64.b64decode(data["file"])

with open(currentPath + data["filename"], "wb") as f:
    f.write(myFile)
    f.close()

Ответы [ 3 ]

0 голосов
/ 03 июня 2018

Как я уже говорил в своем комментарии, упаковка двоичных данных в строковый формат (например, JSON) расточительна - если вы используете base64, вы увеличиваете размер передаваемых данных на 33%, что также затрудняет работу JSON-декодера.для правильного декодирования JSON, так как он должен проходить через всю структуру только для извлечения индексов.

Гораздо лучше отправить их отдельно - JSON как JSON, а затем содержимое файла прямо как двоичный файл.Конечно, вам понадобится способ различить эти два, и самый простой - это просто предварять данные JSON их длиной при отправке, чтобы сервер знал, сколько байтов нужно прочитать, чтобы получить JSON, а затем прочитал остальные.как содержимое файла.Это сделало бы это своего рода очень простым протоколом с пакетами, сформированными как:

[JSON LENGTH][JSON][FILE CONTENTS]

Предполагая, что JSON никогда не будет больше 4 ГБ (и если это так, у вас будут гораздо большие проблемы при разбореэто было бы кошмаром) более чем достаточно иметь JSON LENGTH фиксированных 4 байта (32 бита) в качестве целого числа без знака (вы можете даже использовать 16-битное значение, если не ожидаете, что JSON превысит 64 КБ)поэтому вся стратегия будет работать на стороне клиента следующим образом:

  1. Создать полезную нагрузку
  2. Кодировать ее в JSON, а затем кодировать в bytes, используя кодировку UTF-8
  3. Получите длину вышеупомянутого пакета и отправьте его как первые 4 байта потока
  4. Отправьте пакет JSON
  5. Считайте и отправьте содержимое файла

И на стороне сервера вы выполняете тот же процесс

  1. Считайте первые 4 байта полученных данных, чтобы получить длину полезной нагрузки JSON
  2. Считайте следующее число байтов для соответствияэта длина
  3. расшифровать ихв строку, используя UTF-8, а затем декодируйте JSON, чтобы получить полезную нагрузку
  4. . Считайте оставшиеся потоковые данные и сохраните их в файле

. Или в коде, клиент:

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 ..., чтобы открывать / закрывать свои розетки.

И это все, что нужно сделать.Конечно, в реальных условиях вам нужно иметь дело с отключениями, несколькими клиентами и т. Д. Но это основной процесс.

0 голосов
/ 03 июня 2018

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

Мне пришлось кодировать и декодировать файл, как это, прежде чем использовать json.dumps.

base64_bytes = b64encode(l)
myFile = base64_bytes.decode("utf-8")

Вот рабочий пример:

Клиент:

import os
from base64 import b64encode
import json
import socket

currentPath = os.path.dirname(os.path.abspath(__file__)) + "\\downloads\\"

with open(currentPath + "cat.png", "rb") as f:
    l = f.read()

base64_bytes = b64encode(l)
myFile = base64_bytes.decode("utf-8")

data = {"id": "12", "filename": "cat.png", "message": "So cute!", "file": myFile}

dataToSend = json.dumps(data).encode("utf-8")

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 1234))
s.sendall(dataToSend)
s.close()

Сервер:

import socket
import json
import os
import sys
import base64

currentPath = os.path.dirname(os.path.abspath(__file__)) + "\\fileCache\\"
tempData = bytearray()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 1234))
s.listen(5)
conn, addr = s.accept()

while True:
    dataReceived = conn.recv(4096)

    if sys.getsizeof(dataReceived) > 17:
        tempData = tempData + dataReceived
    else:
        data = json.loads(tempData.decode("utf-8"))
        break

myFile = base64.b64decode(data["file"])

with open(currentPath + data["filename"], "wb") as f:
    f.write(myFile)
    f.close()
0 голосов
/ 02 июня 2018

Вы должны уметь:

data = base64.b64encode(myFile)
dataToSend = json.dumps({"id":"1283","filename":"cat.jpeg", "file":data})

и затем отправлять через сокет.Когда вы получаете данные на другом конце сокета, просто выполните:

jsonDict = json.loads(dataReceived)
data = base64.b64decode(jsonDict["file"])

Лучшим способом может быть просто использовать bson, https://github.com/py-bson/bson.

from gevent import monkey, socket
monkey.patch_all()

import bson
bson.patch_socket()
with open("cat.jpeg", "rb") as f:
    myFile = f.read()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 12345))
s.sendobj({u"id": "1283", u"filename": "cat.jpeg", u"file": myFile})
...