作业:
开发一个支持多用户在线的FTP程序
要求:
- 用户加密认证
- 允许同时多用户登录
- 每个用户有自己的家目录 ,且只能访问自己的家目录
- 对用户进行磁盘配额,每个用户的可用空间不同
- 允许用户在ftp server上随意切换目录
- 允许用户查看当前目录下文件
- 允许上传和下载文件,保证文件一致性
- 文件传输过程中显示进度条
- 附加功能:支持文件的断点续传
README:
1.client连接server端需要验证账号密码,密码使用MD5加密传输,三次验证不成功即退出。
2.用户信息保存在服务器本地文件中,密码MD5加密存储。磁盘配额大小也保存在其中。
3.用户连接上来后,可以执行命令如下
目录变更:cd /cd dirname / cd . /cd ..
文件浏览:ls
文件删除:rm filename
目录增删:mkdir dirname /rmdir dirname
查看当前目录:pwd
查看当前目录大小: du
移动和重命名: mv filename/dirname filename/dirname
上传文件:put filename [True] (True代表覆盖)
下载文件:get filename [True]
上传断点续传: newput filename [o/r] (o代表覆盖,r代表断点续传)
下载断点续传: newget filename [o/r]
4.涉及到目录的操作,用户登录后,程序会给用户一个“锚位”----以用户名字命名的家目录,使用户无论怎么操作,都只能在这个目录底下。而在发给用户的目录信息时,隐去上层目录信息。
5.用户在创建时,磁盘配额大小默认是100M,在上传文件时,程序会计算当前目录大小加文件大小是否会超过配额上限。未超过,上传;超过,返回磁盘大小不够的信息。磁盘配额可通过用户管理程序修改。
6.文件上传和下载后都会进行MD5值比对,验证文件是否一致。
7.服务端和客户端都有显示进度条功能,启用该功能会降低文件传输速度,这是好看的代价。
8.文件断点续传,支持文件上传和下载断点续传。断点续传上传功能还会检测用户磁盘空间是否足够。(断点续传命令使用前面new+put/get命名,包含put/get所有功能,由于逻辑增多,代码复杂,特地保留原put/get,以备后用)。
程序结构:
完整代码:
1.客户端
#Author:Zheng Na import os,sys BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ####获取当前文件的上一级的上一级目录 sys.path.append(BASE_DIR) from core.client import FtpClient if __name__ == '__main__': ftp = FtpClient() ftp.connect('localhost', 9999) auth_tag = False count = 0 while auth_tag != True: ####功能:3次验证不通过即退出 count += 1 if count <= 3: auth_tag = ftp.auth() else: exit() ftp.interactive() ftp.close()
####用户端配置文件#### [DEFAULT] logfile = ../log/client.log download_dir= ../temp ####日志文件位置#### [log] logfile = ../log/client.log ####下载文件存放位置#### [download] download_dir= ../temp
#Author:Zheng Na import os,configparser,logging ####读取配置文件#### 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, encoding='utf-8') ####设定日志目录#### 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+')
# Author:Zheng Na import socket,os,json,hashlib,sys,time,getpass,logging import core.settings def hashmd5(*args): ####MD5加密 m = hashlib.md5() m.update(str(*args).encode()) ciphertexts = m.hexdigest() ####密文 return ciphertexts def processbar(part, total): ####进度条,运行会导致程序变慢 if total != 0: done = int(50 * part / total) sys.stdout.write("\r[%s%s]" % ('█' * done, ' ' * (50 - done))) ####注意:一个方块对应2个空格 sys.stdout.write('{:.2%}'.format(part / total) + ' ' * 3 + str(part) + '/' + str(total)) sys.stdout.flush() class FtpClient(object): def __init__(self): self.client = socket.socket() def connect(self, ip, port): ####连接 self.client.connect((ip, port)) def auth(self): ####用户认证 username = input("请输入用户名>>>:").strip() # password = getpass.getpass("请输入密码>>>:").strip() ####在linux上输入密码不显示,此模块在pycharm中无法使用 password = input("请输入密码>>>:").strip() ####Windows测试用 password = hashmd5(password) msg = { 'username': username, 'password': password } self.client.send(json.dumps(msg).encode('utf-8')) server_response = self.client.recv(1024).decode('utf-8') logging.info(server_response) if server_response == 'ok': print("认证通过!") return True else: print(server_response) return False def interactive(self): ####交互 while True: self.pwd('pwd') ####打印家目录 cmd = input(">> ").strip() if len(cmd) == 0: continue cmd_str = cmd.split()[0] ####用户输入的第一个值必定是命令 if hasattr(self, cmd_str): ####反射:判断一个对象中是否有字符串对应的方法或属性 func = getattr(self, cmd_str) ####利用反射来解耦:根据字符串去获取对象里对应的方法的内存地址或对应属性的值 func(cmd) ####调用命令对应的方法 else: self.help() def help(self): ####帮助 msg = ''' 仅支持如下命令: ls du pwd cd dirname/cd ./cd .. mkdir dirname rm filename rmdir dirname mv filename/dirname filename/dirname get filename [True] (True代表覆盖) put filename [True] (True代表覆盖) newget filename [o/r] (后续增加的新功能,支持断点续传,o代表覆盖,r代表断点续传) newput filename [o/r] (后续增加的新功能,支持断点续传,o代表覆盖,r代表断点续传) ''' print(msg) def pwd(self, *args): ####查看当前目录 cmd_split = args[0].split() if len(cmd_split) == 1: msg = {'action': 'pwd'} self.exec_linux_cmd(msg) else: self.help() def ls(self, *args): ####文件浏览 cmd_split = args[0].split() if len(cmd_split) == 1: msg = {'action': 'ls'} self.exec_linux_cmd(msg) else: self.help() def du(self, *args): ####查看当前目录大小 cmd_split = args[0].split() if len(cmd_split) == 1: msg = {'action': 'du'} self.exec_linux_cmd(msg) else: self.help() def cd(self, *args): ####切换目录 cmd_split = args[0].split() if len(cmd_split) == 1: dirname = '' elif len(cmd_split) == 2: dirname = cmd_split[1] else: return help() msg = { "action": 'cd', "dirname": dirname } self.exec_linux_cmd(msg) def mkdir(self, *args): ####生成目录 cmd_split = args[0].split() if len(cmd_split) == 2: dirname = cmd_split[1] msg = { "action": 'mkdir', "dirname": dirname } self.exec_linux_cmd(msg) else: help() def rm(self, *args): ####删除文件 cmd_split = args[0].split() if len(cmd_split) == 2: filename = cmd_split[1] msg = { "action": 'rm', "filename": filename, "confirm": True ####确认是否直接删除标志 } self.exec_linux_cmd(msg) else: help() def rmdir(self, *args): ####删除目录 cmd_split = args[0].split() if len(cmd_split) == 2: dirname = cmd_split[1] msg = { "action": 'rmdir', "dirname": dirname, "confirm": True ####确认是否直接删除标志 } self.exec_linux_cmd(msg) else: help() def mv(self, *args): ####实现功能:移动文件,移动目录,文件重命名,目录重命名 cmd_split = args[0].split() if len(cmd_split) == 3: objname = cmd_split[1] dstname = cmd_split[2] msg = { "action": 'mv', "objname": objname, "dstname": dstname } self.exec_linux_cmd(msg) else: help() 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): ####判断是否为list类型 for i in server_response: print(i) else: print(server_response) def get(self, *args): ####下载文件 cmd_split = args[0].split() override = cmd_split[-1] ####override:是否覆盖参数,True表示覆盖,放在最后一位 # print(override,type(override)) if override != 'True': override = 'False' # print(override) if len(cmd_split) > 1: filename = cmd_split[1] filepath = os.path.join(core.settings.download_dir, filename) if override != 'True' and os.path.isfile(filepath): ####判断下载目录是否已存在同名文件 override_tag = input('文件已存在,要覆盖文件请输入yes >>>:').strip() if override_tag == 'yes': self.put('put %s True' % filename) else: print('下载取消') else: 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 f = open(filepath, 'wb') while filesize > receive_size: if filesize - receive_size > 1024: size = 1024 else: size = filesize - receive_size data = self.client.recv(size) f.write(data) receive_size += len(data) processbar(receive_size, filesize) ####打印进度条 f.close() # receive_filemd5 = os.popen('md5sum %s' % filepath).read().split()[0] receive_filemd5 = 'a' ####Windows测试用 print('\r\n', filename, 'md5:', receive_filemd5, '原文件md5:', filemd5) if receive_filemd5 == filemd5: print('文件接收完成!') else: print('Error,文件接收异常!') else: help() def put(self, *args): ####上传文件 cmd_split = args[0].split() override = cmd_split[-1] ####override:是否覆盖参数,True表示覆盖,放在最后一位 if override != 'True': override = 'False' # print(cmd_split,override) if len(cmd_split) > 1: filename = cmd_split[1] filepath = os.path.join(core.settings.download_dir, filename) if os.path.isfile(filepath): filesize = os.path.getsize(filepath) ####法1 # filesize = os.stat(filepath).st_size ####法2 ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三步,代码量更多,效率也低 # filemd5 = os.popen('md5sum %s' % filepath).read().split()[0] filemd5 = 'a' ####Windows测试 msg = { "action": 'put', "filename": filename, "filesize": filesize, "filemd5": filemd5, "override": override } # logging.info(msg) self.client.send(json.dumps(msg).encode('utf-8')) ###防止粘包,等服务器确认:这里最好列出一些标准请求码,告诉客户端是否有权限传输文件,类似200 403等 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': f = open(filepath, 'rb') send_size = 0 for line in f: send_size += len(line) self.client.send(line) processbar(send_size, filesize) else: print('\r\n', "file upload success...") f.close() server_response = self.client.recv(1024).decode('utf-8') print(server_response) else: print(filename, 'is not exist') else: self.help() def newget(self, *args): ####下载文件,具有断点续传功能 cmd_split = args[0].split() tag = cmd_split[-1] ####tag:o代表覆盖,r代表续传,放在最后一位 if len(cmd_split) > 1: filename = cmd_split[1] filepath = os.path.join(core.settings.download_dir, filename) if tag not in ('o', 'r'): if os.path.isfile(filepath): ####判断下载目录是否已存在同名文件 tag = input('文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:').strip() else: tag = 'o' if tag in ('o', 'r'): if tag == 'r': local_filesize = os.path.getsize(filepath) else: local_filesize = 0 # 本地文件大小 msg = { 'action': 'newget', 'filename': filename, 'filesize': local_filesize, 'filemd5': '', } 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 = local_filesize if tag == 'r': f = open(filepath, 'ab+') ####用于断点续传 else: f = open(filepath, 'wb+') ####用于覆盖或者新生成文件 while filesize > receive_size: if filesize - receive_size > 1024: size = 1024 else: size = filesize - receive_size data = self.client.recv(size) f.write(data) receive_size += len(data) # print(receive_size, len(data)) ####打印数据流情况 processbar(receive_size, filesize) ####打印进度条 f.close() # receive_filemd5 = os.popen('md5sum %s' % filepath).read().split()[0] receive_filemd5 = 'a' ####Windows测试用 print('\r\n', filename, 'md5:', receive_filemd5, '原文件md5:', filemd5) if receive_filemd5 == filemd5: print('文件接收完成!') else: print('Error,文件接收异常!') else: print("文件未下载") else: help() def newput(self, *args): ####上传文件,具有断点续传功能 cmd_split = args[0].split() tag = cmd_split[-1] ####tag:r代表续传,o代表覆盖,放在最后一位 if tag not in ('o', 'r'): tag = 'unknown' # print(cmd_split,tag) if len(cmd_split) > 1: filename = cmd_split[1] filepath = os.path.join(core.settings.download_dir, filename) if os.path.isfile(filepath): filesize = os.path.getsize(filepath) ####法1 # filesize = os.stat(filepath).st_size ####法2 ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低 # filemd5 = os.popen('md5sum %s' % filepath).read().split()[0] filemd5 = 'a' # Windows测试 msg = { "action": 'newput', "filename": filename, "filesize": filesize, "filemd5": filemd5, "tag": tag } # logging.info(msg) self.client.send(json.dumps(msg).encode('utf-8')) ####发送msg server_response1 = self.client.recv(1024).decode() ####接收文件存在或者文件不存在 # logging.info(server_response) print(server_response1) if server_response1 == '文件存在': ####再确认一遍tag if tag == 'unknown': tag = input('文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:').strip() if tag not in ('o', 'r'): tag = 'unknown' else: ####文件不存在时 tag = 'o' self.client.send(tag.encode()) server_response2 = json.loads(self.client.recv(1024).decode('utf-8')) # print('server_response2:', server_response2) content = server_response2['content'] if tag == 'o' or tag == 'r': if content == 'begin': position = server_response2['position'] print(position) f = open(filepath, 'rb') f.seek(position, 0) send_size = position for line in f: send_size += len(line) self.client.send(line) processbar(send_size, filesize) else: print('\n', "file upload success...") f.close() server_response3 = self.client.recv(1024).decode('utf-8') ####服务端对比md5后发送是否成功接收文件,成功或失败 print(server_response3) else: print(content) ####content:服务器已存在同名文件 或。。。 else: print(content) ####content:文件未上传 else: print(filename, 'is not exist') else: self.help() def newput2(self, *args): ####上传文件,具有断点续传功能,网友写的,与我写的newput功能差不多 cmd_split = args[0].split() override = cmd_split[-1] ####override:是否覆盖参数,放在最后一位 if override != 'True': override = 'False' # print(cmd_split,override) if len(cmd_split) > 1: filename = cmd_split[1] filepath = os.path.join(core.settings.download_dir, filename) if os.path.isfile(filepath): filesize = os.path.getsize(filepath) ####法1 # filesize = os.stat(filepath).st_size ####法2 ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低 filemd5 = os.popen('md5sum %s' % filepath).read().split()[0] filemd5 = 'a' ####Windows测试 msg = { "action": 'newput2', "filename": filename, "filesize": filesize, "filemd5": filemd5, "override": override } # logging.info(msg) self.client.send(json.dumps(msg).encode('utf-8')) ####防止粘包,等服务器确认:这里最好列出一些标准请求码,告诉客户端是否有权限传输文件,类似200 403等 server_response = self.client.recv(1024) # logging.info(server_response) print(server_response) if server_response == b'file have exits, and is a directory, do nothing!': print('文件已存在且为目录,请先修改文件或目录名字,然后再上传') elif server_response == b'file have exits, do nothing!': override_tag = input('文件已存在,要覆盖文件请输入yes,要断点续传请输入r >>>:').strip() if override_tag == 'yes': self.client.send(b'no need to do anything') ####服务端在等待是否续传的信号,发送给服务端确认(功能号:s1) time.sleep(0.5) ####防止黏贴,功能需改进 self.put('put %s True' % filename) elif override_tag == 'r': self.client.send(b'ready to resume from break point') ####服务端在等待是否续传的信号,发送给服务端确认(功能号:s1) self.client.recv(1024) ####这边接收服务端发送过来的du信息,不显示,直接丢弃 server_response = json.loads(self.client.recv(1024).decode('utf-8')) print(server_response) if server_response['state'] == 'True': exist_file_size = server_response['position'] f = open(filepath, 'rb') f.seek(exist_file_size, 0) send_size = exist_file_size for line in f: send_size += len(line) self.client.send(line) processbar(send_size, filesize) else: print('\r\n', '文件传输完毕') f.close() server_response = self.client.recv(1024).decode('utf-8') print(server_response) else: print(server_response['content']) else: self.client.send(b'no need to do anything') ####服务端在等待是否续传的信号,发送给服务端确认(功能号:s1) 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': f = open(filepath, 'rb') send_size = 0 for line in f: send_size += len(line) self.client.send(line) processbar(send_size, filesize) else: print('\r\n', "file upload success...") f.close() server_response = self.client.recv(1024).decode('utf-8') print(server_response) else: print(filename, 'is not exist') else: self.help() def close(self): self.client.close()
2.服务端
# Author:Zheng Na ####os.path.abspath(__file__) 获取当前当前文件的绝对路径 ####os.path.dirname()获取当前文件上一层目录 import os,sys BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ####获取当前文件的上一级的上一级目录 sys.path.append(BASE_DIR) import socketserver from core.server import MyTCPHandler from core.usermanagement import UserOpr if __name__ == '__main__': mainpage = ''' 主页 1、启动服务器 2、进入用户管理 退出请按q ''' while True: print('\033[1;35m{}\033[0m'.format(mainpage)) choice = input('>>>:') if choice == 'q': exit() elif choice == '1': HOST, PORT = "localhost", 9999 server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) server.serve_forever() elif choice == '2': useropr = UserOpr() # useropr.query_all_user() ####查询所有用户信息 useropr.interactive() else: print("\033[1;31m输入错误,请重新输入\033[0m") continue
####用户端配置文件#### [DEFAULT] logfile = ../log/server.log usermgr_log = ../log/usermgr.log upload_dir = ../user_files userinfo_dir = ../user_info ####日志文件位置#### [log] logfile = ../log/server.log usermgr_log = ../log/usermgr.log ####上传文件存放位置#### [upload] upload_dir = ../user_files ####用户信息存放位置#### [userinfo] userinfo_dir = ../user_info
#Author:Zheng Na import os,configparser,logging ####读取配置文件#### base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ####获取当前文件的上一级的上一级目录 config_file = os.path.join(base_dir, 'conf/server.conf') #####将2个路径组合后返回 cf = configparser.ConfigParser() cf.read(config_file,encoding='utf-8') # 不编码会报错:UnicodeDecodeError: 'gbk' codec can't decode byte 0xaf in position 12: illegal multibyte sequence ####设定日志目录#### '''先判断日志文件是否存在,如果不存在,则创建''' if os.path.exists(cf.get('log', 'usermgr_log')): usermgr_log = cf.get('log', 'usermgr_log') else: usermgr_log = 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('userinfo', 'userinfo_dir')): userinfo_dir = cf.get('userinfo', 'userinfo_dir') else: userinfo_dir = os.path.join(base_dir, 'user_info') ####设置日志格式#### logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', filename=usermgr_log, filemode='a+')
#Author:Zheng Na import os,json import core.settings def query_user(username): ####查询用户 filelist = os.listdir(core.settings.userinfo_dir) ####列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印 dict = {} for filename in filelist: with open (os.path.join( core.settings.userinfo_dir,filename),'r',encoding='utf-8') as f: content = json.load(f) ####json反序列化 if content['username'] == username: dict = {'filename':filename,'content':content} # print("查询结果:",dict) return dict
# Author:Zheng Na import socketserver,sys,json,os,time,shutil import core.common def processbar(part, total): ####进度条,运行会导致程序变慢 if total != 0: done = int(50 * part / total) sys.stdout.write("\r[%s%s]" % ('█' * done, ' ' * (50 - done))) ####注意:一个方块对应2个空格 sys.stdout.write('{:.2%}'.format(part / total)+' '*3+str(part)+'/'+str(total)) sys.stdout.flush() def timestamp_to_formatstringtime(timestamp): ####时间戳转化为格式化的字符串 structtime = time.localtime(timestamp) formatstringtime = time.strftime("%Y%m%d %H:%M:%S",structtime) return formatstringtime class MyTCPHandler(socketserver.BaseRequestHandler): def handle(self): 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.spacesize = auth_result['content']['spacesize'] auth_tag = True print(self.username,self.spacesize) user_homedir = os.path.join(core.settings.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.decode()) # logging.info(self.client_address) if not self.data: print(self.client_address, "断开了") break cmd_dic = json.loads(self.data.decode('utf-8')) # print(cmd_dic) action = cmd_dic["action"] # logging.info(cmd_dic) if hasattr(self, action): func = getattr(self, action) func(cmd_dic) else: print("未支持指令:",action) # logging.info('current direcory: %s' % self.positoion) def auth(self): ####用户认证 self.data = json.loads(self.request.recv(1024).decode('utf-8')) print(self.data) recv_username = self.data['username'] recv_password = self.data['password'] query_result = core.common.query_user(recv_username) print(query_result) if query_result == None: self.request.send(b'user does not exist') elif query_result['content']['password'] == recv_password: self.request.send(b'ok') return query_result ####返回查询结果 elif query_result['content']['password'] != recv_password: self.request.send(b'password error') else: self.request.send(b'unkonwn error') def pwd(self,*args): ####查看当前目录 current_position = self.position result = current_position.replace(core.settings.file_dir,'') ####截断目录信息,使用户只能看到自己的家目录信息 print(result) 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): f_abspath = os.path.join(self.position,f) ####给出文件的绝对路径,不然程序会找不到文件 if os.path.isdir(f_abspath): type = 'd' elif os.path.isfile(f_abspath): type = 'f' else: type = 'unknown' fsize = os.path.getsize(f_abspath) ftime = timestamp_to_formatstringtime(os.path.getctime(f_abspath)) result.append('%-20s%-7s%-10s%-23s' % (f,type,fsize,ftime)) self.request.send(json.dumps(result).encode('utf-8')) def du_calc(self): # 注意不能使用os.path.getsize('D:\python-study\s14')返回的是所有目录大小的和 '''统计纯文件和目录占用空间大小,结果小于在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 in dirs: #计算目录占用空间,Linux中每个目录占用4096bytes,实际上也可以按这个值来相加 dirsize += os.path.getsize(os.path.join(root,d)) for f in files: #计算文件占用空间 filesize += os.path.getsize(os.path.join(root,f)) totalsize = dirsize + filesize return totalsize def du(self,*args): ####查看当前目录大小 totalsize = self.du_calc() result = 'current directory total sizes: %d' % totalsize print(result) self.request.send(json.dumps(result).encode('utf-8')) return totalsize def cd(self,*args): ####切换目录,这个函数实在是没怎么看懂 print(*args) user_homedir = os.path.join(core.settings.file_dir,self.username) cmd_dic = args[0] error_tag = False '''判断目录信息''' if cmd_dic['dirname'] == '': self.position = user_homedir elif cmd_dic['dirname'] in ('.','/') or '//' in cmd_dic['dirname']: ####'.','/','//','///+'匹配 pass elif cmd_dic['dirname'] == '..': if user_homedir != self.position and user_homedir in self.position: ####当前目录不是家目录,并且当前目录是家目录下的子目录 self.position = os.path.dirname(self.position) elif '.' not in cmd_dic['dirname'] and os.path.isdir(os.path.join(self.position,cmd_dic['dirname'])):####'.' not in cmd_dict['dir'] 防止../..输入 self.position = os.path.join(self.position,cmd_dic['dirname']) else: error_tag = True if error_tag: result = 'Error,%s is not path here,or path does not exist!' % cmd_dic['dirname'] 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 success' % 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 deleted." % filename else: result = 'Not file deleted' elif os.path.isdir(file_abspath): result = '%s is a dir,please use 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'] dir_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(dir_abspath): if confirm == True: shutil.rmtree(dir_abspath) result = '%s have been deleted.' % dirname else: result = 'Not dir deleted.' elif os.path.isfile(dir_abspath): result = '%s is a file,please use rm' % dirname else: result = 'directory %s not exist!' % dirname self.request.send(json.dumps(result).encode('utf-8')) def mv(self,*args): ####实现功能:移动文件,移动目录,文件重命名,目录重命名 try: print(args) objname = args[0]['objname'] dstname = args[0]['dstname'] obj_abspath = os.path.join(self.position,objname) dst_abspath = os.path.join(self.position,dstname) if os.path.isfile(obj_abspath): if os.path.isdir(dst_abspath) or not os.path.exists(dst_abspath): shutil.move(obj_abspath,dst_abspath) result = 'move success' elif os.path.isfile(dst_abspath): result = 'moving cancel,file has been exist.' elif os.path.isdir(obj_abspath): if os.path.isdir(dst_abspath) or not os.path.exists(dst_abspath): shutil.move(obj_abspath,dst_abspath) result = 'move success' elif os.path.isfile(dst_abspath): result = 'moving cancel,%s is a file.'% dst_abspath else: result = 'nothing done' self.request.send(json.dumps(result).encode('utf-8')) except Exception as e: print(e) result = 'moving fail,' + e self.request.send(json.dumps(result).encode('utf-8')) def get(self,*args): ####发送给客户端文件 cmd_dic = args[0] filename = cmd_dic['filename'] filepath = os.path.join(self.position, filename) if os.path.isfile(filepath): filesize = os.path.getsize(filepath) ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低 # filemd5 = os.popen('md5sum %s' % filepath).read().split()[0] filemd5 = 'a' ####Windows测试用 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信号,下一步发送文件 f = open(filepath, 'rb') send_size = 0 for line in f: send_size += len(line) self.request.send(line) # processbar(send_size, filesize) ####服务端进度条,不需要可以注释掉 else: print('文件传输完毕') f.close() else: print(filepath, '文件未找到') self.request.send(json.dumps('Filenotfound').encode('utf-8')) def put(self, *args): ####接收客户端文件 cmd_dic = args[0] filename = os.path.basename(cmd_dic['filename']) ####传输进来的文件名可能带有路径,将路径去掉 filesize = cmd_dic['filesize'] filemd5 = cmd_dic['filemd5'] override = cmd_dic['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.spacesize, current_size, filesize) if self.spacesize >= current_size + filesize: self.request.send(b'begin') ####发送开始传输信号 f = 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) f.write(data) receive_size += len(data) # print(receive_size,len(data)) ####打印每次接收的数据 # processbar(receive_size, filesize) ####服务端进度条,不需要可以注释掉 else: print("file [%s] has uploaded..." % filename) f.close() # receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0] receive_filemd5 = 'a' ####windows 测试用 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.spacesize,current_size, self.spacesize - current_size, filesize)) def newget(self, *args): ####发送给客户端文件,具有断点续传功能 # print('get receive the cmd',args[0]) cmd_dic = args[0] filename = cmd_dic['filename'] send_size = cmd_dic['filesize'] 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) ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低 # filemd5 = os.popen('md5sum %s' % file_path).read().split()[0] filemd5 = 'a' #Windows测试用 msg = { 'action': 'newget', 'filename': filename, 'filesize': filesize, 'filemd5': filemd5, } print(msg) self.request.send(json.dumps(msg).encode('utf-8')) self.request.recv(1024) ####接收ACK信号,下一步发送文件 f = open(file_path, 'rb') f.seek(send_size,0) for line in f: send_size += len(line) self.request.send(line) # processbar(send_size, filesize) ####服务端进度条,不需要可以注释掉 else: print('文件传输完毕') f.close() else: print(file_path, '文件未找到') self.request.send(json.dumps('Filenotfound').encode('utf-8')) def newput(self, *args): ####接收客户端文件,具有断点续传功能 cmd_dict = args[0] filename = os.path.basename(cmd_dict['filename']) ####传输进来的文件名可能带有路径,将路径去掉 filesize = cmd_dict['filesize'] filemd5 = cmd_dict['filemd5'] tag = cmd_dict['tag'] receive_size = 0 file_path = os.path.join(self.position, filename) if os.path.isfile(file_path): ####检测文件是否已经存在 self.request.send('文件存在'.encode()) tag = self.request.recv(1024).decode() ####接收客户端ack信号 if tag == 'o': os.remove(file_path)####如果文件已经存在,先删除,再计算磁盘空间大小 self.upload(tag,filename, filemd5, filesize, file_path, receive_size) elif tag == 'r': exist_file_size = os.path.getsize(file_path) if exist_file_size <= filesize: receive_size = exist_file_size self.upload(tag,filename, filemd5, filesize, file_path, receive_size) else: print('服务器已存在同名文件且比原文件大') msg = { "content": '服务器已存在同名文件且比原文件大' } self.request.send(json.dumps(msg).encode('utf-8')) else: msg = { "content": '文件未上传' } self.request.send(json.dumps(msg).encode('utf-8')) else: ####文件不存在:如果文件不存在的话,就不用管tag了,直接计算磁盘空间,然后上传 self.request.send('文件不存在!'.encode()) tag = self.request.recv(1024).decode() ####接收客户端ack信号 self.upload(tag,filename,filemd5,filesize,file_path,receive_size) def upload(self,tag,filename,filemd5,filesize,file_path,receive_size): current_size = self.du_calc() print('用户总空间:',self.spacesize, '目前剩余空间:',current_size,'文件大小:', filesize) if tag == 'r': needrecv_size = filesize - receive_size else: needrecv_size = filesize if self.spacesize >= current_size + needrecv_size: msg = { "position":receive_size, "content":'begin' } self.request.send(json.dumps(msg).encode('utf-8')) ####发送开始传输信号 if tag == 'r': f = open(file_path, 'ab') else: f = 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) f.write(data) receive_size += len(data) # processbar(receive_size, filesize) ####服务端进度条,不需要可以注释掉 f.close() # receive_filemd5 = os.popen('md5sum %s' % file_path).read().split()[0] receive_filemd5 = 'a' print('\r\n', filename, '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: msg = { "content":'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, filesize:%d' % ( self.spacesize, current_size, self.spacesize - current_size, filesize) } self.request.send(json.dumps(msg).encode('utf-8')) ## def newput2(self, *args): ####接收客户端文件,具有断点续传功能 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) # print(file_path,os.path.isdir(file_path)) if override != 'True' and os.path.exists(file_path): ####检测文件是否已经存在 if os.path.isdir(file_path): self.request.send(b'file have exits, and is a directory, do nothing!') elif os.path.isfile(file_path): self.request.send(b'file have exits, do nothing!') resume_signal = self.request.recv(1024) ####接收客户端发来的是否从文件断点续传的信号 if resume_signal == b'ready to resume from break point': ####执行断点续传功能 exist_file_size = os.path.getsize(file_path) current_size = self.du() time.sleep(0.5) ####防止粘包 print('用户空间上限:%d, 当前已用空间:%d, 已存在文件大小:%d, 上传文件大小:%d ' % (self.spacesize,current_size,exist_file_size,filesize)) if self.spacesize >= (current_size - exist_file_size + filesize): ####判断剩余空间是否足够 if exist_file_size < filesize: receive_size = exist_file_size print('服务器上已存在的文件大小为:',exist_file_size) msg = { 'state': True, 'position': exist_file_size, 'content': 'ready to receive file' } self.request.send(json.dumps(msg).encode('utf-8')) f = open(file_path, 'ab+') while filesize > receive_size: if filesize - receive_size > 1024: size = 1024 else: size = filesize - receive_size data = self.request.recv(size) f.write(data) receive_size += len(data) # print(receive_size,len(data)) ####打印每次接收的数据 # processbar(receive_size, filesize) ####服务端进度条,不需要可以注释掉 f.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: ####如果上传的文件小于当前服务器上的文件,则为同名但不同文件,不上传。实际还需要增加其他判断条件,判断是否为同一文件。 msg = { 'state': False, 'position': '', 'content': 'Error, file mismatch, do nothing!' } self.request.send(json.dumps(msg).encode('utf-8')) else: ####如果续传后的用户空间大于上限,拒接续传 msg = { 'state': False, 'position':'', 'content':'Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, need_size:%d' % (self.user_spacesize, current_size, self.user_spacesize - current_size, filesize - exits_file_size) } self.request.send(json.dumps(msg).encode('utf-8')) else: pass 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.spacesize, current_size, filesize) if self.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.spacesize, current_size, self.spacesize - current_size, filesize))
#Author:Zheng Na import os,time,json,shutil,hashlib import core.common def hashmd5(self, *args): m = hashlib.md5() m.update(str(*args).encode()) ciphertexts = m.hexdigest() # 密文 return ciphertexts # 用户操作类 class UserOpr(object): def __init__(self): pass def query_userinfo(self,username): query_result = core.common.query_user(username) if query_result != None: # 用户存在 print(query_result) else: print("用户不存在") def save_userinfo(self,username): # 保存用户信息 query_result = core.common.query_user(username) # 检查是否已存在同名用户,如果没有查询结果应该为None if query_result == None: # 用户不存在 id = time.strftime('%Y%m%d%H%M%S', time.localtime()) # 将结构化时间(即元组)转换成格式化的字符串,比如20181211110148 password = '123456' userinfo = { 'username':username, 'id':id, 'phonenumber':'', 'password':hashmd5(password), 'spacesize': 104857600, ## 初始分配100MB存储空间 'level':1 # 会员等级,初始为1,普通会员 } with open(os.path.join(core.settings.userinfo_dir,id),'w',encoding='utf-8') as f: json.dump(userinfo,f) print("用户信息保存完毕") try: # 创建用户家目录 os.mkdir(os.path.join(core.settings.file_dir,username)) print('用户目录创建成功!') except Exception as e: print('用户目录创建失败!',e) else: print("用户名重复,信息未保存") def change_userinfo(self,username): # 修改用户信息 query_result = core.common.query_user(username) # 检测用户是否存在,不存在不处理 if query_result != None: # 用户存在 filename = query_result['filename'] userinfo = query_result['content'] print('before update: ', userinfo) update_item = input("请输入要修改的项目,例如password,phonenumber,spacesize,level:") if update_item in ('username','id'): print(update_item, "项不可更改") elif update_item in ('password','phonenumber','spacesize','level'): print("update item: %s" % update_item) update_value = input("请输入要修改的项目的新值:") if update_item == 'password': userinfo[update_item] = hashmd5(update_value) else: userinfo[update_item] = update_value with open(os.path.join(core.settings.userinfo_dir, filename), 'w', encoding='utf-8') as f: json.dump(userinfo, f) print(update_item, "项用户信息变更保存完毕") print('after update: ', userinfo) else: print('输入信息错误,', update_item, '项不存在') else: print('用户不存在,无法修改') def delete_user(self,username): query_result = core.common.query_user(username) # 检测用户是否存在,不存在不处理 if query_result != None: # 用户存在 filename = query_result['filename'] userfile_path = os.path.join(core.settings.userinfo_dir,filename) os.remove(userfile_path) query_result_again = core.common.query_user(username) if query_result_again == None: print('用户信息文件删除成功!') try: shutil.rmtree(os.path.join(core.settings.file_dir,username)) print('用户家目录删除成功') except Exception as e: print('用户家目录删除失败:',e) else: print('用户信息文件删除失败!') else: print('用户不存在或者已经被删除') def query_all_user(self): # 查询所有用户信息,用于调试使用 filelist = os.listdir(core.settings.userinfo_dir) if filelist != []: for filename in filelist: with open(os.path.join(core.settings.userinfo_dir,filename),'rb') as f: userinfo = json.load(f) print(filename,userinfo) else: print("用户信息为空") def interactive(self): userpage = ''' 用户管理界面 1、新增用户 2、查询用户 3、修改用户 4、删除用户 退出请按q 返回上一界面请按r ''' userpage_data = { '1': 'save_userinfo', '2': 'query_userinfo', '3': 'change_userinfo', '4': 'delete_user' } while True: print('\033[1;35m{}\033[0m'.format(userpage)) choice = input('请输入你的选择:').strip() if choice == 'q': exit("退出程序!") elif choice == 'r': break elif choice in userpage_data: username = input("请输入用户名:").strip() if username == '': print('用户不能为空') continue if hasattr(self,userpage_data[choice]): f = getattr(self, userpage_data[choice]) f(username) else: print("\033[1;31m输入错误,请重新输入\033[0m") continue
运行示例:
D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/ftp/ftp_server/bin/main.py 主页 1、启动服务器 2、进入用户管理 退出请按q >>>:2 用户管理界面 1、新增用户 2、查询用户 3、修改用户 4、删除用户 退出请按q 返回上一界面请按r 请输入你的选择:1 请输入用户名:xiaoming 用户信息保存完毕 用户目录创建成功! 用户管理界面 1、新增用户 2、查询用户 3、修改用户 4、删除用户 退出请按q 返回上一界面请按r 请输入你的选择:2 请输入用户名:xiaoming {'filename': '20181220164706', 'content': {'username': 'xiaoming', 'id': '20181220164706', 'phonenumber': '', 'password': 'd41d8cd98f00b204e9800998ecf8427e', 'spacesize': 104857600, 'level': 1}} 用户管理界面 1、新增用户 2、查询用户 3、修改用户 4、删除用户 退出请按q 返回上一界面请按r 请输入你的选择:3 请输入用户名:xiaoming before update: {'username': 'xiaoming', 'id': '20181220164706', 'phonenumber': '', 'password': 'd41d8cd98f00b204e9800998ecf8427e', 'spacesize': 104857600, 'level': 1} 请输入要修改的项目,例如password,phonenumber,spacesize,level:phonenumber update item: phonenumber 请输入要修改的项目的新值:1234567890 phonenumber 项用户信息变更保存完毕 after update: {'username': 'xiaoming', 'id': '20181220164706', 'phonenumber': '1234567890', 'password': 'd41d8cd98f00b204e9800998ecf8427e', 'spacesize': 104857600, 'level': 1} 用户管理界面 1、新增用户 2、查询用户 3、修改用户 4、删除用户 退出请按q 返回上一界面请按r 请输入你的选择:4 请输入用户名:xiaoming 用户信息文件删除成功! 用户家目录删除成功 用户管理界面 1、新增用户 2、查询用户 3、修改用户 4、删除用户 退出请按q 返回上一界面请按r 请输入你的选择:q 退出程序! Process finished with exit code 1
D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/ftp/ftp_server/bin/main.py 主页 1、启动服务器 2、进入用户管理 退出请按q >>>:1 D:\software\Python3.6.5\python.exe D:/python-study/s14/Day08/ftp/ftp_client/bin/main.py 请输入用户名>>>:zhengna 请输入密码>>>:123123 认证通过! \zhengna >> pwd \zhengna \zhengna >> ls filename type bytes creationtime test d 0 20181214 17:17:05 test.txt f 5028331 20181220 15:43:55 vedio2.avi f 86453774 20181214 17:17:35 \zhengna >> du current directory total sizes: 96510422 \zhengna >> cd test \zhengna\test \zhengna\test >> ls filename type bytes creationtime test2 d 0 20181217 11:21:07 \zhengna\test >> rmdir test2 test2 have been deleted. \zhengna\test >> cd .. \zhengna \zhengna >> rm test test is a dir,please use rmdir \zhengna >> rm test.txt test.txt have been deleted. \zhengna >> mkdir aa aa created success \zhengna >> mv aa bb move success \zhengna >> put test.ttx test.ttx is not exist \zhengna >> put test.txt begin [██████████████████████████████████████████████████]100.00% 5028331/5028331 file upload success... file received successfully! \zhengna >> put test.txt 文件已存在,要覆盖文件请输入yes >>>:yes begin [██████████████████████████████████████████████████]100.00% 5028331/5028331 file upload success... file received successfully! \zhengna >> put test.txt True begin [██████████████████████████████████████████████████]100.00% 11178154/11178154 file upload success... file received successfully! \zhengna >> get test.ttx File no found! \zhengna >> get test.txt {'action': 'get', 'filename': 'test.txt', 'filesize': 11178154, 'filemd5': 'a', 'override': 'True'} [██████████████████████████████████████████████████]100.00% 11178154/11178154 test.txt md5: a 原文件md5: a 文件接收完成! \zhengna >> get test.txt 文件已存在,要覆盖文件请输入yes >>>:yes begin [██████████████████████████████████████████████████]100.00% 11178154/11178154 file upload success... file received successfully! \zhengna >> get test.txt True {'action': 'get', 'filename': 'test.txt', 'filesize': 11178154, 'filemd5': 'a', 'override': 'True'} [██████████████████████████████████████████████████]100.00% 11178154/11178154 test.txt md5: a 原文件md5: a 文件接收完成! \zhengna >> newput test.txt 文件存在 文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:o 0 [██████████████████████████████████████████████████]100.00% 11178154/11178154 file upload success... file received successfully! \zhengna >> newput test.txt 文件存在 文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:r 11178154 file upload success... file received successfully! \zhengna >> newget test.txt 文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:o {'action': 'newget', 'filename': 'test.txt', 'filesize': 11178154, 'filemd5': 'a'} [██████████████████████████████████████████████████]100.00% 11178154/11178154 test.txt md5: a 原文件md5: a 文件接收完成! \zhengna >> newget test.txt 文件已存在,要覆盖文件请输入o,要断点续传请输入r >>>:r {'action': 'newget', 'filename': 'test.txt', 'filesize': 11178154, 'filemd5': 'a'} test.txt md5: a 原文件md5: a 文件接收完成! \zhengna >> newput vedio.avi 文件不存在! Error, disk space do not enough! Nothing done! Total: 104857600, current: 97631928, rest:7225672, filesize:86453774 \zhengna >>
参考:http://blog.51cto.com/tryagaintry/1969589
总结:这是我第一次写一个这么复杂的程序,虽然大多数的代码都是参考别人写好的。实现它我大概用了2周左右的时间,在这过程中,我一直都在努力思考,尽量让自己弄明白每段代码实现了什么功能?为什么这么写?有没有更好的实现方式?我知道最终的程序并不完美,但是对我来说,重要的不是我在上方贴的大段大段的代码,而是在这2周码代码的过程中,我从中学到了什么。