要求:
1、用户加密认证
2、允许同时多用户登录
3、每个用户有自己的家目录 ,且只能访问自己的家目录
4、对用户进行磁盘配额,每个用户的可用空间不同
5、允许用户在ftp server上随意切换目录
6、允许用户查看当前目录下文件
7、允许上传和下载文件,保证文件一致性
8、文件传输过程中显示进度条
9、附加功能:支持文件的断点续传
README:
设计说明
1、client连接server端需要验证账号密码,密码使用MD5加密传输。
2、用户信息保存在本地文件中,密码MD5加密存储。磁盘配额大小也保存在其中。
3、用户连接上来后,可以执行命令如下
目录变更:cd /cd dirname / cd . /cd ..
文件浏览:ls
文件删除:rm filename
目录增删:mkdir dirname /rmdir dirname
查看当前目录:pwd
查看当前目录大小: du
上传文件:put filename
下载文件:get filename
4、涉及到目录的操作,用户登录后,程序会给用户一个“锚位”----以用户名字命名的家目录,使用户无论怎么操作,都只能在这个目录底下。而在发给用户的目录信息时,隐去上层目录信息。
5、用户在创建时,磁盘配额大小默认是100M,在上传文件时,程序会计算当前目录大小加文件大小是否会超过配额上限。未超过,上传;超过,返回磁盘大小不够的信息。磁盘配额可通过用户管理程序修改。
6、文件上传和下载后都会进行MD5值比对,验证文件是否一致。
7、服务端和客户端都有显示进度条功能,启用该功能会降低文件传输速度,这是好看的代价。
8、文件断点续传,暂未实现该功能。我想参照下载软件的做法,将文件下载到一半后断开的信息保存在日志中,然后在下次传输时,检测日志是否有该文件未传完的信息,然后在接着从断点开始传输。等我日后空闲了再补上。
暂且说到这,接下来是正式程序
试运行截图
代码如下:
1、服务端
server.conf
####用户端配置文件#### [DEFAULT] logfile = ../log/server.log usermgr_log = ../log/usermgr.log upload_dir= ../user_files db_dir = ../db ####日志文件位置#### [log] logfile = ../log/server.log usermgr_log = ../log/usermgr.log ####上传文件存放位置#### [upload] upload_dir= ../user_files ####用户信息存放位置#### [db] db_dir = ../db
main.py
#!/usr/bin/env python # -*- coding:utf-8 -*- import socketserver,os from usermanagement import useropr from server import MyTCPHandler info = ‘‘‘ 1、启动服务器 2、进入用户管理 按q退出 ‘‘‘ if __name__ == ‘__main__‘: while True: print(info) choice = input(‘>>>:‘) if choice == ‘q‘: exit() elif choice == ‘1‘: ip, port = ‘0.0.0.0‘, 9999 server = socketserver.ThreadingTCPServer((ip, port), MyTCPHandler) server.serve_forever() elif choice == ‘2‘: useropr.interactive() else:continue
usermanagement
#!/usr/bin/env python # -*- coding:utf-8 -*- #filename:usermanagement.py import os,hashlib,time,pickle,shutil,configparser,logging ####读取配置文件#### base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) config_file = os.path.join(base_dir, ‘conf/server.conf‘) cf = configparser.ConfigParser() cf.read(config_file) ####设定日志目录#### if os.path.exists(cf.get(‘log‘,‘usermgr_log‘)): logfile = cf.get(‘log‘, ‘usermgr_log‘) else: logfile = os.path.join(base_dir,‘log/usermgr.log‘) ####设定用户上传文件目录,这边用于创建用户家目录使用#### if os.path.exists(cf.get(‘upload‘,‘upload_dir‘)): file_dir = cf.get(‘upload‘,‘upload_dir‘) else: file_dir = os.path.join(base_dir,‘user_files‘) ####设定用户信息存储位置#### if os.path.exists(cf.get(‘db‘,‘db_dir‘)): db_path = cf.get(‘db‘,‘db_dir‘) else: db_path = os.path.join(base_dir,‘db‘) def hashmd5(*args): ####用于加密密码信息 m = hashlib.md5() m.update(str(*args).encode()) return m.hexdigest() class useropr(object): def __init__(self,user_name,passwd = ‘123456‘,phone_number=‘‘): self.user_name = user_name self.id = time.strftime("%Y%m%d%H%M%S", time.localtime()) self.phone_number = phone_number self.passwd = passwd self.space_size = 104857600 ####初始分配100MB存储空间 self.member_level = 1 ####会员等级,初始为1,普通会员 @staticmethod ####使用静态方法,可以直接用类命调用,如user.search_user(username),否则需要实例化一个对象后才能调用 def query_user(user_name): ####查询用户 db_filelist=os.listdir(db_path) #print(db_filelist) dict={} for filename in db_filelist: with open(os.path.join(db_path,filename),‘rb‘) as f: content=pickle.load(f) #print(filename,content) ####开启会打印出所有用户信息 if content[‘username‘] == user_name: #print(filename, content) dict={‘filename‘:filename,‘content‘:content} return dict def save_userinfo(self): ####保存用户信息 query_result = self.query_user(self.user_name) ####检查是否已存在同名用户,如果没有查询结果应该为None if query_result == None: user_info = { ‘username‘:self.user_name, ‘id‘:self.id, ‘phonenumber‘:self.phone_number, ‘passwd‘:hashmd5(self.passwd), ‘spacesize‘:self.space_size, ‘level‘:self.member_level } with open(os.path.join(db_path,self.id),‘wb‘) as f: pickle.dump(user_info,f) print(‘用户信息保存完毕‘) try: ####创建用户家目录 os.mkdir(os.path.join(file_dir, self.user_name)) print(‘用户目录创建成功!‘) except Exception as e: print(‘用户目录创建失败,‘,e) else: print(‘用户名重复,信息未保存‘) @staticmethod def change_info(user_name,**kwargs): ####修改信息 query_result = useropr.query_user(user_name) ####用于检测用户是否存在,不存在不处理 if query_result != None: userinfo_filename = query_result[‘filename‘] user_info = query_result[‘content‘] print(‘before update:‘,user_info) for key in kwargs: if key in (‘username‘,‘id‘): ####用户名和ID不可更改 print(key,‘项不可更改‘) elif key in (‘passwd‘,‘phonenumber‘,‘spacesize‘,‘level‘): ####允许修改的键值 if key == ‘passwd‘: user_info[key] = hashmd5(kwargs[key]) ####加密密码保存 else: user_info[key] = kwargs[key] with open(os.path.join(db_path, userinfo_filename), ‘wb‘) as f: pickle.dump(user_info, f) print(key,‘项用户信息变更保存完毕‘) else: print(‘输入信息错误,‘,key,‘项不存在‘) print(‘after update:‘,user_info) else: print(‘用户不存在‘) @staticmethod def delete_user(user_name): ####删除用户 query_result = useropr.query_user(user_name) ####用于检测用户是否存在,不存在不处理 if query_result != None: userinfo_filename = query_result[‘filename‘] userfile_path=os.path.join(db_path, userinfo_filename) os.remove(userfile_path) query_result_again = useropr.query_user(user_name) if query_result_again == None: print(‘用户DB文件删除成功‘) try: shutil.rmtree(os.path.join(file_dir,user_name)) print(‘用户家目录删除成功‘) except Exception as e: print(‘用户家目录删除失败:‘,e) else: print(‘用户DB文件删除失败‘) else: print(‘用户不存在或者已经被删除‘) @staticmethod def query_alluser(): ####查询所有用户信息,用于调试使用 db_filelist=os.listdir(db_path) for filename in db_filelist: with open(os.path.join(db_path,filename),‘rb‘) as f: content=pickle.load(f) print(filename,content) @staticmethod def interactive(): ‘‘‘使用说明: 新增用户请输入类似: a=useropr(username,passwd) a.save_userinfo() 查询用户请输入:useropr.query_user(username) 更改用户信息请输入:useropr.change_info(username,id=123,level=1,passwd=123,phonenumber=123),其中字典部分为可选项 用户删除请输入:useropr.delete_user(username) ‘‘‘ info=‘‘‘ 1、新增用户 2、查询用户 3、修改用户 4、删除用户 退出请按q ‘‘‘ #useropr.query_alluser() ####查询所有用户信息,调试用 while True: print(info) choice = input(‘请输入你的选择:‘).strip() #print(‘operation choice: %s‘ % choice) if choice == ‘q‘: exit() else: username = input(‘请输入用户名:‘).strip() #print(‘username: %s‘ % username) if username == ‘‘: print(‘用户不能为空‘) continue elif choice == ‘1‘: passwd = input(‘请输入密码:‘) new_user = useropr(username, passwd) new_user.save_userinfo() elif choice == ‘2‘: print(useropr.query_user(username)) elif choice == ‘3‘: update_item = input(‘请输入要修改的项目,例如:level,passwd,phonenumber:‘) print(‘update item: %s‘ % update_item) update_value = input(‘请输入要修改的项目新值:‘) useropr.change_info(username,**{update_item:update_value}) #### ‘**{}’ 不加**系统无法识别为字典。不能直接使用update_item=update_value,update_item会直接被当成key值,而不是其中的变量。 elif choice == ‘4‘: useropr.delete_user(username) else: print(‘输入错误‘) continue if __name__ == ‘__main__‘: useropr.interactive()
server.py
#!/usr/bin/env python # -*- coding:utf-8 -*- #filename:server.py import socketserver,json,os,sys,time,shutil,configparser,logging from usermanagement import useropr ####读取配置文件#### base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) config_file = os.path.join(base_dir, ‘conf/server.conf‘) cf = configparser.ConfigParser() cf.read(config_file) ####设定日志目录#### if os.path.exists(cf.get(‘log‘,‘logfile‘)): logfile = cf.get(‘log‘, ‘logfile‘) else: logfile = os.path.join(base_dir,‘log/server.log‘) ####设定用户上传文件目录#### if os.path.exists(cf.get(‘upload‘,‘upload_dir‘)): file_dir = cf.get(‘upload‘,‘upload_dir‘) else: file_dir = os.path.join(base_dir,‘user_files‘) ####设置日志格式### logging.basicConfig(level=logging.INFO, format=‘%(asctime)s %(levelname)s %(message)s‘, datefmt=‘%Y-%m-%d %H:%M:%S‘, filename=logfile, filemode=‘a+‘) def TimeStampToTime(timestamp): ####输入timestamp格式化输出时间,输出格式如:2017-09-16 16:32:35 timeStruct = time.localtime(timestamp) return time.strftime(‘%Y-%m-%d %H:%M:%S‘,timeStruct) def ProcessBar(part,total): ####进度条模块,运行会导致程序变慢 if total !=0: i=round(part*100/total) sys.stdout.write(‘[‘ + ‘>‘ * i + ‘-‘ * (100 - i) + ‘]‘ + str(i) + ‘%‘ + ‘ ‘ * 3 + str(part) + ‘/‘ + str(total) + ‘\r‘) sys.stdout.flush() # if part == total: # print() class MyTCPHandler(socketserver.BaseRequestHandler): def put(self,*args): ####接收客户端文件 #self.request.send(b‘server have been ready to receive‘) ####发送ACK cmd_dict = args[0] filename = os.path.basename(cmd_dict[‘filename‘]) ####传输进来的文件名可能带有路径,将路径去掉 filesize = cmd_dict[‘filesize‘] filemd5 = cmd_dict[‘filemd5‘] override = cmd_dict[‘override‘] receive_size = 0 file_path = os.path.join(self.position,filename) if override != ‘True‘ and os.path.exists(file_path): ####检测文件是否已经存在 self.request.send(b‘file have exits, do nothing!‘) else: if os.path.isfile(file_path): ####如果文件已经存在,先删除,再计算磁盘空间大小 os.remove(file_path) current_size = self.du() ####调用du查看用户磁盘空间大小,但是du命令的最后会发送一个结果信息给client,会和前面和后面的信息粘包,需要注意 self.request.recv(1024) ####接收客户端ack信号,防止粘包,代号:P01 print(self.user_spacesize,current_size,filesize) if self.user_spacesize >= current_size + filesize: self.request.send(b‘begin‘) ####发送开始传输信号 fk = open(file_path,‘wb‘) while filesize > receive_size: if filesize - receive_size > 1024: size = 1024 else: size = filesize - receive_size data = self.request.recv(size) fk.write(data) receive_size += len(data) #print(receive_size,len(data)) ####打印每次接收的数据 #ProcessBar(receive_size, filesize) ####服务端进度条,不需要可以注释掉 fk.close() receive_filemd5 = os.popen(‘md5sum %s‘ % file_path).read().split()[0] print(‘\r\n‘,file_path,‘md5:‘,receive_filemd5,‘原文件md5:‘,filemd5) if receive_filemd5 == filemd5: self.request.send(b‘file received successfully!‘) else: self.request.send(b‘Error, file received have problems!‘) else: self.request.send(b‘Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, filesize:%d‘ % (self.user_spacesize,current_size,self.user_spacesize-current_size,filesize)) def get(self,*args): ####发送给客户端文件 #print(‘get receive the cmd‘,args[0]) filename = args[0][‘filename‘] print(filename) #self.request.send(b‘server have been ready to send‘) ####发送ACK file_path = os.path.join(self.position, filename) if os.path.isfile(file_path): filesize = os.path.getsize(file_path) filemd5 = os.popen(‘md5sum %s‘ % file_path).read().split()[0] ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低 msg = { ‘action‘: ‘get‘, ‘filename‘: filename, ‘filesize‘: filesize, ‘filemd5‘: filemd5, ‘override‘: ‘True‘ } print(msg) self.request.send(json.dumps(msg).encode(‘utf-8‘)) ‘‘‘接下来发送文件给客户端‘‘‘ self.request.recv(1024) ####接收ACK信号,下一步发送文件 fk = open(file_path, ‘rb‘) send_size = 0 for line in fk: send_size += len(line) self.request.send(line) #ProcessBar(send_size, filesize) ####服务端进度条,不需要可以注释掉 else: print(‘文件传输完毕‘) fk.close() else: print(file_path,‘文件未找到‘) self.request.send(json.dumps(‘Filenotfound‘).encode(‘utf-8‘)) def pwd(self,*args): current_position = self.position result = current_position.replace(file_dir,‘‘) ####截断目录信息,使用户只能看到自己的家目录信息 self.request.send(json.dumps(result).encode(‘utf-8‘)) def ls(self,*args): ####列出当前目录下的所有文件信息,类型,字节数,生成时间。 result = [‘%-20s%-7s%-10s%-23s‘ % (‘filename‘, ‘type‘, ‘bytes‘, ‘creationtime‘)] ####信息标题 for f in os.listdir(self.position): type = ‘unknown‘ f_abspath = os.path.join(self.position, f) ####给出文件的绝对路径,不然程序会找不到文件 if os.path.isdir(f_abspath): type = ‘d‘ elif os.path.isfile(f_abspath): type = ‘f‘ result.append(‘%-20s%-7s%-10s%-23s‘ % (f, type, os.path.getsize(f_abspath), TimeStampToTime(os.path.getctime(f_abspath)))) self.request.send(json.dumps(result).encode(‘utf-8‘)) def du(self,*args): ‘‘‘统计纯文件和目录占用空间大小,结果小于在OS上使用du -s查询,因为有一些(例如‘.‘,‘..‘)隐藏文件未包含在内‘‘‘ totalsize = 0 if os.path.isdir(self.position): dirsize, filesize = 0, 0 for root, dirs, files in os.walk(self.position): for d_item in dirs: ####计算目录占用空间,Linux中每个目录占用4096bytes,实际上也可以按这个值来相加 if d_item != ‘‘: dirsize += os.path.getsize(os.path.join(root, d_item)) for f_item in files: ####计算文件占用空间 if f_item != ‘‘: filesize += os.path.getsize(os.path.join(root, f_item)) totalsize = dirsize + filesize result=‘current directory total sizes: %d‘ % totalsize else: result=‘Error,%s is not path ,or path does not exist!‘ % self.position self.request.send(json.dumps(result).encode(‘utf-8‘)) return totalsize def cd(self,*args): print(*args) cmd_dict = args[0] error_tag=False ‘‘‘判断目录信息‘‘‘ if cmd_dict[‘dir‘] == ‘‘: self.position = user_homedir elif cmd_dict[‘dir‘] == ‘.‘ or cmd_dict[‘dir‘] == ‘/‘ or ‘//‘ in cmd_dict[‘dir‘]: ####‘.‘,‘/‘,‘//‘,‘///+‘匹配 pass elif cmd_dict[‘dir‘] == ‘..‘: if user_homedir != self.position and user_homedir in self.position: ####当前目录不是家目录,并且当前目录是家目录下的子目录 self.position = os.path.dirname(self.position) elif ‘.‘ not in cmd_dict[‘dir‘] and os.path.isdir(os.path.join(self.position, cmd_dict[‘dir‘])): ####‘.‘ not in cmd_dict[‘dir‘] 防止../..输入 self.position = os.path.join(self.position, cmd_dict[‘dir‘]) else: error_tag=True ‘‘‘发送结果‘‘‘ if error_tag: result = ‘Error,%s is not path here, or path does not exist!‘ % cmd_dict[‘dir‘] self.request.send(json.dumps(result).encode(‘utf-8‘)) else: self.pwd() def mkdir(self,*args): ####创建目录 try: dirname = args[0][‘dirname‘] if dirname.isalnum(): ####判断文件是否只有数字和字母 if os.path.exists(os.path.join(self.position, dirname)): result = ‘%s have existed‘ % dirname else: os.mkdir(os.path.join(self.position, dirname)) result = ‘%s created succes‘ % dirname else: result=‘Illegal character %s, dirname can only by string and num here.‘ % dirname except TypeError: result = ‘please input dirname‘ self.request.send(json.dumps(result).encode(‘utf-8‘)) def rm(self,*args): ####删除文件 filename = args[0][‘filename‘] confirm =args[0][‘confirm‘] file_abspath = os.path.join(self.position, filename) if os.path.isfile(file_abspath): if confirm == True: os.remove(file_abspath) result=‘%s have been delete.‘ % filename else: result=‘Not file deleted‘ elif os.path.isdir(file_abspath): result=‘%s is a dir, plsese using rmdir‘ % filename else: result=‘File %s not exist!‘ % filename self.request.send(json.dumps(result).encode(‘utf-8‘)) def rmdir(self,*args): ###删除目录 dirname = args[0][‘dirname‘] confirm =args[0][‘confirm‘] file_abspath = os.path.join(self.position, dirname) if ‘.‘ in dirname or ‘/‘ in dirname : ####不能跨目录删除 result=‘should not rmdir %s this way‘ % dirname elif os.path.isdir(file_abspath): if confirm == True: shutil.rmtree(file_abspath) result=‘%s have been delete.‘ % dirname else: result=‘Not file deleted‘ elif os.path.isfile(file_abspath): result=‘%s is a file, not directory deleted‘ % dirname else: result=‘directory %s not exist!‘ % dirname self.request.send(json.dumps(result).encode(‘utf-8‘)) def auth(self): self.data = json.loads(self.request.recv(1024).decode(‘utf-8‘)) print(self.data) recv_username = self.data[‘username‘] recv_passwd = self.data[‘passwd‘] query_result=useropr.query_user(recv_username) print(query_result) if query_result == None: self.request.send(b‘user does not exits‘) elif query_result[‘content‘][‘passwd‘] == recv_passwd: self.request.send(b‘ok‘) return query_result ####返回查询结果 elif query_result[‘content‘][‘passwd‘] != recv_passwd: self.request.send(b‘password error‘) else: self.request.send(b‘unknown error‘) def handle(self): ####处理类,调用以上方法 #self.position = file_dir #print(self.position) auth_tag = False while auth_tag != True: auth_result = self.auth() ####用户认证,如果通过,返回用户名,不通过为None print(‘the authentication result is:‘,auth_result) if auth_result != None: self.username = auth_result[‘content‘][‘username‘] self.user_spacesize = auth_result[‘content‘][‘spacesize‘] auth_tag = True print(self.username,self.user_spacesize) user_homedir = os.path.join(file_dir,self.username) if os.path.isdir(user_homedir): self.position = user_homedir ####定锚,用户家目录 print(self.position) while True: print(‘当前连接:‘,self.client_address) self.data = self.request.recv(1024).strip() print(self.data) logging.info(self.client_address) if len(self.data) == 0 : print(‘客户端断开连接‘) break ####检查发送来的命令是否为空 cmd_dict = json.loads(self.data.decode(‘utf-8‘)) action = cmd_dict[‘action‘] logging.info(cmd_dict) if hasattr(self,action): func = getattr(self,action) func(cmd_dict) else: print(‘未支持指令:‘,action) logging.info(‘current directory:%s‘ % self.position) if __name__ == ‘__main__‘: ip,port=‘0.0.0.0‘,9999 server = socketserver.ThreadingTCPServer((ip,port),MyTCPHandler) server.serve_forever()
2、客户端
client.conf
####用户端配置文件#### [DEFAULT] logfile = ../log/client.log download_dir= ../temp ####日志文件位置#### [log] logfile = ../log/client.log ####下载文件存放位置#### [download] download_dir= ../temp
main.py
#!/usr/bin/env python # -*- coding:utf-8 -*- import configparser,os from client import FtpClient if __name__ == ‘__main__‘: ftp = FtpClient() ftp.connect(‘127.0.0.1‘,9999) auth_tag=False while auth_tag != True: auth_tag=ftp.auth() ftp.interactive()
client.py
#!/usr/bin/env python # -*- coding:utf-8 -*- #filename:client.py import socket,json,os,sys,hashlib,getpass,logging,configparser ####读取配置文件#### base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) config_file = os.path.join(base_dir, ‘conf/client.conf‘) cf = configparser.ConfigParser() cf.read(config_file) ####设定日志目录#### if os.path.exists(cf.get(‘log‘,‘logfile‘)): logfile = cf.get(‘log‘, ‘logfile‘) else: logfile = os.path.join(base_dir,‘log/client.log‘) ####设定下载目录#### if os.path.exists(cf.get(‘download‘,‘download_dir‘)): download_dir = cf.get(‘download‘,‘download_dir‘) else: download_dir = os.path.join(base_dir,‘temp‘) ####设置日志格式### logging.basicConfig(level=logging.INFO, format=‘%(asctime)s %(levelname)s %(message)s‘, datefmt=‘%Y-%m-%d %H:%M:%S‘, filename=logfile, filemode=‘a+‘) def hashmd5(*args): ####用于加密密码信息 m = hashlib.md5() m.update(str(*args).encode()) return m.hexdigest() def ProcessBar(part,total): ####进度条模块 if total !=0: i=round(part*100/total) sys.stdout.write(‘[‘ + ‘>‘ * i + ‘-‘ * (100 - i) + ‘]‘ + str(i) + ‘%‘ + ‘ ‘ * 3 + str(part) + ‘/‘ + str(total) + ‘\r‘) sys.stdout.flush() class FtpClient(object): def __init__(self): self.client = socket.socket() def connect(self,ip,port): self.client.connect((ip,port)) def exec_linux_cmd(self, dict): ####用于后面调用linux命令 logging.info(dict) ####将发送给服务端的命令保存到日志中 self.client.send(json.dumps(dict).encode(‘utf-8‘)) server_response = json.loads(self.client.recv(4096).decode(‘utf-8‘)) if isinstance(server_response,list): for i in server_response: print(i) else: print(server_response) def help(self): info = ‘‘‘ 仅支持如下命令: ls du pwd cd dirname/cd ./cd .. mkdir dirname rm filename rmdir dirname put filename get filename ‘‘‘ print(info) def interactive(self): while True: self.pwd() ####打印当前目录位置 cmd = input(‘>>>:‘).strip() if len(cmd) == 0: continue action = cmd.split()[0] if hasattr(self,action): func=getattr(self,action) func(cmd) else: self.help() def put(self,*args): ####上传文件 cmd = args[0].split() override = cmd[-1] ####override:是否覆盖参数,放在最后一位 if override != ‘True‘: override = ‘False‘ #print(cmd,override) if len(cmd)>1: filename = cmd[1] if os.path.isfile(filename): filesize = os.path.getsize(filename) filemd5 = os.popen(‘md5sum %s‘ % filename).read().split()[0] ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低 msg = { ‘action‘:‘put‘, ‘filename‘:filename, ‘filesize‘:filesize, ‘filemd5‘:filemd5, ‘override‘:override ####True ,or False } logging.info(msg) self.client.send(json.dumps(msg).encode(‘utf-8‘)) server_response = self.client.recv(1024) ####等待服务器确认信号,防止粘包 logging.info(server_response) if server_response == b‘file have exits, do nothing!‘: override_tag=input(‘文件已存在,要覆盖文件请输入yes >>>:‘) if override_tag == ‘yes‘: self.put(‘put %s True‘ % filename) else: print(‘文件未上传‘) else: self.client.send(b‘client have ready to send‘) ####发送确认信号,防止粘包,代号:P01 server_response = self.client.recv(1024).decode(‘utf-8‘) print(server_response) ####注意:用于打印服务器反馈信息,例如磁盘空间不足信息,不能取消 if server_response == ‘begin‘: fk = open(filename,‘rb‘) send_size = 0 for line in fk: #print(len(line)) send_size += len(line) self.client.send(line) ProcessBar(send_size, filesize) else: print(‘\r\n‘,‘文件传输完毕‘) fk.close() server_response = self.client.recv(1024).decode(‘utf-8‘) print(server_response) else: print(‘文件不存在‘) else: print(‘请输入文件名‘) def get(self,*args): ####下载文件 cmd = args[0].split() #print(args[0],cmd) if len(cmd)>1: filename = cmd[1] msg = { ‘action‘: ‘get‘, ‘filename‘: filename, ‘filesize‘: 0, ‘filemd5‘: ‘‘, ‘override‘: ‘True‘ } logging.info(msg) self.client.send(json.dumps(msg).encode(‘utf-8‘)) server_response=json.loads(self.client.recv(1024).decode(‘utf-8‘)) logging.info(server_response) if server_response == ‘Filenotfound‘: print(‘File no found!‘) else: print(server_response) self.client.send(b‘client have been ready to receive‘) ####发送信号,防止粘包 filesize = server_response[‘filesize‘] filemd5 = server_response[‘filemd5‘] receive_size = 0 filepath = os.path.join(download_dir, filename) fk = open(os.path.join(download_dir,filepath), ‘wb‘) while filesize > receive_size: if filesize - receive_size > 1024: size = 1024 else: size = filesize - receive_size data = self.client.recv(size) fk.write(data) receive_size += len(data) #print(receive_size, len(data)) ####打印数据流情况 ProcessBar(receive_size, filesize) ####打印进度条 fk.close() receive_filemd5 = os.popen(‘md5sum %s‘ % filepath).read().split()[0] print(‘\r\n‘,filename, ‘md5:‘, receive_filemd5, ‘原文件md5:‘, filemd5) if receive_filemd5 == filemd5: print(‘文件接收完成!‘) else: print(‘Error,文件接收异常!‘) else: print(‘请输入文件名‘) def pwd(self,*args): ####查看用户目录 msg = { ‘action‘:‘pwd‘, } self.exec_linux_cmd(msg) def ls(self,*args): ####查看文件信息 msg = { ‘action‘:‘ls‘, } self.exec_linux_cmd(msg) def du(self,*args): ####查看当前目录大小 msg = { ‘action‘:‘du‘, } self.exec_linux_cmd(msg) def cd(self,*args): ####切换目录 try: ####如果是直接输入cd,dirname=‘‘ dirname = args[0].split()[1] except IndexError: dirname = ‘‘ msg = { ‘action‘: ‘cd‘, ‘dir‘:dirname } self.exec_linux_cmd(msg) def mkdir(self,*args): ####生成目录 try: ####如果是直接输入rm,跳出 dirname = args[0].split()[1] msg = { ‘action‘: ‘mkdir‘, ‘dirname‘: dirname, } self.exec_linux_cmd(msg) except IndexError: print(‘Not dirname input, do nothing.‘) pass def rm(self,*args): ####删除文件 try: ####如果是直接输入rm,跳出 filename = args[0].split()[1] msg = { ‘action‘: ‘rm‘, ‘filename‘: filename, ‘confirm‘:True ####确认是否直接删除标志 } self.exec_linux_cmd(msg) except IndexError: print(‘Not filename input, do nothing.‘) pass def rmdir(self,*args): try: ####如果是直接输入rm,跳出 dirname = args[0].split()[1] msg = { ‘action‘: ‘rmdir‘, ‘dirname‘: dirname, ‘confirm‘:True ####确认是否直接删除标志 } self.exec_linux_cmd(msg) except IndexError: print(‘Not dirname input, do nothing.‘) pass def auth(self): user_name = input(‘请输入用户名>>>:‘).strip() passwd = getpass.getpass(‘请输入密码>>>:‘).strip() ####在linux上输入密码不显示 msg={ ‘username‘:user_name, ‘passwd‘:hashmd5(passwd) } self.client.send(json.dumps(msg).encode(‘utf-8‘)) server_response = self.client.recv(1024).decode(‘utf-8‘) if server_response == ‘ok‘: print(‘认证通过!‘) return True else: print(server_response) return False if __name__ == ‘__main__‘: ftp = FtpClient() ftp.connect(‘127.0.0.1‘,9999) auth_tag=False while auth_tag != True: auth_tag=ftp.auth() ftp.interactive()
注:配置文件中的中文注释,可能会使程序在启动时报出ASCII decode error,可以去掉。