通过套接字(python)发送包含文件的字典
Posted
技术标签:
【中文标题】通过套接字(python)发送包含文件的字典【英文标题】:Send a dictionary containing a file through a socket (python) 【发布时间】:2018-11-12 12:55:02 【问题描述】:是否可以通过套接字发送包含文件(图像或文档)作为值的字典?
我尝试了类似下面的方法但我失败了..
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 是一个字节数组,无法序列化。
我尝试使用 base64 编码将 myFile 转换为字符串,但没有成功。
部分起作用的是将 myFile 转换为字符串,例如 str(myFile)。 json 序列化器工作,我通过套接字发送它,dict 没问题,但 myFile 数据已损坏,所以我无法重新创建图片。
那么是否可以使用这种方法,或者我应该如何通过套接字发送文件和数据以便在另一端轻松解析?
乐:
仍然无法使用 base64 编码,myFile 仍然是“字节”格式并且 json 给出这个错误:TypeError: Object of type 'bytes' is not JSON serializable
客户
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()
【问题讨论】:
你应该可以做到:dataToSend = json.dumps("id":"1283", "filename":"cat.jpeg", "file":base64.b64encode(myFile))
就好了,这会给你带来什么错误?
为什么不直接发送二进制数据而不是将其编码为base64并在此过程中浪费1/3的带宽?您可以像 <json_size><json><binary_size><binary_data>
这样打包数据,其中大小可以是 4 字节,这将涵盖所有情况,直到 4GB 数据。
@zwer 我是编程新手,我不知道该怎么做。如果您知道要搜索的示例或某些关键字会很棒。
【参考方案1】:
感谢大家的帮助,我终于用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()
【讨论】:
【参考方案2】:正如我在评论中所说,将二进制数据打包成字符串格式(如 JSON)是一种浪费 - 如果您使用 base64,您会将数据传输大小增加 33%,而且这也让 JSON 解码器变得困难正确解码 JSON,因为它需要流过整个结构来提取索引。
最好单独发送它们 - JSON 作为 JSON,然后文件内容直接作为二进制发送。当然,您需要一种区分两者的方法,最简单的方法是在发送 JSON 数据时在其前面加上其长度,以便服务器知道要读取多少字节才能获取 JSON,然后再读取其余部分作为文件内容。这将使它成为一种非常简单的协议,其包形成如下:
[JSON LENGTH][JSON][FILE CONTENTS]
假设 JSON 永远不会大于 4GB(如果是,您将遇到更大的问题,因为解析它将是一场噩梦)拥有固定 4 个字节的 JSON LENGTH
(32位)作为无符号整数(如果您不希望 JSON 超过 64KB,您甚至可以选择 16 位),因此整个策略将在客户端运行:
-
创建负载
将其编码为 JSON,然后使用 UTF-8 编码将其编码为
bytes
获取上述包的长度并将其作为流的前 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 ...
块来打开/关闭套接字。
仅此而已。当然,在真实环境中,您需要处理断开连接、多个客户端等问题。但这是底层过程。
【讨论】:
感谢您的写作和解释。有一个工作示例,我看到了 base64 如何增加传输大小。我现在将尝试这种方法,因为它似乎更好。【参考方案3】:你应该可以做到:
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)
【讨论】:
当我尝试时它没有给我任何错误。该文件不是有效的 jpeg 文件。现在看看你的例子,我认为我在解码部分做错了。我会再试一次并回来提供更多信息。 我在帖子中添加了更多内容。它不适用于base64。明天我将尝试 bson 模块,在阅读了更多内容后,我认为我需要使用 binascii 模块从二进制转换为十六进制字符串。 ***.com/questions/35748549/…以上是关于通过套接字(python)发送包含文件的字典的主要内容,如果未能解决你的问题,请参考以下文章