一、实现一个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)