首页 > 代码库 > 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() #交互
luffy_client.py
技术分享
 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()   #指令合法后解析执行
luffy_server.py
技术分享
[alex]
name = alex Li
password = e99a18c428cb38d5f260853678922e03
expire = 2017-09-20


[egon]
name = egg lin
password = e99a18c428cb38d5f260853678922e03
expire = 2018-01-01
accounts.ini
技术分享
 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
settings.py
技术分享
 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()
log_conf.py
技术分享
  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 = bcurrent 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)         #文件不存在
main.py
技术分享
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)
management.py
技术分享
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 ....]
2017-07-17.log

 

Day36-37:FTP项目实战