无法通过 Python 套接字发送大型二进制文件
Cannot send large binary files over Python socket
我创建了 server.py 和 client.py,目的是在两者之间发送文本和二进制文件。我的代码适用于小型文本文件和小型二进制文件,但大型二进制文件不起作用。
在我的测试中,我使用了一个 1.5 KB 的 .ZIP 文件,我可以毫无问题地发送它。但是,当我尝试发送一个 44 MB 的 .ZIP 文件时,我 运行 遇到了问题。
我的客户端代码如下:
- 客户端创建一个字典,其中包含有关要发送的文件的元数据。
- 二进制文件采用 base64 编码,并作为值添加到字典的“filecontent”键中。
- 字典已 JSON 连载。
- 计算序列化字典的长度,fixed-length作为序列化字典的前缀。
- 客户端将整个消息发送到服务器。
在服务器上:
- 服务器接收 fixed-length header 并解释传输中消息的大小。
- 服务器以 MAXSIZE(测试设置为 500)的块读取消息,临时存储它们。
- 收到整个消息后,服务器将加入整个消息。
- 服务器 base64 解码属于“filecontent”键的值。
- 接下来,它将文件的内容写入磁盘。
正如我所说,这对我的 1.5 KB .ZIP 文件来说工作正常,但对于 44 MB .ZIP 文件,它在服务器上的第 3 步中中断。 json.decoder 引发错误。它抱怨“未终止的字符串开始于...”
故障排除时,我发现邮件的最后一部分没有到达。这解释了 json.decoder 的投诉。我还发现客户端发送的是固定长度的61841613header,这里应该是62279500。相差437887.
当我不让客户端计算消息的大小,而是简单地将大小硬编码为 62279500 时,一切都按预期工作。这让我相信客户端计算较大文件的消息大小的方式有问题。但是我不知道哪里出了问题。
以下是代码的相关部分:
# client.py
connected = True
while connected:
# Actual dictionary contains more metadata
msg = { "filename" : "test.zip" , "author" : "marc" , "filecontent" : "" }
myfile = open("test.zip", "rb")
encoded = base64.b64encode(myfile.read())
msg["filecontent"] = encoded.decode("ascii")
msg = json.dumps(msg)
header = "{:<10}".format(len(msg))
header_msg = header + msg
client.sendall(header_msg.encode("utf-8"))
# server.py
HEADER = 10
MAXSIZE = 500
connected = True
while connected:
msg = conn.recv(HEADER).decode("utf-8")
SIZE = int(msg)
totalmsg = []
while SIZE > 0:
if SIZE > MAXSIZE:
msg = conn.recv(MAXSIZE).decode("utf-8")
totalmsg.append(msg)
SIZE = SIZE - MAXSIZE
else:
msg = conn.recv(SIZE).decode("utf-8")
totalmsg.append(msg)
SIZE = 0
msg = json.loads("".join(totalmsg))
decoded = base64.b64decode(msg["filecontent"])
myfile = open(msg["filename"], "wb")
myfile.write(decoded)
myfile.close()
如评论中所述,conn.recv(MAXSIZE)
最多接收 MAXSIZE,但可以 return 更少。该代码假定它总是收到请求的金额。也没有理由对文件数据进行 base64 编码;它只会使文件数据更大。套接字是一个字节流,所以只发送字节。
header 可以用它和数据之间的标记来描绘。下面我使用了 CRLF 并将 header 编写为单个 JSON 行,还演示了在同一连接上发送几个文件:
client.py
import socket
import json
def transmit(sock, filename, author, content):
msg = {'filename': filename, 'author': author, 'length': len(content)}
data = json.dumps(msg, ensure_ascii=False).encode() + b'\r\n' + content
sock.sendall(data)
client = socket.socket()
client.connect(('localhost',5000))
with client:
with open('test.zip','rb') as f:
content = f.read()
transmit(client, 'test.zip', 'marc', content)
content = b'The quick brown fox jumped over the lazy dog.'
transmit(client, 'mini.txt', 'Mark', content)
server.py
import socket
import json
import os
os.makedirs('Downloads', exist_ok=True)
s = socket.socket()
s.bind(('',5000))
s.listen()
while True:
c, a = s.accept()
print('connected:', a)
r = c.makefile('rb') # wrap socket in a file-like object
with c, r:
while True:
header_line = r.readline() # read in a full line of data
if not header_line: break
header = json.loads(header_line) # process the header
print(header)
remaining = header['length']
with open(os.path.join('Downloads',header['filename']), 'wb') as f:
while remaining :
# Unlike socket.recv() the makefile object won't return less
# than requested unless the socket is closed.
count = f.write(r.read(min(10240, remaining)))
if not count: # socket closed?
if remaining:
print('Unsuccessful')
break
remaining -= count
else:
print('Success')
print('disconnected:', a)
输出:
connected: ('127.0.0.1', 14117)
{'filename': 'test.zip', 'author': 'marc', 'length': 52474063}
Success
{'filename': 'mini.txt', 'author': 'Mark', 'length': 45}
Success
disconnected: ('127.0.0.1', 14117)
我创建了 server.py 和 client.py,目的是在两者之间发送文本和二进制文件。我的代码适用于小型文本文件和小型二进制文件,但大型二进制文件不起作用。
在我的测试中,我使用了一个 1.5 KB 的 .ZIP 文件,我可以毫无问题地发送它。但是,当我尝试发送一个 44 MB 的 .ZIP 文件时,我 运行 遇到了问题。
我的客户端代码如下:
- 客户端创建一个字典,其中包含有关要发送的文件的元数据。
- 二进制文件采用 base64 编码,并作为值添加到字典的“filecontent”键中。
- 字典已 JSON 连载。
- 计算序列化字典的长度,fixed-length作为序列化字典的前缀。
- 客户端将整个消息发送到服务器。
在服务器上:
- 服务器接收 fixed-length header 并解释传输中消息的大小。
- 服务器以 MAXSIZE(测试设置为 500)的块读取消息,临时存储它们。
- 收到整个消息后,服务器将加入整个消息。
- 服务器 base64 解码属于“filecontent”键的值。
- 接下来,它将文件的内容写入磁盘。
正如我所说,这对我的 1.5 KB .ZIP 文件来说工作正常,但对于 44 MB .ZIP 文件,它在服务器上的第 3 步中中断。 json.decoder 引发错误。它抱怨“未终止的字符串开始于...”
故障排除时,我发现邮件的最后一部分没有到达。这解释了 json.decoder 的投诉。我还发现客户端发送的是固定长度的61841613header,这里应该是62279500。相差437887.
当我不让客户端计算消息的大小,而是简单地将大小硬编码为 62279500 时,一切都按预期工作。这让我相信客户端计算较大文件的消息大小的方式有问题。但是我不知道哪里出了问题。
以下是代码的相关部分:
# client.py
connected = True
while connected:
# Actual dictionary contains more metadata
msg = { "filename" : "test.zip" , "author" : "marc" , "filecontent" : "" }
myfile = open("test.zip", "rb")
encoded = base64.b64encode(myfile.read())
msg["filecontent"] = encoded.decode("ascii")
msg = json.dumps(msg)
header = "{:<10}".format(len(msg))
header_msg = header + msg
client.sendall(header_msg.encode("utf-8"))
# server.py
HEADER = 10
MAXSIZE = 500
connected = True
while connected:
msg = conn.recv(HEADER).decode("utf-8")
SIZE = int(msg)
totalmsg = []
while SIZE > 0:
if SIZE > MAXSIZE:
msg = conn.recv(MAXSIZE).decode("utf-8")
totalmsg.append(msg)
SIZE = SIZE - MAXSIZE
else:
msg = conn.recv(SIZE).decode("utf-8")
totalmsg.append(msg)
SIZE = 0
msg = json.loads("".join(totalmsg))
decoded = base64.b64decode(msg["filecontent"])
myfile = open(msg["filename"], "wb")
myfile.write(decoded)
myfile.close()
如评论中所述,conn.recv(MAXSIZE)
最多接收 MAXSIZE,但可以 return 更少。该代码假定它总是收到请求的金额。也没有理由对文件数据进行 base64 编码;它只会使文件数据更大。套接字是一个字节流,所以只发送字节。
header 可以用它和数据之间的标记来描绘。下面我使用了 CRLF 并将 header 编写为单个 JSON 行,还演示了在同一连接上发送几个文件:
client.py
import socket
import json
def transmit(sock, filename, author, content):
msg = {'filename': filename, 'author': author, 'length': len(content)}
data = json.dumps(msg, ensure_ascii=False).encode() + b'\r\n' + content
sock.sendall(data)
client = socket.socket()
client.connect(('localhost',5000))
with client:
with open('test.zip','rb') as f:
content = f.read()
transmit(client, 'test.zip', 'marc', content)
content = b'The quick brown fox jumped over the lazy dog.'
transmit(client, 'mini.txt', 'Mark', content)
server.py
import socket
import json
import os
os.makedirs('Downloads', exist_ok=True)
s = socket.socket()
s.bind(('',5000))
s.listen()
while True:
c, a = s.accept()
print('connected:', a)
r = c.makefile('rb') # wrap socket in a file-like object
with c, r:
while True:
header_line = r.readline() # read in a full line of data
if not header_line: break
header = json.loads(header_line) # process the header
print(header)
remaining = header['length']
with open(os.path.join('Downloads',header['filename']), 'wb') as f:
while remaining :
# Unlike socket.recv() the makefile object won't return less
# than requested unless the socket is closed.
count = f.write(r.read(min(10240, remaining)))
if not count: # socket closed?
if remaining:
print('Unsuccessful')
break
remaining -= count
else:
print('Success')
print('disconnected:', a)
输出:
connected: ('127.0.0.1', 14117)
{'filename': 'test.zip', 'author': 'marc', 'length': 52474063}
Success
{'filename': 'mini.txt', 'author': 'Mark', 'length': 45}
Success
disconnected: ('127.0.0.1', 14117)