0-前言
看过挺多个发文件的例子,但是基本都是发单个,且是 发完连接就结束了
最近正好需要 一个连接
发送 多个文件
,根据需求产生以下内容
涉及知识点:socket 的客户端和服务端应用、json、TCP粘包处理
1-发送单个文件流程
-
【客户端】获取文件信息
必备:大小
可选:文件名、文件绝对路径
-
【客户端】准备一个消息,告诉对方我们要发送的内容、属性信息
{ "消息类型":"请求发送文件", "数据内容":{"大小":123, "文件名":"", "文件绝对路径":"",} }
-
【服务端】收到 “请求发送文件” ,根据文件的内容,做好准备接收,回复可以发送 “request_ack”
-
【客户端】开始发送数据
-
【服务端】不断接收数据
-
【客户端】发送数据结束,发送“quit”
-
【服务端】收到“quit”,说明数据发送结束了;回复“quit_ack”表示我方已经接收完毕
# 自行准备一个文件的信息字典
{
"文件路径":{各种属性}
}
# 【以下为示范】
data = {
"\\HCIA-Security.docx": {
"绝对路径": "D:\\Users\\Desktop\\HCIA\\HCIA-Security.docx",
"文件全名": "HCIA-Security.docx",
"文件名": "HCIA-Security",
"拓展名": ".docx",
"最近修改时间": 16786228.8041983,
"大小": 542353,
"类型": "文件",
"判断结果": "服务端不存在此文件"
},
}
# 客户端发送文件示范,仅提供思路,直接复制不一定跑的起来,因为这只是我程序里的部分代码
def fasong_dange_wenjian(a_data, a_peer):
"""
发送单个文件
:param a_data:单个文件的各种属性
:param a_peer:socket
:return:
"""
# 发送 "请求文件传输" 类型消息,让对端 进入接收文件状态
temp_json = json.dumps({
'data_type': "请求文件传输",
"data": a_data})
socket.send(temp_json.encode('gbk'))
if a_data["类型"] == "文件":
print('--1011发送文件--对方的文件', a_data["对比方的绝对路径"])
_file_path = ''
# 等待对方回复可以开始发送
_request_ack = socket.recv(1024)
if _request_ack == b'request_ack': # 确认可发送就开始 发送文件
_file_path = a_data["绝对路径"] # 获取这个文件的绝对路径
print(f"获取这个文件的绝对路径{_file_path}")
with open(_file_path, "rb") as f:
"""
1kB = 1024 字节
1MB = 1024 * 1024 = 1048576 字节
1GB = 1024 * 1024 * 1024 = 1,073,741,824 字节
"""
_read_size = 1048576 * 500 # 每次读取 500M,以防文件太大,内存不够报错
while True:
_file_data = f.read(_read_size)
if _file_data:
socket.sendall(_file_data) # 发送文件
else:
print("读取的内容为空,读取结束---")
break
socket.send(b'quit') # 发送 文件传输结束 信息,说明文件传输结束
print("1027 文件发送结束")
_quit_ack = socket.recv(1024)
if _quit_ack == b'quit_ack':
print('对方已经收到此文件:', _file_path)
elif a_data["类型"] == "文件夹": # 如果是文件夹,我们发送就结束了
print('1030-发送文件夹--对方的文件夹', a_data["对比方的绝对路径"])
# 等待对方回复可以开始下个循环
_dir_ack = socket.recv(1024)
if _dir_ack == b'dir_ack':
print('对方已经创建此文件夹:', _dir_ack)
# 循环发送单个文件
for x, y in data.items(): # x 文件相对路径,y 文件各种属性
if y["判断结果"] == "服务端不存在此文件":
print("--开始发送单个文件", y["对比方的绝对路径"])
fasong_dange_wenjian(y, peer) # 传个文件各种属性 给他就行了
# 服务端接收文件示范,仅提供思路,直接复制不一定跑的起来,因为这只是我程序里的部分代码
# 等待接收服务端的消息
recved = socket.recv(102400)
# json.loads() 将已编码的 JSON 字符串解码为 Python 对象
_temp_b = recved.decode(encoding='gbk')
temp_json = json.loads(_temp_b)
if temp_json['data_type'] == '请求文件传输': # 收到 客户端 要发送给 服务端请求
while True:
# 开始接收数据
recved = socket.recv(10240)
_temp_b = recved.decode(encoding='gbk')
print(f"--数据是: {_temp_b}")
temp_json = json.loads(_temp_b)
# temp_json = json.loads(recved.decode(encoding='gbk'))
if temp_json['data_type'] == '请求文件传输': # 收到 客户端 要发送给 服务端请求
print("--821--收到 '请求-文件传输'")
# "data" 是 对方的绝对路径
# 判断是不是个文件
if temp_json["data"]["类型"] == "文件":
print(f'824--这是文件{temp_json["data"]["对比方的绝对路径"]}')
# 开始接收文件
elif temp_json["data"]["类型"] == "文件夹":
print(f'824--这是文件夹{temp_json["data"]["对比方的绝对路径"]}')
os.makedirs(temp_json["data"]["对比方的绝对路径"]) # 是目录,直接创建就行了
print("文件夹创建成功", temp_json["data"]["对比方的绝对路径"])
socket.send(b'dir_ack') # 发送信息,说明文件夹创建好了
elif temp_json['data_type'] == '请求文件传输-结束': # 文件发送接收,退出 文件传输模式
break
2-关于发送大文件,本地读取时报错 MemoryError
读取一个4G的文件报了个内存不够的错误
解决方式1:设置每次读取的最大大小
with open(_file_path, "rb") as f:
"""
1kB = 1024 字节
1MB = 1024 * 1024 = 1048576 字节
1GB = 1024 * 1024 * 1024 = 1,073,741,824 字节
"""
_read_size = 1048576 * 500 # 每次读取 500M,以防文件太大,内存不够报错
while True:
_file_data = f.read(_read_size)
if _file_data:
socket.sendall(_file_data) # 发送文件
else:
print("读取的内容为空,读取结束---")
break
解决方式2:更换64位的python
某天发现控制面板里的python是32位,突然想起来32位有最大内存限制,果断卸载换了64位
参考大佬文章:
【已解决】Python MemoryError的问题
3-关于粘包
问题背景
一开始没有限制 socket接收的大小
传小文件没啥问题
然后发现在传输 几个G的文件,传输完成后不会执行后面的写入文件,怀疑【服务端】没收到【客户端】传输结束的 “quit”(自定义的退出标志)
排错过程
考虑是不是发太多,程序反应不过来,在【客户端】新增发送文件数据后 等几秒再发送 “quit”(自定义的退出标志)
测试传1G的文件可以了,但是5G的文件又不行了
干脆记录下 【服务端】一共接收了【客户端】的多少数据
"""
以下是打印 最后2次 接收数据 的记录
传输的文件大小是 3.8G多 (4096065536字节)
【客户端】发送的quit 是 4字节
"""
# 已经接收的大小/文件总大小(单位字节)
接收到数据------4829680 总大小:4095384680/4096065536
接收到数据------680860 总大小:4096065540/4096065536
可以非常清楚的看到
最后一次可以接收的大小: 4095384680(总大小) - 4096065536(已经接受的大小) = 680856
实际上最后一次 接收了 680860,刚好多了4字节,而且 【客户端】发送的quit 是 4字节
所以,最终判断,粘包了
解决方案
单个文件,单个连接场景:
发完连接断开就结束了,可以无视
多个文件,只使用一个连接,用for循环 每次发送 单个文件 场景:
- 准备3个变量,用于存放以下数据
- 获取接收文件的大小信息
- 统计已经接收的数据大小
- 可接收的剩余数据大小
- 实时设置我们还可以接收的大小
- 我们接收的缓冲区大小 = 接收文件的总大小 - 已经接收的数据大小
- 缓冲区大小,一开始会是文件的大小,但是实际上不可能一次全发完占满,只会一部分一部分发,随着不断接收,会越来越小,直到结束为0
# 【服务端 接收侧】
"""
准备3个变量
"""
file_size # 获取接收文件的大小信息
_temp_size = 0 # 统计已经接收的数据大小
recv_size # 可接收的剩余数据大小
"""
开始接收
"""
while True:
# 开始接收文件数据
if file_size != 0: # 如果对面 不是个空文件, recv_size != 0
recv_size = file_size - _temp_size # 传大文件会发送粘包,导致最后的 quit 黏在 文件数据上,所以实时计算可接收的剩余数据大小
if recv_size > 0: # 保证接收的大小在 文件大小之内
_temp_file_data = socket.recv(recv_size)
_temp_size += len(_temp_file_data)
# print(f"接收到数据------{len(_temp_file_data)} 总大小:{_temp_size}/{file_size} 进度:{_temp_size/file_size}")
print(f"接收到数据------{len(_temp_file_data)} 进度:{(_temp_size / file_size) * 100}")
elif recv_size == 0: # 代表文件接收结束,接下来等待 接收 quit
_temp_file_data = socket.recv(1024)
elif file_size == 0: # 如果对面是个空文件, recv_size == 0
_temp_file_data = socket.recv(1024)
print("接收到数据------", len(_temp_file_data))
# 只要不是完成信号,或者数据为0,就一直接收
if _temp_file_data == b'quit':
print("接收完毕,quit", "文件总大小 :", file_size)
break
elif len(_temp_file_data) == 0:
print("这是空数据---quit---")
break
_file_date += _temp_file_data # 把数据先存入变量
# 写入文件【你也可以设置收到数据就写入文件,节省内存,但是我不缺内存,选择先存在内存 后写入储存】
with open(_file_path, "wb+") as f:
f.write(_file_date)
print(f"{_file_route} 写入成功")
# 接受完成标志
socket.send(b'quit_ack')
4-备注-换算表
换算表【数据源自百度百科】
中文单位 | 中文简称 | 英文单位 | 英文简称 | 进率(Byte=1) |
---|---|---|---|---|
比特 | 比特 | bit | b | 0.125 |
字节 | 字节 | Byte | B | 1 |
千字节 | 千字节 | KiloByte | KB | 2^10 |
兆字节 | 兆 | MegaByte | MB | 2^20 |
吉字节 | 吉 | GigaByte | GB | 2^30 |
太字节 | 太 | Terabyte | TB | 2^40 |
拍字节 | 拍 | PetaByte | PB | 2^50 |
艾字节 | 艾 | ExaByte | EB | 2^60 |
泽字节 | 泽 | ZettaByte | ZB | 2^70 |
尧字节 | 尧 | YottaByte | YB | 2^80 |
千亿亿亿字节 | 千亿亿亿字节 | BrontByte | BB | 2^90 |
若是用普通网速1Mbs计算,约8秒钟能下载1MB的文件。
1b(bit,位 或 比特,b)
1B(Byte,字节,B)=8bit
1KB( Kilobyte,千字节,KB)=1024B
1MB( Megabyte,兆字节,MB)=1024KB
1GB( Gigabyte,吉字节,GB)=1024MB
换算:文章来源:https://www.toymoban.com/news/detail-627699.html
1kB = 1024 字节
1MB = 1024 * 1024 = 1048576 字节
1GB = 1024 * 1024 * 1024 = 1073741824 字节
存放的内容:文章来源地址https://www.toymoban.com/news/detail-627699.html
bit(位 或 比特)
一个二进制位只可以表示0和1两种状态(2的1次方);
Byte(字节)
习惯上用大写的“B”表示。
字节是计算机中数据处理的基本单位。
一个字节由八个二进制位构成,即1个字节等于8个比特 (1Byte=8bit) 。
八位二进制数最小为00000000,最大为11111111;
通常1个字节可以存入一个ASCII码,2个字节可以存放一个汉字国标码。
到了这里,关于【python】socket-传输多个文件、大文件的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!