一、实现一个socket 服务端 ,通过客户端上传本地文件到服务端指定目录
服务端代码:
import socketserver,os,json
import shutil #用于更改文件名
class ftpServer(socketserver.BaseRequestHandler):
def handle(self):
'''
self.request是客户端的套接字对象
socket 接字对象处理数据相关逻辑
:return:
'''
# 接收命令
cmd_str = self.request.recv(1024).decode()
cmd = json.loads(cmd_str)
self.excute_cmd(cmd)
# 写入数据到服务端磁盘
def write_file(self,message,exist_size,total_size,server_filemd5_path,server_filename_path,mode):
response = {'msg': message,'size':exist_size}
self.request.sendall(json.dumps(response).encode())
f = open(server_filemd5_path,mode)
while exist_size < total_size:
data = self.request.recv(1024)
f.write(data) # 此处写到内存
f.flush() # 这里才是写道磁盘
exist_size += len(data)
# while 循环完成后 可以在此处做合法校验
# 校验本地保存文件的md5是否和客户端 事先上传的md5 值相等
f.close()
print('写入成功')
# 保存完文件后将md5的文件名改为客户端上传文件的原文件名
if not os.path.exists(server_filename_path):
shutil.move(server_filemd5_path, server_filename_path)
print('上传完成')
else:
pass # 保留,后续增加 文件已存在 在文件名最后追加1,比如文件名1(1).txt
# 获取客户端前置信息并做对应数据处理
def excute_cmd(self,cmd):
# 获取上传文件的md5
file_md5 = cmd['file_md5']
# 获取上传文件的原文件名
file_name = cmd['file_name']
# 事先获取即将要上传的文件的大小
upload_file_size = cmd['size']
# 定义md5命名的文件在服务端的保存位置,服务端路径+客户端文件的md5值作为文件名
server_filemd5_path = os.path.join(os.getcwd(), 'source', file_md5)
# 定义非md5命名的文件在服务端的保存位置,服务端路径+客户端原文件名
server_filename__path = os.path.join(os.getcwd(), 'source', file_name)
# 判断文件路径是否存在
file_exist = os.path.exists(server_filemd5_path)
# 统一定义服务端返回值格式
# response = {'code': '100x','size':xxxx} size 可选
# 接收到客户端请求下载后,判断其文件是否存在,然后通知客户端开始上传
# 然后开始接收客户端上传的文件
if not file_exist:
msg = '开始完整上传'
mode = 'wb'
done_size =0
self.write_file(msg,done_size,upload_file_size,server_filemd5_path,server_filename__path,mode)
else:
msg='开始断点续传'
# 获取在服务端已经保存的文件的大小
done_size = os.stat(server_filemd5_path).st_size
print(done_size)
mode = 'ab'
self.write_file(msg,done_size,upload_file_size,server_filemd5_path,server_filename__path,mode)
if __name__ == '__main__':
ftp = socketserver.ThreadingTCPServer(('192.168.0.105.',8013),ftpServer)
ftp.serve_forever()
客户端代码:
import socket,json,hashlib,os
# 文件加密方法
def file_add_md5(filepath):
m = hashlib.md5()
# 这里传入的是文件的完整路径,打开文件后 循环每一行数据进行加密
with open(filepath,'rb') as f:
for line in f:
m.update(line) #
return m.hexdigest() #返回字符串类型的十六进制数据
# 展示进度条相关
def display_upload(currsize,totla_size):
val = (currsize*100)//totla_size
print('\r上传进度%s%%|%s'%(val,'>'*(val//2)),end='')
# 执行命令并获取本地文件对应信息
def excute_cmd(file_path):
file_md5 = file_add_md5(file_path)
# os传入完整路径 获取文件名
file_name = os.path.basename(file_path)
file_size = os.stat(file_path).st_size
cmd_dict = {'cmd': 'upload', 'file_name': file_name, 'size': file_size, 'file_md5': file_md5}
# dict 转json字符串,再转byte类型
cmd_dict_byte = json.dumps(cmd_dict).encode()
# 发送数据
client.sendall(cmd_dict_byte)
# 客户端等待服务端的响应:
upload_type_byte = client.recv(1024).decode()
# 将json字符串转换字典
resp = json.loads(upload_type_byte)
# code = resp['code']
# exist_size = resp['size']
upload(resp,file_path,file_size)
# 上传文件的逻辑
def upload(resp,file_path,file_size):
#
# # 开始上传文件 方法1 ,通过逐行遍历文件,然后发送
# with open(file_path,'rb') as f:
# for line in f:
# client.sendall(line)
# 开始上传文件 方法2 ,循环read 固定长度数据,然后发送这部分数据
exist_size = resp['size']
f = open(file_path,'rb')
# 跳转至指定位置进行读取
f.seek(exist_size)
print(resp['msg']+file_path)
while exist_size<file_size:
data = f.read(1024)
client.sendall(data)
exist_size+=len(data)
display_upload(exist_size,file_size)
print('upload success')
f.close()
if __name__ == '__main__':
while True:
# 创建一个客户端的 对象
client = socket.socket()
# 客户端对象向服务端 发起连接
client.connect(('192.168.0.105', 8013))
cmd = input('请输入要上传的文件:')
pic = {'1':'1.png','2':'2.mp4'}
file_path = pic.get(cmd)
excute_cmd(file_path)