Python 程序:ftp
1、ftp实现功能
2、目录结构
3、代码
4、效果展示
一、ftp实现功能
1、用户登陆认证
2、多用户同时登陆
3、不同用户家目录不同
4、查看目录下文件
5、用户可以在家目录下切换目录
6、用户可以在家目录下创建目录
7、用户可以在家目录下删除文件或目录
8、用户磁盘配额(不同用户配额可不同,当文件大小超出服务器分配空间大小拒绝上传)
9、上传:支持断点续传
10、下载:支持断点续传和进度条显示
二、目录结构
三、代码
1 import socket,os,json,sys 2 class FtpClient(object): 3 4 def __init__(self,ip,port): 5 self.client = socket.socket() 6 self.client.connect((ip,port)) 7 self.exit_flag = False 8 self.interactive() 9 10 def auth(self): 11 retry_count = 0 12 while retry_count < 3: 13 username = input("username:").strip() 14 if len(username) == 0:continue 15 # passwd = getpass.getpass("password:") #pycharm显示有问题 16 passwd = input("passwd:").strip() 17 msg_dic = { 18 "action":"ftp_auth", 19 "username":username, 20 "passwd":passwd 21 } 22 self.client.send(json.dumps(msg_dic).encode("utf-8")) 23 24 auth_feedback = self.client.recv(1024) 25 auth_feedback = auth_feedback.decode() 26 if auth_feedback == "success": 27 print("\033[32;1m登录成功!\033[0m") 28 self.username = username 29 self.cur_path = username 30 return True 31 else: 32 print ("\033[31;1m账号密码错误!\033[0m") 33 retry_count +=1 34 else: 35 print ("\033[31;1m尝试次数过多,exit!\033[0m") 36 37 def interactive(self): 38 ftp_version = self.client.recv(1024) 39 print(ftp_version.decode()) 40 self.auth() #登录验证 41 self.help() 42 while True: 43 cmd = input(">>:").strip() 44 if len(cmd) == 0: 45 continue 46 cmd_str = cmd.split()[0] #输入的第一个字符永远为指令,取出 47 if hasattr(self,"cmd_%s"%cmd_str): 48 func = getattr(self,"cmd_%s"%cmd_str) 49 func(cmd) 50 else: 51 self.help() 52 53 def cmd_dir(self,*args): 54 cmd_split = args[0].split() 55 filename = ' '.join(cmd_split[1:]) 56 msg_dic = { 57 "action":cmd_split[0], 58 "filename":filename 59 } 60 self.client.send(json.dumps(msg_dic).encode("utf-8")) 61 # 防止粘包等服务器确认 62 cmd_res_size = self.client.recv(1024) 63 self.client.send(b"ok") 64 cmd_res_size = int(cmd_res_size.decode()) 65 received_size = 0 66 while received_size < cmd_res_size: 67 data = self.client.recv(1024) 68 received_size +=len(data) 69 print(data.decode()) 70 71 def cmd_cd(self,*args): 72 73 cmd_split = args[0].split() 74 filename = ' '.join(cmd_split[1:]) 75 msg_dic = { 76 "action":cmd_split[0], 77 "filename":filename 78 } 79 self.client.send(json.dumps(msg_dic).encode("utf-8")) 80 feedback = self.client.recv(100) 81 feedback = feedback.decode() 82 if feedback.startswith("switch_dir::ok"): 83 self.cur_path = feedback.split("::")[-1] 84 else: 85 print ("\033[31;1m%s\033[0m" % feedback.split("::")[-1]) 86 87 def cmd_rmdir(self,*args): 88 cmd_split = args[0].split() 89 filename = ' '.join(cmd_split[1:]) 90 msg_dic = { 91 "action":cmd_split[0], 92 "filename":filename 93 } 94 self.client.send(json.dumps(msg_dic).encode("utf-8")) 95 feedback = self.client.recv(100) 96 print(feedback.decode()) 97 98 def cmd_mkdir(self,*args): #只支持创建目录,创建文件自己上传就行 99 cmd_split = args[0].split() 100 filename = ' '.join(cmd_split[1:]) 101 msg_dic = { 102 "action":cmd_split[0], 103 "filename":filename 104 } 105 self.client.send(json.dumps(msg_dic).encode("utf-8")) 106 feedback = self.client.recv(100) 107 print(feedback.decode()) 108 109 def cmd_put(self,*args): 110 cmd_split = args[0].split() 111 if len(cmd_split) > 1: 112 filename = cmd_split[1] 113 if os.path.isfile(filename): 114 filesize = os.stat(filename).st_size 115 # msg_str = "%s|%s"%(filename,filesize) 116 msg_dic = { 117 "action":cmd_split[0], 118 "filename":filename, 119 "size":filesize, 120 "has_file":False 121 } 122 self.client.send(json.dumps(msg_dic).encode("utf-8")) 123 #防止粘包等服务器确认 124 server_response = self.client.recv(1024) #此时服务器端判断大小是否超出磁盘配额,返回参数 125 data = server_response.decode() 126 data_list= data.split("|") 127 if data_list[0] == "ok" and data_list[1] == "no": 128 f = open(filename,"rb") 129 for line in f : 130 self.client.send(line) 131 else: 132 print("file upload success..") 133 f.close() 134 elif data_list[0] == "ok" and data_list[1] != "no": 135 print(data_list[1]) 136 size = int(data_list[1]) 137 f = open(filename,"rb") 138 f.seek(size)#seek到断点位置 139 for line in f : 140 self.client.send(line) 141 else: 142 print("file upload success..") 143 f.close() 144 else: 145 print(server_response.decode()) 146 else: 147 print(filename,"is not exist") 148 149 def cmd_get(self,*args): 150 cmd_split = args[0].split() 151 if len(cmd_split) > 1: 152 filename = cmd_split[1] 153 msg_dic = { 154 "action":cmd_split[0], 155 "filename":filename, 156 "size":0, 157 "overridden":True 158 } 159 160 if os.path.isfile(filename): 161 print("文件已存在") 162 old_size = os.stat(filename).st_size 163 msg_dic["size"] = old_size 164 else: 165 print("文件不存在") 166 old_size = 0 167 msg_dic["size"] = old_size 168 169 self.client.send(json.dumps(msg_dic).encode("utf-8")) 170 #防止粘包等服务器确认 171 data = self.client.recv(1024) 172 server_response = json.loads(data.decode()) 173 174 if server_response["filename"] is not None: 175 file_name = server_response["filename"] 176 if old_size>=server_response["size"]: 177 print("文件已下载完毕!") #文件已下载完毕 178 received_size = old_size 179 else: 180 f = open(filename, 'ab') 181 received_size = old_size 182 self.client.send(b"ok") 183 while received_size < server_response["size"]: 184 if server_response["size"] - received_size >= 1024: #此时要收不只一次 185 size = 1024 186 else: #最后一次收的数据大小,防止多收,避免粘包 187 size = server_response["size"] - received_size 188 data = self.client.recv(1024) 189 received_size += len(data) #每次收到的大小 190 f.write(data) 191 cur_percent = received_size / server_response["size"] * 100 192 self.show_progress(server_response["size"],received_size,cur_percent) #显示进度条 193 print("下载成功!") 194 else: 195 print("文件不存在!") 196 197 def show_progress(self,total,finished,percent): 198 progress_mark = "=" * int(percent/2) 199 sys.stdout.write("[%s/%s]%s>%s\r" %(total,finished,progress_mark,percent)) 200 sys.stdout.flush() 201 if percent == 100: 202 print ('\n') 203 204 def help(self): 205 msg = ''' 206 ---命令菜单--- 207 dir 208 mkdir filename 209 rmdir filename 210 cd filename 211 get filename 212 put filename 213 ''' 214 print(msg) 215 216 ftp = FtpClient("localhost",9999)
1 import socketserver,json,os 2 import user_data 3 from os.path import join, getsize 4 5 class MyTCPHandler(socketserver.BaseRequestHandler): 6 def ftp_auth(self,*args): 7 print ('----auth----') 8 auth_res = False 9 cmd_dic = args[0] 10 if len(cmd_dic) == 3: 11 msg_type = cmd_dic["action"] 12 username = cmd_dic["username"] 13 passwd = cmd_dic["passwd"] 14 if username in user_data.accounts: 15 if user_data.accounts[username]['passwd'] == passwd: 16 auth_res = True 17 self.login_user = username 18 self.cur_path = '%s/%s' %(os.path.dirname(__file__),user_data.accounts[username]['home']) 19 self.home_path = '%s/%s' %(os.path.dirname(__file__),user_data.accounts[username]['home']) 20 self.quotation = user_data.accounts[username]['quotation'] 21 else: 22 print ('---wrong passwd---') 23 auth_res = False 24 else: 25 auth_res = False 26 else: 27 auth_res = False 28 if auth_res: 29 msg = "success" 30 print ('\033[32;1muser:%s has passed authentication!\033[0m' %username) 31 else: 32 msg = "fail" 33 self.request.send(msg.encode()) 34 35 def disk_size(self,dir): 36 home_size = 0 37 for root, dirs, files in os.walk(dir): 38 home_size += sum([getsize(join(root, name)) for name in files]) 39 size = self.quotation - home_size 40 return size 41 42 def put(self,*args): 43 '''接受客户端文件''' 44 cmd_dic = args[0] 45 filename = cmd_dic["filename"] 46 filesize = cmd_dic["size"] 47 size = self.disk_size(self.home_path) 48 print(size,filesize) 49 if size > int(filesize): 50 if os.path.isfile(filename): 51 print("文件已存在") 52 old_size = os.stat(filename).st_size 53 print(old_size,filesize) 54 if old_size>=filesize: 55 self.request.send(b"ok|no") 56 f = open(filename + "_new","wb") #文件已上传完毕,上传新的文件 57 received_size = 0 58 else: 59 b = "ok|%s"%old_size 60 self.request.send(b.encode()) 61 f = open(filename, 'ab') 62 received_size = old_size 63 else: 64 self.request.send(b"ok|no") 65 f = open(filename,"wb") 66 received_size = 0 67 68 while received_size < filesize: 69 data = self.request.recv(1024) 70 f.write(data) 71 received_size += len(data) 72 else: 73 print("file [%s] has uploaded.."%filename) 74 75 76 77 78 else: 79 result = "磁盘空间不足!" 80 print(result) 81 self.request.send(result.encode()) 82 83 def get(self,*args): 84 '''客户端下载文件''' 85 cmd_dic = args[0] 86 filename = cmd_dic["filename"] 87 get_size = cmd_dic["size"] 88 89 if os.path.isfile(filename): #判断是否已经存在 90 filesize = os.stat(filename).st_size 91 cmd_dic["size"] = filesize 92 self.request.send(json.dumps(cmd_dic).encode("utf-8")) 93 self.request.recv(1024) 94 f = open(filename,"rb") 95 f.seek(get_size)#seek到断点位置 96 for line in f: 97 self.request.send(line) 98 f.close() 99 100 else: 101 cmd_dic['filename'] = None 102 self.request.send(json.dumps(cmd_dic).encode("utf-8")) 103 104 def dir(self,*args): 105 cmd_dic = args[0] 106 action = cmd_dic["action"] 107 filename = cmd_dic["filename"] 108 home_prefix = user_data.accounts[self.login_user]['home'] 109 dir_path = '%s/%s' %(self.cur_path,filename) 110 os.chdir(dir_path) 111 cmd_res = os.popen("dir").read() 112 print(cmd_res) 113 print(len(cmd_res.encode())) 114 # if len(cmd_res.encode()) == 0 : 115 # cmd_res = "没有输出。。" 116 self.request.send(str(len(cmd_res.encode())).encode()) 117 self.request.recv(1024) #等客户端确认 118 self.request.send(cmd_res.encode()) 119 120 def cd(self,*args): 121 cmd_dic = args[0] 122 action = cmd_dic["action"] 123 filename = cmd_dic["filename"] 124 if len(filename) == 0:# means no dir follows cd cmd, go back to home directory 125 self.cur_path = self.home_path 126 relative_path = self.cur_path.split(self.home_path)[-1] 127 switch_res = "switch_dir::ok::%s" % relative_path 128 else: 129 if os.path.isdir("%s/%s" %(self.cur_path,filename)): 130 abs_path = "%s/%s" %(self.cur_path,filename) 131 if abs_path.startswith(self.home_path): 132 self.cur_path = abs_path 133 print("success") 134 relative_path = self.cur_path.split(self.home_path)[-1] 135 switch_res = "switch_dir::ok::%s" % relative_path 136 else: 137 switch_res = "switch_dir::error::target dir doesn't exist" 138 else: 139 switch_res = "switch_dir::error::target dir doesn't exist" 140 self.request.send(switch_res.encode()) 141 142 def rmdir(self,*args): 143 cmd_dic = args[0] 144 action = cmd_dic["action"] 145 filename = cmd_dic["filename"] 146 back = '' 147 if os.path.isdir("%s/%s" %(self.cur_path,filename)): 148 print(self.cur_path) 149 os.rmdir("%s/%s" %(self.cur_path,filename)) 150 back = "目录删除成功!" 151 elif os.path.exists("%s/%s" %(self.cur_path,filename)): 152 os.remove("%s/%s" %(self.cur_path,filename)) 153 back = "文件删除成功!" 154 else: 155 back = "文件或目录不存在!" 156 self.request.send(back.encode()) 157 158 def mkdir(self,*args): 159 cmd_dic = args[0] 160 action = cmd_dic["action"] 161 filename = cmd_dic["filename"] 162 back = '' 163 print(self.cur_path) 164 print(self.home_path) 165 if self.cur_path.startswith(self.home_path): 166 os.mkdir(filename) 167 back = "目录创建成功!" 168 else: 169 back = "没有权限在此目录创建目录!" 170 self.request.send(back.encode()) 171 172 def handle(self): 173 self.request.send(b"Welcome, ftp version 0.0.1") 174 while True: 175 try: 176 self.data = self.request.recv(1024).strip() 177 print("{} wrote:".format(self.client_address[0])) 178 print(self.data) 179 cmd_dic = json.loads(self.data.decode()) 180 action = cmd_dic["action"] 181 if hasattr(self,action): 182 func = getattr(self,action) 183 func(cmd_dic) 184 except ConnectionResetError as e : 185 print("客户端已断开!",e) 186 break 187 188 if __name__ == "__main__": 189 HOST, PORT = "localhost", 9999 190 # server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) #单线程 191 server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler) #多线程并发,没来一个请求服务器就会开启一个独立的线程 192 server.serve_forever()
1 accounts = { 2 'zz': {'passwd': "123", 3 'quotation': 100000000, 4 'home': 'home/zz' 5 }, 6 'jj':{'passwd': "123", 7 'quotation': 1000000, 8 'home': 'home/jj' 9 }, 10 }
四、效果展示
1、用户登陆认证:
2、多用户同时登录
3、不同用户家目录不同
4、查看目录下文件
5、在家目录下切换目录
6、在家目录下任意目录里创建目录
7、删除目录或文件
8、用户磁盘配额
9、上传:支持断点续传
10、下载:支持断点续传和进度条显示