网络编程之FTP云盘程序

#ftp_server.py
import socket,json,hashlib,struct,os,socketserver
#md5加密类
class getMd5(object):
    # 普通加密用于账户密码加密方法
    @classmethod
    def makeMd5(cls,msg):
        md5=hashlib.md5(b'salt!@#$')
        md5.update(msg.encode('utf-8'))
        return md5.hexdigest()

    # 图片加密用于数据校验方法
    @classmethod
    def noEncodeMd5(cls,msg):
        md5 = hashlib.md5(b'salt!@#$')
        md5.update(msg)
        return md5.hexdigest()


#数据校验类
class DataVer(object):
    # 接收数据校验方法,guanDao信息管道,RecvData接收的数据
    @classmethod
    def RecvDataVer(cls,guanDao,recvData):
        print('开始MD5校验...')
        md5_data_len = struct.unpack('i', guanDao.recv(4))[0]  # 解包
        md5_data = guanDao.recv(md5_data_len).decode('gbk')  # 解码得到客户端加密数据字符串
        if md5_data == getMd5.noEncodeMd5(recvData):  # 对比加密字符串
            guanDao.send('ok'.encode('gbk'))
            print('数据校验成功,数据正确!!!!')
        else:
            print('数据校验失败,数据错误')

    # (断点续传)发送数据相同性校验方法
    @classmethod
    def ddxcRecvDataVer(cls, guanDao, file_path):
        client_md5DataInfoDicJsonStr_len = struct.unpack('i', guanDao.recv(4))[0]# 解包
        client_md5DataInfoDic_JsonStr = guanDao.recv(client_md5DataInfoDicJsonStr_len).decode('gbk')# 解码得到客户端加密数据字符串
        client_md5DataInfo_Dic = json.loads(client_md5DataInfoDic_JsonStr)  # 得到客户端文件信息的字典
        loaded_len=client_md5DataInfo_Dic['client_Data_len']#服务端要读取的长度
        with open(file_path,'rb')as f:
            server_data=f.read(loaded_len)#读服务端数据
        if getMd5.noEncodeMd5(server_data)==client_md5DataInfo_Dic['client_md5Data']:#对比服务端数据加密数据与客户端加密数据对比
            guanDao.send(struct.pack('i',1))
            print('数据校验成功,可以进行断点续传')
            return loaded_len
        else:
            guanDao.send(struct.pack('i',0))
            print('数据校验失败,不可以进行断点续传')
            return 0

    # 发送数据校验方法,guanDao信息管道,sendData发送的数据
    @classmethod
    def SendDataVer(cls,guanDao,sendData):
        print('开始MD5数据校验...')
        MD5_data = getMd5.noEncodeMd5(sendData)  # 加密数据字符串
        guanDao.send(struct.pack('i', len(MD5_data)))  # 打包
        guanDao.send(MD5_data.encode('gbk'))  # 编码发送加密数据字符串
        if guanDao.recv(1024).decode('gbk') == 'ok':  # 对比返回数据
            print('数据校验成功,数据正确!!!!')
        else:
            print('数据校验失败,数据错误')

    # (断点续传)接收数据相同性校验方法
    @classmethod
    def ddxcSendDataVer(cls, guanDao, sendData,data_fromServer_len):#sendData 数据 data_fromServer_len数据长度
        MD5_data = getMd5.noEncodeMd5(sendData)  # 加密数据字符串
        #发送数据相关信息字典
        sendData_info_dic={
            'MD5_data':MD5_data,
            'data_fromServer_len':data_fromServer_len
        }
        sendData_infoDic_jsonstr=json.dumps(sendData_info_dic)#字典变成json字符串
        guanDao.send(struct.pack('i', len(sendData_infoDic_jsonstr)))  # 打包发送json字符串长度
        guanDao.send(sendData_infoDic_jsonstr.encode('gbk'))  # 编码发送json字符串长度


