首页 > 代码库 > Day36-37:FTP项目实战
Day36-37:FTP项目实战
FTP上传下载服务器
要求:
1、多用户认证
2、每个用户有自己的家目录
3、ls 查看当前目录
4、get file 下载文件
5、put File 上传文件
6、del file 删除文件或文件夹
7、mkdir dir 创建文件夹
8、cd 切换目录
9、日志记录
目录结构
1 import optparse 2 import socket 3 import json,os 4 import shelve 5 6 class FtpClient(object): 7 """ftp客户端""" 8 MSG_SIZE = 1024 # 消息最长1024 9 10 def __init__(self): 11 self.username = None #用户名 12 self.terminal_display = None 13 self.shelve_obj = shelve.open(".luffy_db") #存放断点续传信息 14 self.current_dir = None #当前所在路径 15 16 parser = optparse.OptionParser() 17 parser.add_option("-s","--server", dest="server", help="ftp server ip_addr") 18 parser.add_option("-P","--port",type="int", dest="port", help="ftp server port") 19 parser.add_option("-u","--username", dest="username", help="username info") 20 parser.add_option("-p","--password", dest="password", help="password info") 21 self.options , self.args = parser.parse_args() #拿到用户输入参数 22 23 # print(self.options,self.args,type(self.options),self.options.server) 24 self.argv_verification() 25 26 self.make_connection() 27 28 29 def argv_verification(self): 30 """检查参数合法性""" 31 if not self.options.server or not self.options.port: #IP和端口不能为空 32 exit("Error: must supply server and port parameters") 33 34 35 def make_connection(self): 36 """建立socket链接""" 37 self.sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建socket对象 38 self.sock.connect((self.options.server,self.options.port)) #连接服务端 39 40 def get_response(self): 41 """获取服务器端返回""" 42 data = http://www.mamicode.com/self.sock.recv(self.MSG_SIZE) #接受信息 43 return json.loads(data.decode()) #返回信息 44 45 46 def auth(self): 47 """用户认证""" 48 count = 0 49 while count < 3: #3次认证 50 username = input("username:").strip() 51 if not username:continue 52 password = input("password:").strip() 53 54 cmd = { 55 ‘action_type‘:‘auth‘, 56 ‘username‘:username, 57 ‘password‘:password, 58 } 59 60 self.sock.send(json.dumps(cmd).encode("utf-8")) #发送服务端进行验证 61 response = self.get_response() #拿到返回结果 62 print("response:",response) 63 if response.get(‘status_code‘) == 200: #pass auth 64 self.username = username #记录当前用户名 65 self.terminal_display = "[%s]>>:" % self.username #命令前显示 66 self.current_dir = "\\" #当前路径为根目录 67 return True 68 else: 69 print(response.get("status_msg")) #打印错误信息 70 count += 1 71 72 def unfinished_file_check(self): 73 """检查shelve db ,把为正常传完的文件列表打印,按用户的指令决定是否重传""" 74 if list(self.shelve_obj.keys()): #判断断点续传信息中是否有信息 75 print("-------Unfinished file list -------------") 76 for index,abs_file in enumerate(self.shelve_obj.keys()): #拿到索引和文件绝对路径 77 # del self.shelve_obj[abs_file] 78 received_file_size = os.path.getsize(self.shelve_obj[abs_file][1]) #拿到在本地的文件名,拿到已下载的大小 79 print("%s. %s %s %s %s" %(index,abs_file, 80 self.shelve_obj[abs_file][0], 81 received_file_size, 82 received_file_size/self.shelve_obj[abs_file][0]*100 83 )) #打印索引、文件路径、文件大小、已经下载大小,百分比 84 85 while True: 86 choice = input("[select file index to re-download]").strip() #等待用输入索引 87 if not choice:continue 88 if choice == ‘back‘:break #跳过续传 89 if choice.isdigit(): 90 choice = int(choice) #将选择的数字字符串转化为整形 91 if choice >= 0 and choice <= index: #判断索引存在 92 selected_file = list(self.shelve_obj.keys())[choice] #拿到选择续传的文件的文件路径 93 already_received_size = os.path.getsize(self.shelve_obj[selected_file][1]) #拿到文件大小 94 95 print("tell server to resend file ", selected_file) 96 #abs_filename + size +received_size 97 self.send_msg(‘re_get‘, file_size=self.shelve_obj[selected_file][0], #发送消息头,包括文件名,大小,已下载大小 98 received_size=already_received_size, 99 abs_filename=selected_file) 100 101 response = self.get_response() #拿到返回消息 102 if response.get(‘status_code‘) == 401: #"File exist ,ready to re-send !", 103 local_filename = self.shelve_obj[selected_file][1] #拿到本地已下载的文件名 104 105 106 107 f = open(local_filename,‘ab‘) #打开文件追加写 108 total_size = self.shelve_obj[selected_file][0] 109 recv_size = already_received_size 110 current_percent = int(recv_size /total_size *100) 111 progress_generator = self.progress_bar(total_size,current_percent,current_percent) 112 progress_generator.__next__() 113 while recv_size < total_size: 114 if total_size - recv_size < 8192: # last recv 115 data = http://www.mamicode.com/self.sock.recv(total_size - recv_size) 116 else: 117 data = http://www.mamicode.com/self.sock.recv(8192) 118 recv_size += len(data) 119 f.write(data) 120 progress_generator.send(recv_size) 121 122 print("file re-get done") 123 else: 124 print(response.get("status_msg")) #打印错误信息 125 126 def interactive(self): 127 """处理与Ftpserver的所有交互""" 128 if self.auth(): #验证成功 129 self.unfinished_file_check() #进入断点续传检查 130 131 while True: 132 user_input = input(self.terminal_display).strip() #输入指令 133 if not user_input:continue 134 135 cmd_list = user_input.split() #字符串指令分割 136 if hasattr(self,"_%s"%cmd_list[0]): #判断类时候有指令方法 137 func = getattr(self,"_%s"%cmd_list[0]) #拿到指令对应的方法 138 func(cmd_list[1:]) #传入指令并执行 139 140 141 def parameter_check(self,args,min_args=None,max_args=None,exact_args=None): 142 """参数个数合法性检查""" 143 if min_args: #最少参数个数 144 if len(args) < min_args: 145 print("must provide at least %s parameters but %s received." %(min_args,len(args))) 146 return False 147 if max_args: #最多参数个数 148 if len(args) > max_args: 149 print("need at most %s parameters but %s received." %(max_args,len(args))) 150 return False 151 152 if exact_args: #特定参数个数 153 if len(args) != exact_args: 154 print("need exactly %s parameters but %s received." % (exact_args, len(args))) 155 return False 156 157 return True 158 159 def send_msg(self,action_type,**kwargs ): 160 """打包消息并发送到远程""" 161 msg_data =http://www.mamicode.com/ { 162 ‘action_type‘: action_type, 163 ‘fill‘:‘‘ 164 } #消息头初始内容 165 msg_data.update(kwargs) #将传入的关键字参数更新到消息头 166 167 bytes_msg = json.dumps(msg_data).encode() #序列化并转成bytes 168 if self.MSG_SIZE > len(bytes_msg): #消息头定长 169 msg_data[‘fill‘] = msg_data[‘fill‘].zfill( self.MSG_SIZE - len(bytes_msg)) 170 bytes_msg = json.dumps(msg_data).encode() 171 172 self.sock.send(bytes_msg) #发送消息头 173 174 def _ls(self,cmd_args): 175 """ 176 display current dir‘s file list 177 :param cmd_args: 178 :return: 179 """ 180 self.send_msg(action_type=‘ls‘) #发送ls的消息头 181 response = self.get_response() 182 print(response) 183 if response.get(‘status_code‘) == 302: #ready to send long msg 184 cmd_result_size = response.get(‘cmd_result_size‘) #信息的大小 185 received_size = 0 186 cmd_result = b‘‘ 187 while received_size < cmd_result_size: #循环接受消息 188 if cmd_result_size - received_size < 8192: #last receive 189 data = http://www.mamicode.com/self.sock.recv( cmd_result_size - received_size) 190 else: 191 data = http://www.mamicode.com/self.sock.recv(8192) 192 cmd_result += data 193 received_size += len(data) 194 else: 195 print(cmd_result.decode("gbk")) #打印结果 196 197 def _cd(self,cmd_args): 198 """change to target dir""" 199 if self.parameter_check(cmd_args, exact_args=1): #只有一个参数 200 target_dir = cmd_args[0] #拿到目标目录 201 self.send_msg(‘cd‘,target_dir=target_dir) #发送消息头,包括目标目录 202 response = self.get_response() 203 print(response) 204 if response.get("status_code") == 350:#dir changed 205 self.terminal_display = "[\%s & %s]" % (response.get(‘current_dir‘),self.username) #显示目录和用户名 206 self.current_dir = response.get(‘current_dir‘) #修改当前目录 207 208 def _get(self,cmd_args): 209 """download file from ftp server 210 1.拿到文件名 211 2.发送到远程 212 3.等待服务器返回消息 213 3.1 如果文件存在, 拿到文件大小 214 3.1.1 循环接收 215 3.2 文件如果不存在 216 print status_msg 217 218 """ 219 if self.parameter_check(cmd_args,min_args=1): 220 filename = cmd_args[0] 221 self.send_msg(action_type=‘get‘,filename=filename) #发送get指令和文件名 222 response = self.get_response() 223 if response.get(‘status_code‘) == 301:# file exist ,ready to receive 224 file_size = response.get(‘file_size‘) #文件大小 225 received_size = 0 #已接收大小 226 227 progress_generator = self.progress_bar(file_size) #初始化进度条生成器 228 progress_generator.__next__() #先next才能send 229 230 #save to shelve db 231 file_abs_path = os.path.join(self.current_dir,filename) #拿到文件在服务器的绝对路径 232 self.shelve_obj[file_abs_path] = [file_size,"%s.download" %filename] #将文件信息存在本地 233 234 f = open("%s.download" %filename,"wb") #打开文件,就收数据 235 while received_size < file_size: 236 if file_size - received_size < 8192:#last recv 237 data = http://www.mamicode.com/self.sock.recv( file_size - received_size ) 238 else: 239 data = http://www.mamicode.com/self.sock.recv(8192) 240 received_size += len(data) 241 f.write(data) #写数据 242 progress_generator.send(received_size) #打印进度条 243 244 else: 245 print(‘\n‘) 246 print("---file [%s] recv done,received size [%s]----"%( filename,file_size)) 247 del self.shelve_obj[file_abs_path] #文件传输完毕,删除本地信息 248 f.close() 249 os.rename("%s.download"%filename,filename) #改文件名 250 251 else: 252 print(response.get(‘status_msg‘)) #打印错误信息 253 254 255 def progress_bar(self,total_size,current_percent=0,last_percent=0): 256 257 while True: 258 received_size = yield current_percent #收到当前接受到的文件大小,循环完返回当前的百分比 259 current_percent = int(received_size / total_size *100) 260 261 if current_percent > last_percent: 262 print("#" * int(current_percent / 2) + "{percent}%".format(percent=current_percent), end=‘\r‘, 263 flush=True) 264 last_percent = current_percent # 把本次循环的percent赋值给last 265 266 def _put(self,cmd_args): 267 """上传本地文件到服务器 268 1. 确保本地文件存在 269 2. 拿到文件名+大小,放到消息头里发给远程 270 3. 打开文件,发送内容 271 """ 272 273 274 if self.parameter_check(cmd_args, exact_args=1): #检查参数个数 275 local_file = cmd_args[0] #拿到要上传的文件名 276 if os.path.isfile(local_file): #判断本地文件是否存在 277 total_size = os.path.getsize(local_file) #拿到文件大小 278 self.send_msg(‘put‘,file_size=total_size,filename=local_file) #发送put指令,包括文件名、大小 279 f = open(local_file,‘rb‘) 280 uploaded_size = 0 281 #last_percent = 0 282 283 progress_generator = self.progress_bar(total_size) #进度条 284 progress_generator.__next__() 285 for line in f: #发送数据 286 self.sock.send(line) 287 uploaded_size += len(line) 288 progress_generator.send(uploaded_size) #打印进度条 289 290 else: 291 print(‘\n‘) 292 print(‘file upload done‘.center(50,‘-‘)) 293 f.close() 294 295 def _mkdir(self,cmd_args): 296 """创建文件夹""" 297 if self.parameter_check(cmd_args, exact_args=1): #检查参数个数 298 dir_name = cmd_args[0] #拿到要创建的目录 299 self.send_msg(‘mkdir‘, dir_name=dir_name) # 发送mkdir指令,包括目录 300 response = self.get_response() 301 print(response.get(‘status_msg‘)) 302 303 def _del(self,cmd_args): 304 """创建文件夹""" 305 if self.parameter_check(cmd_args, exact_args=1): #检查参数个数 306 target = cmd_args[0] #拿到要删除的目录或文件 307 self.send_msg(‘del‘, target=target) # 发送del指令,包括目标 308 response = self.get_response() 309 print(response.get(‘status_msg‘)) 310 311 if __name__ == "__main__": 312 client = FtpClient() 313 client.interactive() #交互
1 import os,sys 2 3 #拿到server的绝对路径,添加到搜索模块的路径集 4 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 sys.path.append(BASE_DIR) 6 7 if __name__ == "__main__": 8 from core import management 9 10 argv_parser = management.ManagementTool(sys.argv) #将用户输入指令传入管理工具,检验合法性 11 argv_parser.execute() #指令合法后解析执行
[alex] name = alex Li password = e99a18c428cb38d5f260853678922e03 expire = 2017-09-20 [egon] name = egg lin password = e99a18c428cb38d5f260853678922e03 expire = 2018-01-01
1 import os 2 3 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 4 5 HOST = "0.0.0.0" 6 PORT = 9999 7 8 USER_HOME_DIR = os.path.join(BASE_DIR,‘home‘) 9 10 LOG_DIR=os.path.join(BASE_DIR,‘log‘) 11 12 ACCOUNT_FILE = "%s/conf/accounts.ini" % BASE_DIR 13 14 MAX_SOCKET_LISTEN = 5
1 import os 2 import logging.config 3 import time 4 from conf.settings import LOG_DIR 5 6 # 定义三种日志输出格式 开始 7 8 standard_format = ‘[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]‘ 9 ‘[%(levelname)s][%(message)s]‘ 10 11 simple_format = ‘[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s‘ 12 13 # id_simple_format = ‘[%(levelname)s][%(asctime)s] %(message)s‘ 14 id_simple_format = ‘%(message)s‘ 15 16 # 定义日志输出格式 结束 17 18 # logfile_dir = os.path.dirname(os.path.abspath(__file__)) # log文件的目录 19 20 logfile_name = ‘%s.log‘%time.strftime(‘%Y-%m-%d‘,time.localtime()) # log文件名 21 22 # 如果不存在定义的日志目录就创建一个 23 if not os.path.isdir(LOG_DIR): 24 os.mkdir(LOG_DIR) 25 26 27 # log文件的全路径 28 # logfile_path = os.path.join(logfile_dir, logfile_name) 29 logfile_path = "%s%s%s" % (LOG_DIR, os.path.sep, logfile_name) 30 31 32 33 34 # log配置字典 35 LOGGING_DIC = { 36 ‘version‘: 1, 37 ‘disable_existing_loggers‘: False, 38 ‘formatters‘: { 39 ‘standard‘: { 40 ‘format‘: standard_format 41 }, 42 ‘simple‘: { 43 ‘format‘: id_simple_format 44 }, 45 }, 46 ‘filters‘: {}, 47 ‘handlers‘: { 48 ‘console‘: { 49 ‘level‘: ‘DEBUG‘, 50 ‘class‘: ‘logging.StreamHandler‘, # 打印到屏幕 51 ‘formatter‘: ‘simple‘ 52 }, 53 ‘default‘: { 54 ‘level‘: ‘DEBUG‘, 55 ‘class‘: ‘logging.handlers.RotatingFileHandler‘, # 保存到文件 56 ‘filename‘: logfile_path, # 日志文件 57 ‘maxBytes‘: 1024*1024*5, # 日志大小 5M 58 ‘backupCount‘: 5, 59 ‘formatter‘: ‘standard‘, 60 ‘encoding‘: ‘utf-8‘, # 日志文件的编码,再也不用担心中文log乱码了 61 }, 62 }, 63 ‘loggers‘: { 64 ‘‘: { 65 ‘handlers‘: [‘default‘, ‘console‘], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 66 ‘level‘: ‘DEBUG‘, 67 ‘propagate‘: True, # 向上(更高level的logger)传递 68 }, 69 }, 70 } 71 72 73 def load_logging_cfg(): 74 logging.config.dictConfig(LOGGING_DIC) # 导入上面定义的logging配置 75 logger = logging.getLogger(__name__) # 生成一个log实例 76 # logger.info(‘log 配置文件没有问题!‘) # 记录该文件的运行状态 77 return logger 78 79 if __name__ == ‘__main__‘: 80 load_my_logging_cfg()
1 import socket 2 from conf import settings 3 import json,hashlib,os,time 4 import configparser 5 import subprocess 6 from core import log_conf 7 8 class FTPServer(object): 9 """处理与客户端所有的交互的socket server""" 10 11 STATUS_CODE ={ 12 200 : "Passed authentication!", 13 201 : "Wrong username or password!", 14 300 : "File does not exist !", 15 301 : "File exist , and this msg include the file size- !", 16 302 : "This msg include the msg size!", 17 350 : "Dir changed !", 18 351 : "Dir doesn‘t exist !", 19 401 : "File exist ,ready to re-send !", 20 402 : "File exist ,but file size doesn‘t match!", 21 501 : "directory create success!", 22 502 : "directory exist!", 23 503 : "File delete successful!", 24 504 : "The folder is not empty and cannot be deleted!", 25 } #状态码集合 26 27 MSG_SIZE = 1024 #消息最长1024 28 29 def __init__(self,management_instance): 30 self.management_instance = management_instance #将ManagementTool对象本身传入 31 self.sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #建立socket对象 32 self.sock.bind((settings.HOST,settings.PORT)) #绑定IP和端口 33 self.sock.listen(settings.MAX_SOCKET_LISTEN) #开始监听 34 self.accounts = self.load_accounts() #加载账户信息 35 self.user_obj = None #保存当前用户 36 self.user_current_dir = None #当前用户目录 37 self.log=log_conf.load_logging_cfg() 38 39 40 def run_forever(self): 41 """启动socket server""" 42 print(‘starting LuffyFtp server on %s:%s‘.center(50,‘-‘) %(settings.HOST,settings.PORT)) 43 44 while True: #循环接受连接 45 self.request,self.addr = self.sock.accept() #拿到链接和地址 46 # print("got a new connection from %s....." %(self.addr,)) 47 self.log.info("got a new connection from %s....." %(self.addr,)) 48 try: 49 self.handle() #进入用户交互 50 except Exception as e: 51 # print("Error happend with client,close connection.",e) 52 self.log.info("Error happend with client,close connection.",e) 53 self.request.close() #打印错误信息,关闭连接 54 55 56 def handle(self): 57 """处理与用户的所有指令交互""" 58 while True: 59 60 raw_data = http://www.mamicode.com/self.request.recv(self.MSG_SIZE) #接受客户端发送的消息 61 print(‘------->‘,raw_data) 62 if not raw_data: #用户断了连接,打印消息并删除链接和地址 63 # print("connection %s is lost ...." % (self.addr,)) 64 self.log.info("connection %s is lost ...." % (self.addr,)) 65 del self.request,self.addr 66 break 67 68 data = http://www.mamicode.com/json.loads(raw_data.decode("utf-8")) #先转化为utf—8再反序列化 69 action_type = data.get(‘action_type‘) #拿到指令 70 if action_type: 71 if hasattr(self,"_%s" % action_type): #查找类是否有对应方法 72 func = getattr(self,"_%s" % action_type) #拿到对应方法并执行 73 func(data) 74 75 else: 76 print("invalid command,") 77 78 79 def load_accounts(self): 80 """加载所有账号信息""" 81 config_obj = configparser.ConfigParser() #拿到信息对象 82 config_obj.read(settings.ACCOUNT_FILE) #读取本地信息 83 84 print(config_obj.sections()) #打印信息标头 85 return config_obj 86 87 88 def authenticate(self,username,password): 89 """用户认证方法""" 90 if username in self.accounts: #判断用户是否存在 91 _password = self.accounts[username][‘password‘] #拿到用户密码 92 md5_obj = hashlib.md5() 93 md5_obj.update(password.encode()) 94 md5_password = md5_obj.hexdigest() #拿到密码的MD5值 95 # print("passwd:",_password,md5_password) 96 if md5_password == _password: #判断密码是否相等 97 98 self.user_obj = self.accounts[username] #保存用户信息 99 self.user_obj[‘home‘]= os.path.join(settings.USER_HOME_DIR,username) #服务端用户家目录的的绝对路径 100 #set user home directory 101 self.user_current_dir = self.user_obj[‘home‘] #保存当前路径 102 return True #认证成功返回TRUE 103 else: 104 return False #密码错误返回FALSE 105 else: 106 return False #用户名错误返回FALSE 107 108 def send_response(self,status_code,*args,**kwargs): 109 """ 110 打包发送消息给客户端 111 :param status_code: 112 :param args: 113 :param kwargs: {filename:ddd,filesize:222} 114 :return: 115 """ 116 data =http://www.mamicode.com/ kwargs 117 data[‘status_code‘] = status_code #返回状态码 118 data[‘status_msg‘] = self.STATUS_CODE[status_code] #返回信息 119 data[‘fill‘] = ‘‘ 120 121 bytes_data = http://www.mamicode.com/json.dumps(data).encode() #序列化并转bytes 122 123 if len(bytes_data) < self.MSG_SIZE: #定长 124 data[‘fill‘] = data[‘fill‘].zfill( self.MSG_SIZE - len(bytes_data)) 125 bytes_data =http://www.mamicode.com/ json.dumps(data).encode() 126 127 self.request.send(bytes_data) #发送到客户端 128 129 def _auth(self,data): 130 """处理用户认证请求""" 131 # print("auth ",data ) 132 if self.authenticate(data.get(‘username‘),data.get(‘password‘)): #进行认证 133 print(‘pass auth....‘) 134 135 #1. 消息内容,状态码 136 #2. json.dumps 137 #3 . encode 138 self.send_response(status_code=200) #认证成功返回200 139 140 else: 141 self.send_response(status_code=201) #认证失败返回201 142 143 144 def _get(self,data): 145 """client downloads file through this method 146 1. 拿到文件名 147 2. 判断文件是否存在 148 2.1 如果存在, 返回状态码+文件大小 149 2.1.1打开文件,发送文件内容 150 2.2 如果不存在, 返回状态码 151 3. 152 """ 153 filename = data.get(‘filename‘) #拿到文件名 154 #full_path = os.path.join(self.user_obj[‘home‘],filename) 155 full_path = os.path.join(self.user_current_dir,filename) #拿到文件路径 156 if os.path.isfile(full_path): #判断文件是否存在 157 filesize = os.stat(full_path).st_size #拿到文件大小 158 self.send_response(301,file_size=filesize) #返回状态码和文件大小 159 print("ready to send file ") 160 f = open(full_path,‘rb‘) #打开文件,发送数据 161 for line in f: 162 self.request.send(line) 163 else: 164 # print(‘file send done..‘,full_path) 165 self.log.info(‘file send done..‘,full_path) 166 167 f.close() 168 169 170 else: 171 self.send_response(300) #文件不存在,返回状态码 172 173 def _re_get(self,data): 174 """re-send file to client 175 1. 拼接文件路径 176 2. 判断文件是否存在 177 2.1 如果存在,判断文件大小是否与客户端发过来的一致 178 2.1.1 如果不一致,返回错误消息 179 2.1.2 如果一致,告诉客户端,准备续传吧 180 2.1.3 打开文件,Seek到指定位置,循环发送 181 2.2 文件不存在,返回错误 182 183 184 """ 185 print("_re_get",data) 186 abs_filename = data.get(‘abs_filename‘) #拿到文件路径 187 full_path = os.path.join(self.user_obj[‘home‘],abs_filename.strip("\\")) #拼接家目录和文件名 188 print("reget fullpath", full_path) 189 # print("user home",self.user_obj[‘home‘]) 190 if os.path.isfile(full_path): #判断文件是否存在 191 if os.path.getsize(full_path) == data.get(‘file_size‘): #判断文件大是否一致 192 self.send_response(401) #返回状态码,让客户端准备接收数据 193 f = open(full_path,‘rb‘) #打开文件 194 f.seek(data.get("received_size")) #光标移动到断点处 195 for line in f: 196 self.request.send(line) 197 else: 198 # print("-----file re-send done------") 199 self.log.info(‘file re-send done..‘, full_path) 200 201 f.close() 202 else:#2.1.1 203 self.send_response(402,file_size_on_server=os.path.getsize(full_path)) #文件大小不一致,返回错误信息 204 else: 205 self.send_response(300) #文件不存在,返回错误信息 206 207 def _put(self,data): 208 """client uploads file to server 209 1. 拿到local文件名+大小 210 2. 检查本地是否已经有相应的文件。self.user_cuurent_dir/local_file 211 2.1 if file exist , create a new file with file.timestamp suffix. 212 2.2 if not , create a new file named local_file name 213 3. start to receive data 214 """ 215 local_file = data.get("filename") #拿到文件名 216 full_path = os.path.join(self.user_current_dir,local_file) #文件路径 217 if os.path.isfile(full_path): #代表文件已存在,不能覆盖 218 filename = "%s.%s" %(full_path,time.time()) #保存新的文件名 219 else: 220 filename = full_path 221 222 f = open(filename,"wb") #打开文件,准备写数据 223 total_size = data.get(‘file_size‘) 224 received_size = 0 225 226 while received_size < total_size: 227 if total_size - received_size < 8192: # last recv 228 data = http://www.mamicode.com/self.request.recv(total_size - received_size) 229 else: 230 data = http://www.mamicode.com/self.request.recv(8192) 231 received_size += len(data) 232 f.write(data) 233 print(received_size, total_size) 234 else: 235 # print(‘file %s recv done‘% local_file) 236 self.log.info(‘file %s recv done‘% local_file) 237 238 f.close() 239 240 241 def _ls(self,data): 242 """run dir command and send result to client""" 243 cmd_obj = subprocess.Popen(‘dir %s‘ %self.user_current_dir,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) #执行Windows上的dir命令 244 stdout = cmd_obj.stdout.read() #拿到执行结果 245 stderr = cmd_obj.stderr.read() #拿到错误信息 246 247 cmd_result = stdout + stderr #拼接结果信息 248 249 if not cmd_result: #没有结果返回结果,定义返回结果 250 cmd_result = b‘current dir has no file at all.‘ 251 252 self.send_response(302,cmd_result_size=len(cmd_result)) #发送状态码和信息长度 253 self.request.sendall(cmd_result) #发送结果 254 255 def _cd(self,data): 256 """根据用户的target_dir改变self.user_current_dir 的值 257 1. 把target_dir 跟user_current_dir 拼接 258 2. 检测 要切换的目录是否存在 259 2.1 如果存在 , 改变self.user_current_dir的值到新路径 260 2.2 如果不存在,返回错误消息 261 262 """ 263 #/home/alex/FuckHomework/ cfd 264 target_dir = data.get(‘target_dir‘) #拿到客户端发送来的目标路径 265 full_path = os.path.abspath(os.path.join(self.user_current_dir,target_dir) ) #拼接当前路径和目标路径,再拿到服务端的绝对路径 266 print("full path:",full_path) 267 if os.path.isdir(full_path): #判断路径是否存在 268 269 if full_path.startswith(self.user_obj[‘home‘]): #判断路径是否是用户家目录下的路径 270 self.user_current_dir = full_path #是的话,将目标路径赋值给当前路径 271 relative_current_dir = self.user_current_dir.replace(self.user_obj[‘home‘], ‘‘) #用户拿到相对路径 272 self.send_response(350, current_dir=relative_current_dir) #发送给用户状态码和相对路径 273 274 else: 275 self.send_response(351) #目标路径不是用户允许访问的路径,返回状态码 276 277 else: 278 self.send_response(351) #目标路径不存在,返回状态码 279 280 def _mkdir(self,data): 281 """ 282 创建文件夹目录 283 1、将用户发送的dir_name和self.user_current_dir进行拼接 284 2、判断文件夹是否存在 285 2.1不存在就创建 并返回信息 286 2.2存在就返回错误信息 287 :param data: 288 :return: 289 """ 290 dir_name = data.get(‘dir_name‘) #拿到目录名字 291 full_path = os.path.abspath(os.path.join(self.user_current_dir,dir_name)) #拼接路径 292 if not os.path.isdir(full_path): #判断目录是否存在 293 os.makedirs(full_path) #不存在则创建 294 self.log.info(‘%s directory create success!‘ % full_path) #日志记录并打印 295 self.send_response(501) #返回状态码 296 else: 297 self.send_response(502) 298 def _del(self,data): 299 """ 300 创建文件夹目录 301 1、将用户发送的target和self.user_current_dir进行拼接 302 2、判断文件或者文件夹是否存在 303 2.1文件存在,删除文件 304 2.2文件夹存在,判断是否为空 305 2.2.1空文件夹可删除 306 2.2.2文件夹不为空,不可删除 307 2.3路径不存在,返回错误信息 308 :param data: 309 :return: 310 """ 311 target = data.get(‘target‘) 312 full_path = os.path.abspath(os.path.join(self.user_current_dir,target)) #拼接路径 313 if os.path.isfile(full_path): #判断是否为文件 314 os.remove(full_path) #删除文件 315 self.log.info(‘%s file delete success!‘ % full_path) 316 self.send_response(503) 317 elif os.path.isdir(full_path): #判断路径为文件夹 318 if not os.listdir(full_path): #文件夹为空,可以删除 319 os.rmdir(full_path) 320 self.log.info(‘%s directory delete success!‘ % full_path) 321 self.send_response(503) 322 323 else: 324 self.send_response(504) #不为空,不能删除,并发送结果 325 else: 326 self.send_response(300) #文件不存在
from core import main class ManagementTool(object): """负责对用户输入的指令进行解析并调用相应模块处理""" def __init__(self,sys_argv): self.sys_argv = sys_argv #接受用户输入指令 print(self.sys_argv) self.verify_argv() #检验指令合法性 def verify_argv(self): """验证指令合法性""" if len(self.sys_argv) < 2: #至少输入一个参数 self.help_msg() #输出帮助信息 cmd = self.sys_argv[1] #拿到指令 if not hasattr(self,cmd): #判断类是否存在对应方法 print("invalid argument!") self.help_msg() def help_msg(self): """帮助信息""" msg = ‘‘‘ start start FTP server stop stop FTP server restart restart FTP server createuser username create a ftp user ‘‘‘ exit(msg) def execute(self): """解析并执行指令""" cmd = self.sys_argv[1] func = getattr(self,cmd ) func() def start(self): """start ftp server""" server = main.FTPServer(self) #建立socket对象 server.run_forever() #启动 def creteuser(self): """ 创建用户 :return: """ print(self.sys_argv)
1 [2017-07-17 16:48:35,798][MainThread:1952][task_id:core.log_conf][main.py:47][INFO][got a new connection from (‘127.0.0.1‘, 49629).....] 2 [2017-07-17 16:49:14,426][MainThread:1952][task_id:core.log_conf][main.py:294][INFO][E:\PycharmProjects\qz5\Day28\LuffyFTP\server\home\alex\abc\ab\cd directory create success!] 3 [2017-07-17 16:49:42,910][MainThread:1952][task_id:core.log_conf][main.py:320][INFO][E:\PycharmProjects\qz5\Day28\LuffyFTP\server\home\alex\abc\ab\cd directory delete success!] 4 [2017-07-17 16:49:56,433][MainThread:1952][task_id:core.log_conf][main.py:320][INFO][E:\PycharmProjects\qz5\Day28\LuffyFTP\server\home\alex\abc\ab directory delete success!] 5 [2017-07-17 16:50:00,880][MainThread:1952][task_id:core.log_conf][main.py:64][INFO][connection (‘127.0.0.1‘, 49629) is lost ....]
Day36-37:FTP项目实战
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。