036_python的大文件下载以及进度条展示
复习
1.黏包现象
粘包现象的成因:
- tcp协议的特点,面向流的,为了保证可靠传输,所以有很多优化的机制。
- 无边界 所有在连接建立的基础上传递的数据之间没有界限。
- 收发消息很有可能不完全相等。
- 缓存机制,导致没发过去的消息会在发送端缓存,没接收完的消息会在接收端缓存。
解决:
- 给应用层定制协议
解决方案一:只发送一条信息 - 先发送一个定长表示待发送数据长度的bytes,先接收一个固定长度
- 再发送要发送的数据,再按照长度接收数据。
解决方案二 :发送的多条信息 - 先发送一个定长表示待发送字典长度的bytes,先接收一个固定长度。
- 再发送要发送字典(字典中存储的是文件信息),再按照长度接收字典。
- 再发送文件,再根据字典中的信息接收相应的内容。
那么这2种方案,如何选择呢?
如果发送数据之前,发送的定制协议超过1个变量时,就应该使用字典
否则,就需要发送多次,比如:文件名的长度,文件名,文件大小的长度,文件的内容...
发送的次数越多,在网络上,就比较浪费时间。
如果将上面的5个变量,放到一个字典里面,只需要发送一次,就可以了。
如果只有1个变量,就使用第一种方案。
大文件下载以及进度条展示
大文件传输:
server.py
import os
import json
import socket
import struct
filepath = r'E:\BaiduYunDownload\[电影天堂www.dy2018.com]移动迷宫3:死亡解药BD国英双语中英双字.mp4'
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
conn,addr = sk.accept()
filename = os.path.basename(filepath)
filesize = os.path.getsize(filepath)
dic = {'filename':filename,'filesize':filesize}
str_dic = json.dumps(dic).encode('utf-8')
len_dic = len(str_dic)
length = struct.pack('i',len_dic)
conn.send(length) # dic的长度
conn.send(str_dic) # dic
with open(filepath,'rb') as f: # 文件
while filesize:
content = f.read(4096)
conn.send(content)
filesize -= len(content)
'''
这里不能减等4096,因为文件,最后可能只有3字节。
要根据读取的长度len(content),来计算才是合理的。
'''
conn.close()
sk.close()
client.py
import json
import struct
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
dic_len = sk.recv(4)
dic_len = struct.unpack('i',dic_len)[0]
dic = sk.recv(dic_len)
str_dic = dic.decode('utf-8')
dic = json.loads(str_dic)
with open(dic['filename'],'wb') as f: # 使用wb更严谨一些,虽然可以使用ab
while dic['filesize']:
content = sk.recv(4096) #这边的4096,可以不用和server对应,改成1024,也可以
dic['filesize'] -= len(content)
f.write(content)
sk.close()
先执行server.py,再执行client.py等待30多秒,当前目录就会出现一个视频文件,打开确认,是否可以播放。
客户体验太差了,用户不知道啥时候能接收完,程序到底有没有卡住?下载花了多长时间?都不知道下面来一个进阶版的,增加进度条和下载时间
主要是修改client.py,代码如下:
import json
import struct
import socket
import sys
import time
def processBar(num, total): # 进度条
rate = num / total
rate_num = int(rate * 100)
if rate_num == 100:
r = '\r%s>%d%%\n' % ('=' * rate_num, rate_num,)
else:
r = '\r%s>%d%%' % ('=' * rate_num, rate_num,)
sys.stdout.write(r)
sys.stdout.flush
start_time = time.time() # 开始时间
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
dic_len = sk.recv(4)
dic_len = struct.unpack('i',dic_len)[0]
dic = sk.recv(dic_len)
str_dic = dic.decode('utf-8')
dic = json.loads(str_dic)
with open(dic['filename'],'wb') as f: # 使用wb更严谨一些,虽然可以使用ab
content_size = 0
while True:
content = sk.recv(4096)<br> f.write(content) # 写入文件
content_size += len(content) # 接收大小
processBar(content_size,dic['filesize']) # 执行进度条函数
if content_size == dic['filesize']:break # 当接收的总大小等于文件大小时,终止循环
sk.close() # 关闭连接
end_time = time.time() # 结束时间
print('本次下载花费了{}秒'.format(end_time - start_time))
执行效果如下:
上面效果展示了100个等号,太长了,那么要缩减到1/3呢?
修改进度条函数
def processBar(num, total): # 进度条
rate = num / total
rate_num = int(rate * 100)
if rate_num == 100:
r = '\r%s>%d%%\n' % ('=' * int(rate_num / 3), rate_num,) # 控制等号输出数量,除以3,表示显示1/3
else:
r = '\r%s>%d%%' % ('=' * int(rate_num / 3), rate_num,)
sys.stdout.write(r)
sys.stdout.flush
再次执行:
再来一个高级版,显示绿色的飞机代码如下
代码如下:
def processBar(num, total): # 进度条
rate = num / total
rate_num = int(rate * 100)
pretty = '✈'
if rate_num == 100:
r = '\r\033[32m{}\033[0m{}%\n'.format(pretty * int(rate_num / 5), rate_num,)
else:
r = '\r\033[32m{}\033[0m{}%'.format(pretty * int(rate_num / 5), rate_num,)
sys.stdout.write(r)
sys.stdout.flush
效果如下:
再来一个每秒换色,导入一个随机换色类。
import random
class Prompt(object): # 提示信息显示
colour_dic = {
'red': 31,
'green': 32,
'yellow': 33,
'blue': 34,
'purple_red': 35,
'bluish_blue': 36,
'white': 37,
}
def __init__(self):
pass
@staticmethod
def display(msg, colour='white'):
choice = Prompt.colour_dic.get(colour)
# print(choice)
if choice:
info = "\033[1;{};1m{}\033[1;0m".format(choice, msg)
return info
else:
return False
def random_color(msg): # 随机换色
colour_list = []
for i in Prompt.colour_dic:
colour_list.append(i)
length = len(colour_list) - 1 # 最大索引值
index = random.randint(0, length) # 随机数
ret = Prompt.display(msg, colour_list[index]) # 随机颜色
return ret
修改client.py
from Prompt import Prompt
def processBar(num, total): # 进度条
rate = num / total
rate_num = int(rate * 100)
pretty = Prompt.random_color('✈') # 随机换色
if rate_num == 100:
r = '\r{}{}%\n'.format(pretty * int(rate_num / 5), rate_num,)
else:
r = '\r{}{}%'.format(pretty * int(rate_num / 5), rate_num,)
sys.stdout.write(r)
sys.stdout.flush
执行效果如下:
1.增加MD5校验
server.py
import os
import json
import socket
import struct
import hashlib
sk = socket.socket()
sk.bind(('127.0.0.1', 9000))
sk.listen()
conn, addr = sk.accept()
filename =r '[电影天堂www.dy2018.com]移动迷宫3:死亡解药BD国英双语中英双字.mp4' # 文件名
absolute_path = os.path.join(r'E:\BaiduYunDownload',filename) # 文件绝对路径
buffer_size = 1024*1024 # 缓冲大小,这里表示1MB
md5obj = hashlib.md5()
with open(absolute_path, 'rb') as f:
while True:
content = f.read(buffer_size) # 每次读取指定字节
if content:
md5obj.update(content)
else:
break # 当内容为空时,终止循环
md5 = md5obj.hexdigest()
print(md5) # 打印md5值
dic = {'filename':filename,
'filename_md5':str(md5),'buffer_size':buffer_size,
'filesize':os.path.getsize(absolute_path)}
str_dic = json.dumps(dic).encode('utf-8')
len_dic = len(str_dic)
length = struct.pack('i', len_dic)
conn.send(length) # dic的长度
conn.send(str_dic) # dic
with open(absolute_path, 'rb') as f: # 文件
while dic['filesize']:
content = f.read(dic['buffer_size'])
conn.send(content)
dic['filesize'] -= len(content)
'''
这里不能减等4096,因为文件,最后可能只有3字节。
要根据读取的长度len(content),来计算才是合理的。
'''
conn.close()
client.py
import json
import struct
import socket
import sys
import time
import hashlib
import os
from Prompt import Prompt
def processBar(num, total): # 进度条
rate = num / total
rate_num = int(rate * 100)
pretty = Prompt.random_color('✈')
if rate_num == 100:
r = '\r{}{}%\n'.format(pretty * int(rate_num / 5), rate_num,)
else:
r = '\r{}{}%'.format(pretty * int(rate_num / 5), rate_num,)
sys.stdout.write(r)
sys.stdout.flush
start_time = time.time() # 开始时间
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
dic_len = sk.recv(4)
dic_len = struct.unpack('i',dic_len)[0]
dic = sk.recv(dic_len)
str_dic = dic.decode('utf-8')
dic = json.loads(str_dic)
md5 = hashlib.md5()
with open(dic['filename'],'wb') as f: # 使用wb更严谨一些,虽然可以使用ab
content_size = 0
while True:
content = sk.recv(dic['buffer_size']) # 接收指定大小
f.write(content) # 写入文件
content_size += len(content) # 接收大小
md5.update(content) # 摘要
processBar(content_size,dic['filesize']) # 执行进度条函数
if content_size == dic['filesize']:break # 当接收的总大小等于文件大小时,终止循环
md5 = md5.hexdigest()
print(md5) # 打印md5值
if dic['filename_md5'] == str(md5):
print(Prompt.display('md5校验正确--下载成功','green'))
else:
print(Prompt.display('文件验证失败', 'red'))
os.remove(dic['filename']) # 删除文件
sk.close() # 关闭连接
end_time = time.time() # 结束时间
print('本次下载花费了{}秒'.format(end_time - start_time))
执行输出:
socket的更多方法介绍
1.更多方法
服务端套接字函数
s.bind()
绑定(主机,端口号)到套接字
s.listen()
开始TCP监听
s.accept()
被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect()
主动初始化TCP服务器连接
s.connect_ex()
connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv()
接收TCP数据
s.send()
发送TCP数据
s.sendall()
发送TCP数据
s.recvfrom()
接收UDP数据
s.sendto()
发送UDP数据
s.getpeername()
连接到当前套接字的远端的地址
s.getsockname()
当前套接字的地址
s.getsockopt()
返回指定套接字的参数
s.setsockopt()
设置指定套接字的参数
s.close()
关闭套接字
面向锁的套接字方法
s.setblocking()
设置套接字的阻塞与非阻塞模式
s.settimeout()
设置阻塞套接字操作的超时时间
s.gettimeout()
得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno()
套接字的文件描述符
s.makefile()
创建一个与该套接字相关的文件
2.send和sendall方法
官方文档对socket模块下的socket.send()和socket.sendall()解释如下:
socket.send(string[, flags])
Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Returns the number of bytes sent. Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, the application needs to attempt delivery of the remaining data.
send()的返回值是发送的字节数量,这个数量值可能小于要发送的string的字节数,也就是说可能无法发送string中所有的数据。如果有错误则会抛出异常。
socket.sendall(string[, flags])
Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Unlike send(), this method continues to send data from string until either all data has been sent or an error occurs. None is returned on success. On error, an exception is raised, and there is no way to determine how much data, if any, was successfully sent.
尝试发送string的所有数据,成功则返回None,失败则抛出异常。
故,下面两段代码是等价的:
#sock.sendall('Hello world\n')
#buffer = 'Hello world\n'
#while buffer:
# bytes = sock.send(buffer)
# buffer = buffer[bytes:]
验证客户端链接的合法性
使用hashlib.md5
加密
为什么要随机字符串,是为了防止客户端的数据被窃取。生成随机的bytes类型数据,它是解不出来的。
import os
print(os.urandom(32))
执行输出:
b'PO\xca8\xc8\xf3\xa0\xb5,\xdd\xb8K \xa8D\x9cN"\x82\x03\x86g\x18e\xa7\x97\xa77\xb9\xa5VA'
server.py
import os
import socket
import hashlib
secret_key = '老衲洗头用飘柔' # 加密key
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
while True:
try:
conn,addr = sk.accept()
random_bytes = os.urandom(32) # 随即产生32个字节的字符串,返回bytes
conn.send(random_bytes) # 发送随机加密key
md5 = hashlib.md5(secret_key.encode('utf-8')) # 使用secret_key作为加密盐
md5.update(random_bytes) #得到MD5消息摘要
ret = md5.hexdigest() #以16进制返回消息摘要,它是一个32位长度的字符串
msg = conn.recv(1024).decode('utf-8') # 接收的信息解码
if msg == ret:print('是合法的客户端') # 如果接收的摘要和本机计算的摘要一致,就说明是合法的
else:conn.close() # 关闭连接
finally: # 无论如何,都执行下面的代码
sk.close() # 关闭连接
break
client.py
import socket
import hashlib
secret_key = '老衲洗头用飘柔' # 加密key
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
urandom = sk.recv(32) # 接收32字节,也就是os.urandom的返回值
md5_obj = hashlib.md5(secret_key.encode('utf-8')) # 使用加密盐加密
md5_obj.update(urandom)
sk.send(md5_obj.hexdigest().encode('utf-8')) # 发送md5摘要
print('-----')
sk.close() # 关闭连接
先执行server.py,再执行client.py
client输出:-----
server输出:是合法的客户端
如果100客户端,来连接呢?秘钥都是通用的。
一般情况下,用在哪些场景呢?
比如公司级别,比如1台机器,向100台服务器获取数据
假如黑客渗透到内网,得知到服务器IP地址。就可以做端口扫描,一台计算机的端口范围是0~65535扫描6万多次,就能知道了。
1.使用hmac加密
hmac是专门来做客户端合法性的
import hmac
obj = hmac.new(key=b'secret_key',msg=b'100212002155')
print(obj.hexdigest())
执行输出:
27111d37764a2fe5bc79d297e7b54c35
客户端也使用hmac,验证一下,就可以了。
改造server和client
server.py
import os
import socket
import hmac
secret_key = '老衲洗头用飘柔'.encode('utf-8')
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
while True:
try:
conn,addr = sk.accept()
random_bytes = os.urandom(32)
conn.send(random_bytes)
obj = hmac.new(key=secret_key,msg=random_bytes)
ret = obj.hexdigest()
msg = conn.recv(1024).decode('utf-8')
if msg == ret:print('是合法的客户端')
else:conn.close()
finally:
sk.close()
break
client.py
import socket
import hmac
secret_key = '老衲洗头用飘柔'.encode('utf-8')
sk = socket.socket()
sk.connect(('127.0.0.1', 9000))
urandom = sk.recv(32)
hmac_obj = hmac.new(key=secret_key, msg=urandom)
sk.send(hmac_obj.hexdigest().encode('utf-8'))
print('-----')
sk.close()
socketserver
SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进 程” 专门负责处理当前客户端的所有请求。
它能实现多个客户端,同时连接,它继承了socket
ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。
使用ThreadingTCPServer
:
创建一个继承自 SocketServer.BaseRequestHandler
的类,必须继承
类中必须定义一个名称为 handle 的方法,必须重写
看BaseRequestHandler 的源码,它的hendle方法,是空的
def handle(self):
pass
需要自己去实现
server.py
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
print(self.request)
server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyServer)
server.serve_forever()
client.py
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
sk.close()
先执行server.py,再执行client.py
server输出
<socket.socket fd=540, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 62243)>
每连接一个client.就会触发handle,输出request监听,等待连接,接收,全由hendle完成了。
server.py
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
print(self.request)
self.request.send(b'hello') # 跟所有的client打招呼
print(self.request.recv(1024)) # 接收客户端的信息
server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyServer)
server.serve_forever()
client.py
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
print(sk.recv(1024))
inp = input('>>>').encode('utf-8')
sk.send(inp)
sk.close()
先执行server.py,再执行client.py
client输出:b'hello'
>>>hiserver输出:<socket.socket fd=316, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9000), raddr=('127.0.0.1', 49176)> b'hi'
开多个客户端,也可以执行
server能够和多个client通信
1.连续发送
client连续发送:
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
while True:
print(sk.recv(1024))
#inp = input('>>>').encode('utf-8')
sk.send(b'hahaha')
sk.close()
server连续接收:
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
while True:
print(self.request)
self.request.send(b'hello') # 跟所有的client打招呼
print(self.request.recv(1024)) # 接收客户端的信息
server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyServer)
server.serve_forever()
执行效果如下:
如果server端口重复,使用以下代码:
# 设置allow_reuse_address允许服务器重用地址
socketserver.TCPServer.allow_reuse_address = True
完整代码如下:
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
while True:
print(self.request) # 这里不能使用input,否则卡住了
self.request.send(b'hello') # 跟所有的client打招呼
print(self.request.recv(1024)) # 接收客户端的信息
if __name__ == '__main__':
socketserver.TCPServer.allow_reuse_address = True
server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyServer)
server.serve_forever()
明日默写:
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
self.request.send(b'hello')
msg = self.request.recv(1024)
print(msg)
if __name__ == '__main__':
socketserver.TCPServer.allow_reuse_address = True
server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),MyServer)
server.serve_forever()
036_python的大文件下载以及进度条展示的更多相关文章
- python 全栈开发,Day36(作业讲解(大文件下载以及进度条展示),socket的更多方法介绍,验证客户端链接的合法性hmac,socketserver)
先来回顾一下昨天的内容 黏包现象粘包现象的成因 : tcp协议的特点 面向流的 为了保证可靠传输 所以有很多优化的机制 无边界 所有在连接建立的基础上传递的数据之间没有界限 收发消息很有可能不完全相 ...
- python (大文件下载及进度条展示) 验证客户端链接的合法性,socketserver
##########总结########### 文件校验加进度条显示 ####server import os import json import socket import struct impo ...
- Asp.Net上传大文件带进度条swfupload
Asp.Net基于swfupload上传大文件带进度条百分比显示,漂亮大气上档次,大文件无压力,先看效果 一.上传效果图 1.上传前界面:图片不喜欢可以自己换 2.上传中界面:百分比显示 3.上传后返 ...
- JS原生上传大文件显示进度条-php上传文件
JS原生上传大文件显示进度条-php上传文件 在php.ini修改需要的大小: upload_max_filesize = 8M post_max_size = 10M memory_li ...
- PHP_APC扩展dll上传大文件及进度条实例
1.弄好了APC之后,就是使用它了,下面是个例子,是一个进度条上传的例子,作为笔记记录下来 在这个例子之前,我们需要做如下的设置,如果我们需要上传的是大文件的话,请在您的php.ini文件中做如下的设 ...
- PHP 文件下载 显示进度条
前台调用:js调用: function downloadfile(id,name,price,curcount_pricelimit){ Date.prototype.Format = functio ...
- 一个简单、易用的Python命令行(terminal)进度条库
eprogress 是一个简单.易用的基于Python3的命令行(terminal)进度条库,可以自由选择使用单行显示.多行显示进度条或转圈加载方式,也可以混合使用. 示例 单行进度条 多行进度条 圆 ...
- ProgressBar 进度条开源项目总结
在Android开发中,我们不免会遇到进度条展示的需求,以下是本人之前star的开源项目,供大家参考: 一.ArcProgressBar 开源项目地址:https://github.com/zenoT ...
- R语言学习笔记:使用tcltk包显示进度条
一般在跑耗时较长的程序时,我们不知道程序到底有没有正常跑着,或者在爬虫的时候不知道爬到什么时候断了.因此可以添加进度条来显示当前进度,观察进度是否有进展.当进度条卡住的时候,可以判断程序断线,从而可以 ...
随机推荐
- Retrofit 网络访问框架简单使用
1.引入远程依赖:包括okhttp;retrofit2;retrofit的GSON解析器 compile'com.squareup.okhttp3:okhttp:3.2.0' compile'com. ...
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十四)之Type Information
Runtime type information (RTTI) allow you to discover and use type information while a program is ru ...
- python基础之函数详解
Python基础之函数详解 目录 Python基础之函数详解 一.函数的定义 二.函数的调用 三.函数返回值 四.函数的参数 4.1 位置参数 4.2 关键字参数 实参:位置实参和关键字参数的混合使用 ...
- 详解 List接口
本篇博文所讲解的这两个类,都是泛型类(关于泛型,本人在之前的博文中提到过),我们在学习C语言时,对于数据的存储,用的差不多都是数组和链表. 但是,在Java中,链表就相对地失去了它的存在价值,因为Ja ...
- testlink的api
testlink可以做很多你想象得到的事情,如API测试参数管理,Excel导入导出,快速模板创建测试用例,集成Jenkins. TestLink API第三方库: TestLink-API-Pyth ...
- 虚拟机体验NAS私人云全揭秘:序言——虚拟机体验NAS私人云缘由
"世界在新冠肺炎疫情后将永远改变",对于2020春天在全球蔓延的新冠肺炎疫情,美国前国务卿基辛格做了这样的评价.确实,也改变了我们.春节期间,本着少添乱的原则,响应国家号召,自我隔 ...
- kafka高吞吐量之消息压缩
背景 保证kafka高吞吐量的另外一大利器就是消息压缩.就像上图中的压缩饼干. 压缩即空间换时间,通过空间的压缩带来速度的提升,即通过少量的cpu消耗来减少磁盘和网络传输的io. 消息压缩模型 消息格 ...
- mysql 更改默认字符集
mysql 默认字符集概述 首先,MySQL的字符集问题主要是两个概念: haracter Sets Collations 前者是字符内容及编码,后者是对前者进行比较操作的一些规则.这两个参数集可以在 ...
- 优质中文NLP资源集合,做项目一定用得到!
今天要给大家在推荐 Github 上一个优质的中文 NLP 工具和资源集合项目——funNLP,已经获得了 5.3k Stars,1k+ Forks. 项目作者 杨洋,一枚水博&互联网民工,目 ...
- 关于go的入门书籍——go自学的序
说实话,许世伟的<GO语言编程>,承载的心血是轻易可见的.但是我更喜欢那种工具书,就是简单说明他干嘛她干嘛,就能干嘛干嘛··· 比如读张晏关于<取代Apache的高性能Web服务器& ...