#程序流程类
class ProgramProcess(object):
    # 主页面方法
    @classmethod
    def MainPage(cls,conn):
        gongNeng_lst = ['login', 'zhuce']  # 功能列表
        while 1:
            print('接受执行哪个方法的指令。。。')
            fangshi=conn.recv(1024).decode('gbk')
            # print('字符串指令:',fangshi)
            conn.send('ok'.encode('gbk'))
            # print('发送了确认接收的指令ok')
            if fangshi.upper()=='Q':
                print('程序结束')
                break
            func=getattr(cls,gongNeng_lst[int(fangshi)-1])#反射
            func(conn)#运行对应方法

    # 注册方法
    @classmethod
    def zhuce(cls,conn):
        while 1:
            print('等待接受数据。。。')
            from_client_name = conn.recv(1024).decode('utf-8')
            print('接收到的数据为:',from_client_name)
            if from_client_name.upper()=='Q':
                print('返回主界面')
                break
            with open('user_info', 'a+', encoding='utf-8')as f:
                f.seek(0)#把光标移到开头
                for line in f:
                    if from_client_name == line.strip().split('|')[0]:
                        print('账号已存在,不允许注册')
                        conn.send(json.dumps(0).encode('utf-8'))
                        break
                else:
                    print('允许注册')
                    conn.send(json.dumps(1).encode('utf-8'))
                    from_client_info=conn.recv(1024).decode('utf-8')
                    name=from_client_info.split('|')[0]#获取到名字
                    neiCun=input('请给新用户分配内存空间M(空为默认值50M):')
                    if neiCun=='':
                        neiCun='50'
                    f.write(from_client_info+'|'+neiCun+'\n')#把账号|密码写入文件
                    file_path=r'.\server_file' + '//' + name#生成账户路径
                    os.makedirs(file_path)#新建账户文件夹
                    print('注册成功!!!')
                    conn.send(json.dumps(1).encode('utf-8'))

    # 登录方法
    @classmethod
    def login(cls,conn):
        flag = 0
        while not flag:
            print('等待接受数据。。。')
            from_client_usrInfo = conn.recv(1024).decode('gbk')#获取账号
            if from_client_usrInfo.upper()=='Q':
                print('返回主界面')
                break
            with open('user_info', 'r', encoding='utf-8')as f:
                for line in f:
                    if from_client_usrInfo in line.strip():#数据比对成功
                        print('数据比对成功')
                        conn.send(json.dumps(1).encode('utf-8'))
                        if conn.recv(1024).decode('gbk')=='ok':#收到为‘ok’
                            conn.send(line.strip().encode('gbk'))#把文件name|psw|neiCun数据发送过去
                            name,psw,neiCun=line.strip().split('|')#把此行数据分割放入对象
                            obj = Account(name, psw, neiCun)#实例化对象
                            obj.sendPage(conn)#进入第二界面
                            flag = 1
                        break
                else:
                    print('数据比对失败')
                    conn.send(json.dumps(0).encode('utf-8'))


