无法通过 Python 套接字发送大型二进制文件

Cannot send large binary files over Python socket

我创建了 server.py 和 client.py,目的是在两者之间发送文本和二进制文件。我的代码适用于小型文本文件和小型二进制文件,但大型二进制文件不起作用。

在我的测试中,我使用了一个 1.5 KB 的 .ZIP 文件,我可以毫无问题地发送它。但是,当我尝试发送一个 44 MB 的 .ZIP 文件时,我 运行 遇到了问题。

我的客户端代码如下:

  1. 客户端创建一个字典,其中包含有关要发送的文件的元数据。
  2. 二进制文件采用 base64 编码,并作为值添加到字典的“filecontent”键中。
  3. 字典已 JSON 连载。
  4. 计算序列化字典的长度,fixed-length作为序列化字典的前缀。
  5. 客户端将整个消息发送到服务器。

在服务器上:

  1. 服务器接收 fixed-length header 并解释传输中消息的大小。
  2. 服务器以 MAXSIZE(测试设置为 500)的块读取消息,临时存储它们。
  3. 收到整个消息后,服务器将加入整个消息。
  4. 服务器 base64 解码属于“filecontent”键的值。
  5. 接下来,它将文件的内容写入磁盘。

正如我所说,这对我的 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)