Python Socket编程中处理MP4等大文件流传输时,最核心的问题是如何解决流式传输无边界导致的接收不完整问题。MP4文件通常体积较大,单次传输无法完成,需要分多次接收,若没有合理的机制标识文件起始、结束和校验数据,很容易出现接收端文件损坏、缺失内容的情况。

大文件流接收不完整的核心原因
Socket传输属于字节流传输,本身不会自动划分数据边界,发送端连续发送的数据可能会被接收端合并或者拆分接收,这就是TCP的粘包和拆包问题。对于MP4这类大文件,发送端会分多次发送文件数据,接收端如果只调用一次recv方法,大概率只能拿到部分数据。另外如果网络出现波动,部分数据包丢失,或者接收端缓冲区设置过小,也会导致数据接收不全。
完整接收的实现方案
1. 自定义传输协议格式
我们需要在发送端和接收端约定统一的传输规则,明确文件的相关元信息和数据边界,推荐的协议格式如下:
- 头部:固定长度,包含文件名长度、文件总大小、校验值长度三个字段,每个字段用固定字节存储
- 文件名:根据头部中的文件名长度读取对应字节
- 校验值:根据头部中的校验值长度读取对应字节,用于接收完成后校验文件完整性
- 文件数据:剩余的所有字节都是文件内容
2. 发送端实现代码
发送端需要先读取MP4文件,计算文件的MD5校验值,然后按照约定的协议格式依次发送头部、文件名、校验值、文件数据:
import socket
import os
import hashlib
def send_mp4_file(file_path, server_ip, server_port):
# 创建Socket连接
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((server_ip, server_port))
# 读取文件内容
with open(file_path, 'rb') as f:
file_data = f.read()
# 计算文件MD5校验值
file_md5 = hashlib.md5(file_data).hexdigest()
file_name = os.path.basename(file_path)
file_size = len(file_data)
# 协议头部:文件名长度(4字节)、文件大小(8字节)、校验值长度(4字节),使用大端字节序
header = len(file_name).to_bytes(4, byteorder='big') + file_size.to_bytes(8, byteorder='big') + len(file_md5).to_bytes(4, byteorder='big')
# 依次发送头部、文件名、校验值、文件数据
client_socket.sendall(header)
client_socket.sendall(file_name.encode('utf-8'))
client_socket.sendall(file_md5.encode('utf-8'))
client_socket.sendall(file_data)
client_socket.close()
print(f"文件{file_name}发送完成")
if __name__ == '__main__':
# 示例调用,发送当前目录下的test.mp4文件到本地8888端口
send_mp4_file('test.mp4', '127.0.0.1', 8888)
3. 接收端实现代码
接收端需要按照协议格式依次解析头部、文件名、校验值,然后循环接收文件数据直到达到文件总大小,最后校验文件完整性:
import socket
import hashlib
def recv_mp4_file(server_ip, server_port, save_dir):
# 创建服务端Socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((server_ip, server_port))
server_socket.listen(1)
print(f"服务端启动,监听{server_port}端口")
conn, addr = server_socket.accept()
print(f"客户端{addr}连接成功")
# 接收固定长度的头部:4+8+4=16字节
header = conn.recv(16)
if len(header) != 16:
print("接收头部失败")
return
# 解析头部字段
name_len = int.from_bytes(header[:4], byteorder='big')
file_size = int.from_bytes(header[4:12], byteorder='big')
md5_len = int.from_bytes(header[12:16], byteorder='big')
# 接收文件名
file_name = conn.recv(name_len).decode('utf-8')
# 接收校验值
file_md5 = conn.recv(md5_len).decode('utf-8')
# 循环接收文件数据,直到达到文件总大小
recv_size = 0
file_data = b''
while recv_size < file_size:
# 每次最多接收1024*1024字节,即1MB
chunk = conn.recv(min(1024*1024, file_size - recv_size))
if not chunk:
break
file_data += chunk
recv_size += len(chunk)
# 校验文件完整性
calc_md5 = hashlib.md5(file_data).hexdigest()
if calc_md5 == file_md5 and len(file_data) == file_size:
# 保存文件
save_path = os.path.join(save_dir, file_name)
with open(save_path, 'wb') as f:
f.write(file_data)
print(f"文件{file_name}接收完整,已保存到{save_path}")
else:
print(f"文件{file_name}接收不完整,校验失败")
conn.close()
server_socket.close()
if __name__ == '__main__':
# 示例调用,在本地8888端口接收文件,保存到当前目录
recv_mp4_file('127.0.0.1', 8888, '.')
关键注意事项
- 接收数据时不要假设单次
recv能拿到所有需要的数据,必须循环接收直到满足长度要求 - 缓冲区大小设置要合理,过大会占用过多内存,过小会增加系统调用次数,通常设置为1MB左右比较合适
- 校验值建议使用MD5或者SHA256,避免传输过程中数据被篡改或者丢失无法感知
- 如果是UDP传输,还需要额外添加丢包重传机制,因为UDP本身不保证数据可靠到达
常见问题排查
如果还是出现文件接收不完整的情况,可以先检查接收端循环接收的逻辑是否正确,是否严格按照文件总大小作为结束条件。然后可以对比发送端和接收端的文件大小、校验值,定位是数据丢失还是解析逻辑错误。如果是网络波动导致的,可以适当增加接收超时时间,或者添加断点续传的逻辑。
Python_Socket大文件传输MP4流接收文件完整性校验修改时间:2026-07-23 18:39:31