#账户及操作相关类
class Account(object):
    # 初始化方法
    def __init__(self,name,psw,neiCun):
        self.name=name
        self.psw=psw
        self.neiCun=neiCun
        self.path=r'.\server_file'+'//'+name

    # 功能主页面方法
    def sendPage(self,conn):
        gongNeng_lst = ['update', 'download','newdir','deldir' ]  # 功能列表
        while 1:
            print('接受执行哪个方法的指令。。。')
            num_str=conn.recv(1024).decode('gbk')
            # print('字符串指令:',fangshi)
            conn.send('ok'.encode('gbk'))
            # print('发送了确认接收的指令ok')
            if num_str.upper()=='Q':
                print('返回主界面')
                break
            if num_str=='5':#当字符串数字为5时
                print('选择的功能:查看文件夹')
                Account.show(conn,self.path)#运行遍历文件夹方法
            else:
                func=getattr(self,gongNeng_lst[int(num_str)-1])#反射
                func(conn)#运行对应方法

    # 上传文件方法
    def update(self,conn):
        print('选择的功能:上传文件')
        while 1:
            updata_path=self.path+'//'#默认上传文件到自己的文件夹
            client_fileinfo_len=struct.unpack('i',conn.recv(4))[0]#解包4字节json字典字符串长度
            if client_fileinfo_len:#发过来的数据不是0
                # print('file_info字典json字符串的长度:',client_fileinfo_len)
                client_fileinfo_dic=json.loads(conn.recv(client_fileinfo_len).decode('gbk'))#接收file_info字典
                if client_fileinfo_dic['updataPath'] == '':#判断字典里的上传保存路径是否为空
                    updata_path=self.path#拼接上传文件的路径上层文件夹的路径
                    updata_path_new=self.path+'//'+client_fileinfo_dic['filename']#此路径是上传数据写文件的路径
                else:
                    updata_path=os.path.join(updata_path,client_fileinfo_dic['updataPath'])#拼接上传文件的路径上层文件夹的路径
                    updata_path_new=updata_path+'//'+client_fileinfo_dic['filename']#此路径是上传数据写文件的路径
                if os.path.exists(updata_path):#判断上传文件保存路径是否存在
                    conn.send(struct.pack('i',1))#发送打包数据1
                    fromclient_wenjian_length = client_fileinfo_dic['filesize']  # 上传文件的大小
                    print('上传文件的字节长度:', fromclient_wenjian_length)
                    usedNeiCun = Account.CalculateFileSize(self.path)  # 计算已使用内存
                    usedNeiCun = usedNeiCun / (1024 * 1024)  # 转换成M
                    SurplusNeiCun = float(self.neiCun) - float(usedNeiCun)  # 剩余内存
                    print('剩余内存:', SurplusNeiCun)
                    fromclient_wenjian_len = fromclient_wenjian_length / (1024 * 1024)  # 上传文件大小M
                    if SurplusNeiCun > fromclient_wenjian_len:  # 内存可接受这么大的文件
                        conn.send(struct.pack('i', 1))  # 内存可接受这么大数据向服务端发送打包1
                        if os.path.exists(updata_path_new):#判断文件路径updata_path_new是否存在相同的文件
                            conn.send(struct.pack('i',1)) #此路径文件已存在发送打包1数据
                            print('上传的文件已存在,请进行MD5数据校验看数据是否完整')
                            self.duanDianXuChuan(conn,updata_path_new,fromclient_wenjian_length)#进入断点续传方法,把管道,与路径传过去
                        else:#不存在此文件路径时
                            conn.send(struct.pack('i',0)) #此路径文件不存在发送打包0数据
                            recv_data_len = 0  # 接收数据的长度
                            recv_data = b''
                            with open(updata_path_new, 'wb')as f:  # 打开服务端接收文件的
                                while recv_data_len < fromclient_wenjian_length:  # 当接收数据长度小于总数据长度时进入循环
                                    data = conn.recv(1024)
                                    f.write(data)
                                    recv_data_len += len(data)
                                    recv_data += data
                                    per_cent = recv_data_len * 100 // fromclient_wenjian_length
                                    # 通过\r来实现同一行打印,每次打印都回到行首打印
                                    print('\r' + '%d%%  %s' % (per_cent, '>' * (per_cent // 5)), end='')
                                else:
                                    conn.send(struct.pack('i', 1))  # 向客户端发送接收完毕的数据
                                    print('\n' + '上传接收完毕,接收字节长度为:', recv_data_len)
                                    DataVer.RecvDataVer(conn, recv_data)  # 调用接收数据校验方法
                                    usedNeiCun = Account.CalculateFileSize(self.path)  # 计算已使用内存
                                    usedNeiCun = usedNeiCun / (1024 * 1024)  # 转换成M
                                    conn.send(struct.pack('f', usedNeiCun))  # 返回已使用内存
                    else:
                        conn.send(struct.pack('i', 0))  # 内存不足发送0
                        conn.send(struct.pack('f', SurplusNeiCun))
                        print('内存不足')
                else:
                    conn.send(struct.pack('i',0))
                    print('上传文件保存路径不存在')
            else:
                print('退出上传功能')
                break

    # 下载文件方法
    def download(self,conn):
        print('选择的功能:下载文件')
        while 1:
            file_path_new=conn.recv(1024).decode('gbk')#接受要下载的文件名称
            if file_path_new.upper()=='Q':
                print('退出下载功能')
                break
            file_path=self.path+'//'+file_path_new#默认服务器给客户端发送的文件路径
            if os.path.exists(file_path):#判断文件路径是否存在
                conn.send(struct.pack('i',1))#路径存在发送四字节1
                file_info={
                    'filename':os.path.basename(file_path_new),
                    'filepath':file_path,
                    'filesize':os.path.getsize(file_path)
                }#文件信息组成的字典
                print('文件字节长度:', file_info['filesize'])
                conn.send(struct.pack('i',len(json.dumps(file_info))))#发送file_info字典序列化成字符串的长度
                conn.send(json.dumps(file_info).encode('gbk'))#发送file_info字典序列化后的编码
                if struct.unpack('i',conn.recv(4))[0]:#判断客户端是否存在要下载的文件
                    print('客户端已存在此文件,下面进行数据校验')
                    self.downLoadDuanDianXuChuan(conn,file_path)#调用断点续传方法
                else:
                    send_data_len = 0#发送数据长度
                    send_data=b''#发送的数据
                    with open(file_path, 'rb')as f:
                        while send_data_len<file_info['filesize']:
                            data=f.read(1024)
                            conn.send(data)
                            send_data_len+=len(data)
                            send_data+=data
                            per_cent = send_data_len*100 // file_info['filesize']
                            # 通过\r来实现同一行打印,每次打印都回到行首打印
                            print('\r' + '%d%%  %s' % (per_cent, '>' * (per_cent // 5)), end='')
                        else:
                            if conn.recv(1024).decode('utf-8')=='ok':
                                print('\n'+'下载数据发送完毕,发送字节长度为:',send_data_len)
                                DataVer.SendDataVer(conn,send_data)#调用发送数据校验方法
            else:
                conn.send(struct.pack('i',0))#文件路径不存在发送4字节数值0
                print('文件路径不存在')

    # 新建文件夹方法
    def newdir(self,conn):
        print('选择的功能:新建文件夹')
        while 1:
            path=conn.recv(1024).decode('gbk')
            if path.upper()=='Q':
                print('退出新建文件功能')
                break
            new_path=self.path+'//'+path#拼接新建文件夹的路径
            new_name = os.path.basename(path)  # 获取新建文件夹的名字
            new_path_dir=new_path.replace('//'+new_name,'')#获取上层文件夹路径
            if os.path.exists(new_path_dir):#判断上层文件件路径是否存在
                conn.send(struct.pack('i',1))
                os.makedirs(new_path)#新建文件夹
            else:
                conn.send(struct.pack('i',0))
                print('路径不存在')

    # 删除文件夹方法
    def deldir(self,conn):
        print('选择的功能:删除文件夹')
        while 1:
            path = conn.recv(1024).decode('gbk')
            if path.upper()=='Q':
                print('退出删除文件功能')
                break
            new_path = self.path + '//' + path  # 拼接删除文件夹的路径
            new_name = os.path.basename(path)  # 获取删除文件夹的名字
            new_path_dir = new_path.replace('//' + new_name, '')  # 获取上层文件夹路径
            if os.path.exists(new_path_dir):  # 判断上层文件件路径是否存在
                conn.send(struct.pack('i', 1))
                os.removedirs(new_path)  # 删除文件夹
            else:
                conn.send(struct.pack('i', 0))
                print('路径不存在')

    #上传的断点续传的方法
    def duanDianXuChuan(self,conn,updata_path_new,fromclient_wenjian_length):
        #updata_path_new 文件上传到服务端的保存路径
        #fromclient_wenjian_length 上传文件的总长度
        with open(updata_path_new,'rb')as f:#打开服务端文件
            data_from_server=f.read()#服务端数据
            data_fromServer_len=len(data_from_server)#服务端数据的长度
        DataVer.ddxcSendDataVer(conn,data_from_server,data_fromServer_len)#调用数据校验方法
        if struct.unpack('i', conn.recv(4))[0]:
            print('可以进行断点续传')
            with open(updata_path_new,'ab+')as f:
                while data_fromServer_len<fromclient_wenjian_length:
                    data=conn.recv(1024)
                    f.write(data)#继续上次数据上传
                    data_fromServer_len+=len(data)
                    percent=data_fromServer_len*100//fromclient_wenjian_length
                    print('\r %d%% %s '%(percent,'>'*(percent//5)),end='')
                else:
                    print('\n断点续传完成')
                    f.seek(0)
                    datas=f.read()
                    from_clientdata_md5str=conn.recv(len(getMd5.noEncodeMd5(datas))).decode('gbk')
                    if from_clientdata_md5str==getMd5.noEncodeMd5(datas):
                        conn.send(struct.pack('i',1))
                        print('md5数据校验成功!!!')
                    else:
                        conn.send(struct.pack('i',0))
                        print('MD5数据校验失败!!!')
        else:
            print('数据不一致不能进行断点续传')

    #下载的断点续传方法
    def downLoadDuanDianXuChuan(self,conn,file_path):
        loaded_len=DataVer.ddxcRecvDataVer(conn,file_path)#调用数据校验方法
        if loaded_len:#进入断点续传的条件
            server_data_len=os.path.getsize(file_path)#服务端数据字节长度
            with open(file_path,'rb')as f:
                f.seek(loaded_len)#光标挪到读到的位置
                while loaded_len < server_data_len:
                    data=f.read(1024)
                    conn.send(data)#发送断点数据
                    loaded_len+=len(data)
                    percent=(loaded_len*100)//server_data_len
                    print('\r %d%% %s'%(percent,'>'*(percent//5)),end='')
                else:
                    print('\n完成了断点续传')
                    f.seek(0)
                    datas=f.read()
                    conn.send(getMd5.noEncodeMd5(datas).encode('gbk'))
                    if struct.unpack('i',conn.recv(4))[0]:
                        print('MD5数据校验成功!!')
                    else:
                        print('MD5数据校验失败!!')

    # 查看文件夹方法
    @staticmethod
    def show(conn,file_path):
        nameList = os.listdir(file_path)  # 获取账号根目录下的name列表
        conn.send(json.dumps(nameList).encode('gbk'))  # json转字符串编码发送
        while 1:
            name=conn.recv(1024).decode('gbk')#接收客户端发来的name
            if name.upper()=='Q':
                print('返回上层文件夹')
                break
            new_path=file_path+'//'+name#拼接新路径
            if os.path.isdir(new_path):#判断路径是否为文件夹
                conn.send(struct.pack('i',1))#是文件夹发送打包的1
                Account.show(conn,new_path)#进入递归
            else:
                conn.send(struct.pack('i',0))#如果不是文件夹发送给客户端打包0

    # 计算文件夹大小的方法
    @staticmethod
    def CalculateFileSize(path):
        usedNeiCun=0
        list_name=os.listdir(path)#获取路径下的文件和文件夹名字的列表
        for name in list_name:
            path_abs=os.path.join(path,name)#拼接路径
            if os.path.isfile(path_abs):#如果是文件
                usedNeiCun+=os.path.getsize(path_abs)#计算文件大小
            elif os.path.isdir(path_abs):#如果是文件夹
                size=Account.CalculateFileSize(path_abs)#接受内层计算的值
                usedNeiCun+=size
        return usedNeiCun #返回已经使用的内存大小


#多线程通信类
class myServer(socketserver.BaseRequestHandler):
    #获取信息管道方法
    def handle(self):
        print('连接成功!!!')
        conn=self.request
        ProgramProcess.MainPage(conn)
        print('断开连接,等待下次连接>>>>>>>>>>>>>')
        conn.close()

#程序入口
if __name__ == '__main__':
    socketserver.TCPServer.allow_reuse_address = True#允许端口重用
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8001), myServer)
    server.serve_forever()
#ftp_client.py
import socket,json,hashlib,struct,os
#md5加密类
class getMd5(object):
    # 普通加密用于账户密码加密方法
    @classmethod
    def makeMd5(cls,msg):
        md5=hashlib.md5(b'salt!@#$')
        md5.update(msg.encode('utf-8'))
        return md5.hexdigest()

    # 图片加密用于数据校验方法
    @classmethod
    def noEncodeMd5(cls,msg):
        md5 = hashlib.md5(b'salt!@#$')
        md5.update(msg)
        return md5.hexdigest()


#数据校验类
class DataVer(object):
    # 接收数据校验方法,guanDao信息管道,RecvData接收的数据
    @classmethod
    def RecvDataVer(cls,guanDao,recvData):
        print('开始MD5校验...')
        md5_data_len = struct.unpack('i', guanDao.recv(4))[0]  # 解包
        md5_data = guanDao.recv(md5_data_len).decode('gbk')  # 解码得到客户端加密数据字符串
        if md5_data == getMd5.noEncodeMd5(recvData):  # 对比加密字符串
            guanDao.send('ok'.encode('gbk'))
            print('数据校验成功,数据正确!!!!')
        else:
            print('数据校验失败,数据错误')

    # (断点续传)发送数据相同性校验方法
    @classmethod
    def ddxcRecvDataVer(cls, guanDao, file_path):#_file_path 本地上传文件路径
        sendData_jsonstr_len = struct.unpack('i', guanDao.recv(4))[0]  # 解包
        sendData_infoDic_jsonstr = guanDao.recv(sendData_jsonstr_len).decode('gbk')  # 解码得到客户端json字符串
        sendData_info_dic=json.loads(sendData_infoDic_jsonstr)#发送数据相关信息字典
        read_data_len=sendData_info_dic['data_fromServer_len']#读文件的长度
        with open(file_path,'rb')as f:
            client_read_data=f.read(read_data_len)
            #read_data_len读文件的长度
            #client_read_data 读取的数据
        if getMd5.noEncodeMd5(client_read_data)==sendData_info_dic['MD5_data']:#数据相同可以断点续传
            guanDao.send(struct.pack('i',1))#发送打包数据1
            print('数据相同可以断点续传')
            return read_data_len
        else:
            guanDao.send(struct.pack('i', 0))  # 发送打包数据0
            print('同一路径下数据不同,无法进行断点续传')
            return 0

    # 发送数据校验方法,guanDao信息管道,sendData发送的数据
    @classmethod
    def SendDataVer(cls, guanDao, sendData):
        print('开始MD5数据校验...')
        MD5_data = getMd5.noEncodeMd5(sendData)  # 加密数据字符串
        guanDao.send(struct.pack('i', len(MD5_data)))  # 打包
        guanDao.send(MD5_data.encode('gbk'))  # 编码发送加密数据字符串
        if guanDao.recv(1024).decode('gbk') == 'ok':  # 对比返回数据
            print('数据校验成功,数据正确!!!!')
        else:
            print('数据校验失败,数据错误')

    # (断点续传)接收数据相同性校验方法
    @classmethod
    def ddxcSendDataVer(cls, guanDao,abs_download_path):
        with open(abs_download_path,'rb')as f:
            client_Data=f.read()#读出服务端数据
        client_md5Data=getMd5.noEncodeMd5(client_Data)#md5加密得到加密数据字符串
        client_md5DataInfo_Dic={
            'client_md5Data':client_md5Data,
            'client_Data_len':len(client_Data)
        }
        client_md5DataInfoDic_JsonStr=json.dumps(client_md5DataInfo_Dic)#json字符串
        guanDao.send(struct.pack('i', len(client_md5DataInfoDic_JsonStr)))  # 打包
        guanDao.send(client_md5DataInfoDic_JsonStr.encode('gbk'))  # 编码发送加密数据字符串
        if guanDao.recv(4)[0]:
            print('数据校验成功,可以进行断点续传')
            return len(client_Data)#已经下载的数据长度
        else:
            print('数据校验失败,不可以进行断点续传')
            return 0

#程序流程类
class ProgramProcess(object):
    # 程序入口的方法
    @classmethod
    def MainPage(cls):
        client = sendMSG.buid_connct()  # 调用生成client对象的方法
        lst = ['login', 'zhuce']
        while 1:
            print('''
    欢迎进入FTP文件系统
****************************
1.登录
2.注册
****************************
                ''')
            num = input('请输入要执行的序号(按Q退出):')
            if num=='1'or num=='2'or num.upper()=='Q':#判断输入合法
                client.send(num.encode('gbk'))
                if num.upper() == 'Q':
                    print('程序退出>>>>>>>')
                    break
                if client.recv(1024).decode('gbk') == 'ok':
                    func = getattr(cls, lst[int(num) - 1])
                    func(client)
            else:
                print('输入有误')
        client.close()

    # 注册方法
    @classmethod
    def zhuce(cls, client):
        while 1:
            name = input('请输入注册的账号(按q退出):')
            client.send(name.encode('utf-8'))
            if name.upper() == 'Q':
                break
            if json.loads(client.recv(1024).decode('utf-8')):
                psw = getMd5.makeMd5(input('请输入密码:'))
                print('等待服务端为您分配磁盘内存空间...')
                if sendMSG.senD(client, name, psw):
                    print('注册成功!!!')
            else:
                print('账号已存在')

    # 登陆方法
    @classmethod
    def login(cls, client):
        while 1:
            name = input('请输入账号(Q退出):')
            if name.upper() == 'Q':
                client.send(name.encode('gbk'))
                break
            psw = getMd5.makeMd5(input('请输入密码:'))
            if sendMSG.senD(client, name, psw):
                client.send('ok'.encode('gbk'))
                print('登陆成功!!!!')
                account_info=client.recv(1024).decode('gbk')#接受name|psw|neiCun数据
                name,psw,neiCun=account_info.split('|')#切割解构
                obj=Acount(name,psw,neiCun)#实例化对象
                obj.sendPage(client)#调用实例方法
                break
            else:
                print('账号或密码错误')


#账户相关类
class Acount(object):
    # 初始化方法
    def __init__(self,name,psw,neiCun):
        self.name=name
        self.psw=psw
        self.neiCun=neiCun

    # 功能界面方法
    def sendPage(self,client):
        while 1:
            print('''
      FTP系统主界面
****************************
1.上传文件
2.下载文件
3.新建文件夹
4.删除文件夹
5.查看文件夹
****************************
            ''')
            run_lst=['update','download','newdir','deldir',]
            chioce_num=input('请输入要执行的数字(按Q退出):')
            if chioce_num=='1'or chioce_num=='2'or chioce_num=='3'or chioce_num=='4'or chioce_num=='5'or chioce_num.upper()=='Q':#判断输入合法性
                client.send(chioce_num.encode('gbk'))#发送方法编号\
                if client.recv(1024).decode('gbk')=='ok':#接收到服务端返回的🆗数据
                    if chioce_num.upper() == 'Q':
                        print('返回上一层')
                        break
                    if chioce_num=='5':#如果输入的是'5'
                        print('选择的功能:查看文件夹')
                        Acount.show(client)#运行show静态方法
                    else:
                        func=getattr(self,run_lst[int(chioce_num)-1])#反射
                        func(client)#运行对应方法
                else:
                    print('没有收到服务端返回数据')
            else:
                print('输入有误')

    # 上传文件方法
    def update(self,client):
        print('选择的功能:上传文件')
        while 1:
            client_file_path=input('请输入上传文件的本地路径(Q退出):')
            if client_file_path.upper()=='Q':
                client.send(struct.pack('i',0))#发送打包数据0
                print('退出文件上传功能')
                break
            if os.path.exists(client_file_path):#本地上传路径存在
                flag=0
                while not flag:
                    updata_path=input('请输入保存到服务端的路径(层级用//隔开,为空是默认路径):')
                    file_info={
                        'filename':os.path.basename(client_file_path),#文件信息字典
                        'filepath':client_file_path,
                        'filesize':os.path.getsize(client_file_path),
                        'updataPath':updata_path
                    }#file_info字典
                    print('上传文件的字节长度:',file_info['filesize'])
                    file_info_jsonstr=json.dumps(file_info)#字典变成json字符串
                    fileInfo_jsonstrlen_stru=struct.pack('i',len(file_info_jsonstr))#打包字典json字符串长度
                    client.send(fileInfo_jsonstrlen_stru)#发送字典长度包
                    client.send(file_info_jsonstr.encode('gbk'))  # 发送字典file_info
                    if struct.unpack('i', client.recv(4))[0]:  # 上传文件保存到服务端的路径存在
                        flag = 1
                        if struct.unpack('i',client.recv(4))[0]:#如果内存空间可以放下此文件大小
                            if struct.unpack('i',client.recv(4))[0]:#如果上传文件的路径下有与上传文件同名的文件
                                print('服务端路径下已有与上传文件同名文件,进行MD5比对中...')
                                self.duanDianXuChuan(client,client_file_path)#进入断点续传方法
                            else:#如果上传文件的路径下没有与上传文件同名的文件
                                send_data_len=0#发送的数据长度
                                send_data=b''
                                with open(file_info['filepath'],'rb')as f:
                                    while send_data_len<file_info['filesize']:#当接收的数据长度小于总数据长度时
                                        data=f.read(1024)
                                        client.send(data)
                                        send_data_len+=len(data)
                                        send_data+=data
                                        per_cent = send_data_len*100//file_info['filesize']
                                        # 通过\r来实现同一行打印,每次打印都回到行首打印
                                        print('\r' + '%d%%  %s' % (per_cent,'>'*(per_cent//5)),end='')
                                    else:
                                        if struct.unpack('i',client.recv(4))[0]:
                                            print('\n'+'上传完毕,上传数据字节长度为',file_info['filesize'])
                                            DataVer.SendDataVer(client,send_data)#调用发送数据校验方法
                                            usdNeiCun=struct.unpack('f',client.recv(4))[0]
                                            print('总内存: %s M'%self.neiCun)
                                            print('已使用内存为: %d  M'% usdNeiCun)
                        else:
                            SurplusNeiCun=struct.unpack('f',client.recv(4))[0]#剩余空间
                            file_size=file_info['filesize']/(1024*1024)#上传文件大小M
                            print('内存剩余空间:%sM '%SurplusNeiCun)
                            print('文件大小: %sM'%file_size)
                            print('内存空间不足!!!!!')
                    else:
                        print('文件保存到服务端的路径不存在!!!')
            else:
                print('本地上传文件路径不存在!!!!')

    # 下载文件方法
    def download(self,client):
        print('选择的功能:下载文件')
        while 1:
            file_path=input('请输入要下载文件的路径(层级用//隔开,Q退出):').replace(' ','')
            client.send(file_path.encode('gbk'))#发送要下载的文件名
            if file_path.upper()=='Q':
                print('退出下载功能')
                break
            if struct.unpack('i',client.recv(4))[0]:#如果文件名存在
                download_path=r'.\client_file'+'//' # 默认下载路径
                print('等待接收服务端发送的文件...')
                server_fileinfojsonstr_len = struct.unpack('i',client.recv(4))[0]#拆包4字节file_info字典的json字符串长度
                # print('file_info字典的json字符串长度:', server_fileinfojsonstr_len)
                server_fileinfo_jsonstr=client.recv(server_fileinfojsonstr_len).decode('gbk')#接收file_info字典的json字符串
                file_info_dic=json.loads(server_fileinfo_jsonstr)#json反序列化变成file_info字典
                fromclient_wenjian_length=file_info_dic['filesize']#获取文件长度
                print('下载文件的字节长度:',fromclient_wenjian_length)
                abs_download_path=os.path.join(download_path,file_info_dic['filename'])#下载的全路径
                if os.path.exists(abs_download_path):#下载到服务端的路径存在
                    client.send(struct.pack('i',1))#服务端存在要下载的文件
                    print('客户端已存在此文件,下面进行数据校验')
                    self.downLoadDuanDianXuChuan(client,abs_download_path,fromclient_wenjian_length)#调用断点续传方法
                else:
                    client.send(struct.pack('i',0))#服务端不存在要下载的文件
                    recv_data_len=0#接收的文件字节长
                    recv_data=b''
                    with open(abs_download_path, 'wb')as f:
                        while recv_data_len < fromclient_wenjian_length:#当接受的字节长度总和小于数据总长度时进入循环
                            data = client.recv(1024)
                            f.write(data)#写入文件
                            recv_data_len += len(data)
                            recv_data+=data
                            # 进度条百分比前的数字X(X%)
                            per_cent = recv_data_len*100 // fromclient_wenjian_length
                            # 通过\r来实现同一行打印,每次打印都回到行首打印
                            print('\r' + '%d%%  %s' % (per_cent, '>' * (per_cent // 5)), end='')
                        else:
                            client.send('ok'.encode('utf-8'))#发送接收完毕信号
                            print('\n'+'下载完毕,接收数据字节长度为:',recv_data_len)
                            DataVer.RecvDataVer(client,recv_data)#调用接收数据校验方法
            else:
                print('客户端不存在此文件')

    # 遍历文件夹方法
    @staticmethod
    def show(client):
        print('本层文件夹目录为:')
        nameList = json.loads(client.recv(1024).decode('gbk'))
        while 1:
            for name in nameList:
                print(name)
            new_name=input('请选择要继续查看的文件夹名(Q退出):')
            if new_name.upper() =='Q':
                client.send(new_name.encode('gbk'))  # 把q发送给服务端
                print('返回上层目录')
                break
            if new_name in nameList:#输入的名字在列表中
                client.send(new_name.encode('gbk'))#发送new_name
                if struct.unpack('i',client.recv(4))[0]:#判断是不是文件夹
                    Acount.show(client)#进入递归
                else:
                    print('此名字不是文件夹')
            else:
                print('本层目录不存在此文件夹名')

    # 新建文件夹方法
    def newdir(self,client):
        print('选择的功能:新建文件夹')
        while 1:
            path=input('请输入新建夹路径(层级用//隔开),不熟悉请先选择查看文件夹方法(按q退出):')
            client.send(path.encode('gbk'))
            if path.upper()=='Q':
                print('退出新建文件功能')
                break
            if struct.unpack('i',client.recv(4))[0]:
                print('新建文件夹成功!!')
            else:
                print('文件路径错误')

    # 删除文件夹方法
    def deldir(self,client):
        print('选择的功能:删除文件夹')
        while 1:
            path=input('请输入删除夹路径(层级用//隔开),不熟悉请先选择查看文件夹方法(按q退出):')
            client.send(path.encode('gbk'))
            if path.upper()=='Q':
                print('退出删除文件功能')
                break
            if struct.unpack('i',client.recv(4))[0]:
                print('删除文件夹成功!!!')
            else:
                print('文件路径错误')

    #上传断点续传方法
    def duanDianXuChuan(self,cilent,client_file_path):
        readed_data_len=DataVer.ddxcRecvDataVer(cilent,client_file_path)#文件从此位置开始读
        if readed_data_len:#是否进行断点续传校验方法
            print('服务端已经存在数据大小:',readed_data_len)
            data_len=os.path.getsize(client_file_path)#文件总长度
            with open(client_file_path,'rb')as f:
                f.seek(readed_data_len)#把光标放到断点位置
                while readed_data_len <data_len:#已读长度小于总长度
                    data=f.read(1024)
                    readed_data_len+=len(data)
                    cilent.send(data)#发送断点数据
                    percent = readed_data_len * 100 // data_len
                    print('\r %d%% %s ' % (percent, '>' * (percent // 5)), end='')
                else:
                    print('\n 断点续传完成')
                    f.seek(0)
                    datas=f.read()
                    cilent.send(getMd5.noEncodeMd5(datas).encode('gbk'))
                    if struct.unpack('i',cilent.recv(4))[0]:
                        print('md5数据校验成功!!')
                    else:
                        print('MD5数据校验失败')

    #下载断点续传方法
    def downLoadDuanDianXuChuan(self,client,abs_download_path,fromclient_wenjian_length):
        loaded_len=DataVer.ddxcSendDataVer(client,abs_download_path)#调用数据校验方法
        if loaded_len:#进入断点续传的条件
            with open(abs_download_path,'ab+')as f:
                while loaded_len < fromclient_wenjian_length:
                    data=client.recv(1024)
                    f.write(data)
                    loaded_len+=len(data)
                    percent=(loaded_len*100)//fromclient_wenjian_length
                    print('\r %d%%  %s'%(percent,'>'*(percent//5)),end='')
                else:
                    print('\n断点续传完成')
                    f.seek(0)
                    datas=f.read()
                    fromserver_md5_str=client.recv(len(getMd5.noEncodeMd5(datas))).decode('gbk')
                    if fromserver_md5_str==getMd5.noEncodeMd5(datas):
                        client.send(struct.pack('i',1))
                        print('md5数据校验成功!!')
                    else:
                        client.send(struct.pack('i',0))
                        print('md5数据校验失败!!')

#发送信息相关类
class sendMSG(object):
    # 建立连接方法,返回client对象
    @classmethod
    def buid_connct(cls):
        client = socket.socket()
        client.connect(('127.0.0.1', 8001))
        return client

    # 发送数据user_info方法,返回账号密码是否正确(1/0)
    @classmethod
    def senD(cls,client,name,psw):
        user_info = name + '|' + psw
        # print('数据传输格式:',user_info)
        client.send(user_info.encode('utf-8'))
        from_server_res=json.loads(client.recv(1024).decode('utf-8'))
        return from_server_res


#程序入口
if __name__=='__main__':
     ProgramProcess.MainPage()

  

posted @ 2018-10-23 15:58  冒蓝火的加特林哒哒哒  阅读(305)  评论(0编辑  收藏  举报