开发一个支持多用户在线的FTP程序

首先对于这样的任务要建立文件目录,不能将所有的代码放在一个文件夹下。文件目录,先两个大目录,客户端FTP_client,服务端FTP_server。

服务端FTP_server下面肯定还要文件夹bin(放启动文件),conf(放配置文件,方便后续修改),core(放主逻辑,主程序),home,logger(放日志,这个作业用不到)

下面按顺序介绍,先是bin文件下,有一个ftp_server.py,代码如下:

1 import os,sys
2 
3 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 将当前搜索路径返回FTP_server这级
4 sys.path.append(BASE_DIR)
5 from core import main
6 
7 if __name__ == '__main__':
8 
9     main.ArgvHandler()
ftp_server.py

启动文件,顾名思义,相当于其他文件夹下的其他文件都是复制到这个目录下执行的,所以有关涉及路径操作时注意。

下面是core文件下的两个.py文件:

 1 import optparse
 2 import socketserver
 3 from conf import settings
 4 from core import server
 5 '''
 6 core文件下面放的是主逻辑
 7 '''
 8 
 9 
10 class ArgvHandler(object):
11 
12     def __init__(self):                                            # 接收参数,解析命令
13         self.op=optparse.OptionParser()
14 
15         # op.add_option("-s","--host",dest="host",help="server IP address")
16         # op.add_option("-P","--port",dest="port",help="server port")
17 
18         options,args=self.op.parse_args()                          # options返回的类,里面有你add_option加入的属性,加入的其他放在args中
19         # print(options,args)
20         # print(options.host,options.port)
21 
22         self.verify_argv(options,args)                             # 这个函数相当于是一个命令的分发,比如在命令行中输入Python ftp_server.py start
23 
24 
25     def verify_argv(self,options,args):                            # 命令行的分发,这里args[0],就是start,所以取得函数名后,对应执行
26 
27         if hasattr(self,args[0]):
28             func=getattr(self,args[0])
29             func()
30 
31         else:
32             self.op.print_help()
33 
34 
35     def start(self):
36         print('server is working ....')
37         ser=socketserver.ThreadingTCPServer((settings.IP,settings.PORT),server.ServerHandler)                # 建立TCP链接循环,而且是可并发的
38         ser.serve_forever()
main.py
  1 import socketserver
  2 import json
  3 import configparser
  4 import os
  5 from conf import settings
  6 import hashlib
  7 import shutil
  8 
  9 
 10 STATUS_CODE  = {
 11     250 : "Invalid cmd format, e.g: {'action':'get','filename':'test.py','size':344}",
 12     251 : "Invalid cmd ",
 13     252 : "Invalid auth data",
 14     253 : "Wrong username or password",
 15     254 : "Passed authentication",
 16     255 : "Filename doesn't provided",
 17     256 : "File doesn't exist on server",
 18     257 : "ready to send file",
 19     258 : "md5 verification",
 20 
 21     800 : "the file exist,but not enough ,is continue? ",
 22     801 : "the file exist !",
 23     802 : " ready to receive datas",
 24 
 25     900 : "md5 valdate success"
 26 
 27 }
 28 '''
 29 写socketserver首先得写一个自己的类,里面要重写handle函数,里面在写通讯循环;然后外面写链接循环(main里面已经写了)
 30 '''
 31 
 32 class ServerHandler(socketserver.BaseRequestHandler):
 33 
 34     def handle(self):
 35 
 36         while 1:                         # 这里不断循环,其实就是与一个客户端不断的通讯(交互)
 37 
 38             data=self.request.recv(1024).strip()
 39             print("data-----",data)
 40             if len(data)==0:break
 41 
 42             data=json.loads(data.decode("utf8"))
 43             print("data:",data)
 44 
 45             '''
 46             data = {'action':'auth',
 47                     'username':user,
 48                     'password':password}
 49             '''
 50 
 51             if data.get("action"):                                             # 这里又是一个命令行的分发,其实啰嗦的写可以用多个if,稍微好一点是用字典,这样写最好
 52                 if hasattr(self,'_%s'%data.get("action")):
 53                     func=getattr(self,'_%s'%data.get("action"))
 54                     func(**data)
 55 
 56                 else:
 57                     print("Invalid cmd!")
 58                     self.send_response(251)
 59 
 60             else:
 61                 print("invalid cmd format")
 62                 self.send_response(250)
 63 
 64 
 65     def send_response(self,status_code,data=None):
 66         '''向客户端返回数据'''
 67         response = {'status_code':status_code,'status_msg':STATUS_CODE[status_code]}
 68         if data:
 69             response.update(data)
 70         self.request.send(json.dumps(response).encode())
 71 
 72 
 73     def _auth(self,**data):
 74 
 75         if data.get("username") is None or data.get("password") is None:
 76             self.send_response(252)
 77 
 78         user=self.authenticate(data.get("username"),data.get("password"))
 79 
 80         if user is None:
 81             print("")
 82             self.send_response(253)
 83 
 84         else:
 85             self.user=user
 86             print("passed authentication",user)
 87             self.send_response(254)
 88             self.mainPath=os.path.join(settings.BASE_DIR,"home",self.user)
 89 
 90 
 91     def authenticate(self,username,password):           # 用户名与密码验证,相当于是跟数据库中的信息匹配,看有没有这个用户,看密码对不对
 92 
 93         cfp=configparser.ConfigParser()
 94         cfp.read(settings.ACCOUNT_PATH)
 95 
 96         if username in cfp.sections():
 97             print(".....",cfp[username]["Password"])
 98 
 99             Password=cfp[username]["Password"]
100             if Password==password:
101                 print("auth pass!")
102                 return username
103 
104 
105     def _post(self,**data): # 文件上传功能实现
106         # 上传文件有几种可能:1,文件已经存在(就算存在,也分两种情况,1)存在的是完整的;2)存在的不完整(又分两种情况,是否需要续传));
107         # 2,原来不存在
108 
109         file_name=data.get("file_name")
110         file_size=data.get("file_size")
111         target_path=data.get("target_path")
112         print(file_name,file_size,target_path)
113         abs_path=os.path.join(self.mainPath,target_path,file_name)
114         print("abs_path",abs_path)
115 
116         has_received=0
117 
118 
119         if os.path.exists(abs_path):
120             has_file_size=os.stat(abs_path).st_size
121             if has_file_size <file_size:
122                self.request.sendall(b"800")
123 
124                is_continue=str(self.request.recv(1024),"utf8")
125                if is_continue=="Y":
126                     self.request.sendall(bytes(str(has_file_size),"utf8"))
127                     has_received+=has_file_size
128                     f=open(abs_path,"ab")
129 
130                else:
131                     f=open(abs_path,"wb")
132             else:
133                 self.request.sendall(b"801")
134                 return #注意这个return必须有
135 
136         else:
137             self.request.sendall(b"802")
138             f=open(abs_path,"wb")
139 
140 
141         while has_received<file_size:
142             try:
143                 data=self.request.recv(1024)
144                 if not data:
145                     raise Exception
146             except Exception:
147 
148                 break
149 
150             f.write(data)
151             has_received+=len(data)
152 
153         f.close()
154 
155 
156     def _post_md5(self,**data):
157 
158         file_name=data.get("file_name")
159         file_size=data.get("file_size")
160         target_path=data.get("target_path")
161         print(file_name,file_size,target_path)
162         abs_path=os.path.join(self.mainPath,target_path,file_name)
163         print("abs_path",abs_path)
164 
165         has_received=0
166 
167 
168         if os.path.exists(abs_path):
169             has_file_size=os.stat(abs_path).st_size
170             if has_file_size <file_size:
171                self.request.sendall(b"800")
172 
173                is_continue=str(self.request.recv(1024),"utf8")
174                if is_continue=="Y":
175                     self.request.sendall(bytes(str(has_file_size),"utf8"))
176                     has_received+=has_file_size
177                     f=open(abs_path,"ab")
178 
179                else:
180                     f=open(abs_path,"wb")
181             else:
182                 self.request.sendall(b"801")
183                 return #注意这个return必须有
184 
185         else:
186             self.request.sendall(b"802")
187             f=open(abs_path,"wb")
188 
189 
190         if data.get('md5'):
191                 print("hhhhhhhhhhh")
192                 md5_obj = hashlib.md5()
193 
194                 while has_received<file_size:
195 
196                     try:
197                         data=self.request.recv(1024)
198                         if not data:
199                             raise Exception
200                     except Exception:
201 
202                         break
203 
204                     f.write(data)
205                     has_received+=len(data)
206                     recv_file_md5=md5_obj.update(data)
207                     print("mmmmmm")
208 
209                 else:
210                     self.request.sendall(b"ok")#解决粘包
211                     send_file_md5=self.request.recv(1024).decode("utf8")
212                     print("send_file_md5",send_file_md5)
213                     self.request.sendall("900".encode("utf8"))
214 
215         else:
216 
217             while has_received<file_size:
218 
219                 try:
220                     data=self.request.recv(1024)
221                     if not data:
222                         raise Exception
223                 except Exception:
224 
225                     break
226             f.write(data)
227             has_received+=len(data)
228 
229         f.close()
230 
231 
232 
233     def _ls(self,**data):
234 
235         file_list=os.listdir(self.mainPath)
236 
237         file_str='\n'.join(file_list)
238         if not file_list:
239             file_str="<empty directory>"
240         self.request.send(file_str.encode("utf8"))
241 
242 
243     def _cd(self,**data):
244 
245         path=data.get("path")
246 
247         if path=="..":
248             self.mainPath=os.path.dirname(self.mainPath)
249         else:
250             self.mainPath=os.path.join(self.mainPath,path)
251 
252         self.request.send(self.mainPath.encode("utf8"))
253 
254 
255 
256 
257     def _mkdir(self,**data):
258 
259         dirname = data.get("dirname")
260         tar_path=os.path.join(self.mainPath,dirname)
261         if not os.path.exists(tar_path):
262             if "/" in dirname:
263                 os.makedirs(tar_path) #创建多级目录
264             else:
265                 os.mkdir(tar_path) #创建单级目录
266 
267             self.request.send(b"mkdir_success!")
268 
269         else:
270             self.request.send(b"dir_exists!")
271 
272 
273     def _rmdir(self,**data):
274         dirname =data.get("dirname")
275 
276         tar_path=os.path.join(self.mainPath,dirname)
277         if os.path.exists(tar_path):
278             if os.path.isfile(tar_path):
279                 os.remove(tar_path) #删除文件
280             else:
281                 shutil.rmtree(tar_path) #删除目录
282 
283             self.request.send(b"rm_success!")
284         else:
285             self.request.send(b"the file or dir does not exist!")
286 
287 
288     def _pwd(self,**data):
289         self.request.send(self.mainPath.encode("utf8"))
server.py

下面是conf文件下的两个文件,一个.py,一个.cfg相当于数据库功能。

 1 import os
 2 
 3 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 4 
 5 
 6 
 7 IP="127.0.0.1"
 8 PORT=8088
 9 
10 
11 ACCOUNT_PATH=os.path.join(BASE_DIR,"conf","accounts.cfg")
settings.py
1 [DEFAULT]
2 
3 [yuan]
4 Password = 123
5 Quotation = 100
6 
7 [root]
8 Password = root
9 Quotation = 100
accounts.cfg

home文件夹下面还有两个文件夹,家目录root文件夹,与一个用户文件夹yuan。

yuan文件夹下面还有一个images文件夹。(调试时,就将用户yuan的一张图片上传到服务器的这个文件夹下)。上传前,它们都是空。

 

关于FTP的客户端,那就简单了,所有程序都在文件FTP_client下,里面有一个.py文件与一张要测试上传的图片。

  1 import optparse
  2 import socket
  3 import json,os,sys,time,hashlib
  4 
  5 STATUS_CODE  = {
  6     250 : "Invalid cmd format, e.g: {'action':'get','filename':'test.py','size':344}",
  7     251 : "Invalid cmd ",
  8     252 : "Invalid auth data",
  9     253 : "Wrong username or password",
 10     254 : "Passed authentication",
 11 
 12     800 : "the file exist,but not enough ,is continue? ",
 13     801 : "the file exist !",
 14     802 : " ready to receive datas",
 15 
 16     900 : "md5 valdate success"
 17 
 18 }
 19 
 20 class ClientHanlder(object):
 21 
 22     def __init__(self):
 23 
 24         self.op=optparse.OptionParser()
 25         self.op.add_option("-s","--server",dest="server")
 26         self.op.add_option("-P","--port",dest="port")
 27         self.op.add_option("-u","--username",dest="username")
 28         self.op.add_option("-p","--password",dest="password")
 29         self.options , self.args=self.op.parse_args()
 30         self.verify_args()
 31         self.make_connection()
 32         self.mainPath=os.path.dirname(os.path.abspath(__file__))
 33         self.last=0
 34 
 35 
 36 
 37     def verify_args(self,):
 38         if self.options.server and self.options.port:
 39             #print(options)
 40             if int(self.options.port) >0 and int(self.options.port) <65535:
 41                 return True
 42             else:
 43                 exit("Err:host port must in 0-65535")
 44 
 45     def make_connection(self):
 46 
 47         self.sock = socket.socket()
 48         self.sock.connect((self.options.server,int(self.options.port)))
 49 
 50 
 51     def interactive(self):
 52 
 53         if self.authenticate():
 54 
 55             print("---start interactive with you ...")
 56             while 1:
 57                 cmd_info=input("[%s]"%self.current_path).strip()
 58                 if len(cmd_info)==0:continue
 59                 cmd_list = cmd_info.split()
 60                 if hasattr(self,"_%s"%cmd_list[0]):
 61                     func=getattr(self,"_%s"%cmd_list[0])
 62                     func(cmd_list)
 63 
 64                 else:
 65                     print("Invalid cmd")
 66 
 67 
 68 
 69 
 70     def authenticate(self):
 71 
 72         if self.options.username and self.options.password:
 73             return self.get_auth_result(self.options.username,self.options.password)
 74         else:
 75 
 76             username=input("username: ")
 77             password=input("password: ")
 78             return self.get_auth_result(username,password)
 79 
 80     def get_auth_result(self,username,password):
 81 
 82         data = {'action':'auth',
 83                 'username':username,
 84                 'password':password}
 85 
 86         self.sock.send(json.dumps(data).encode())
 87         response = self.get_response()
 88         print("response",response)
 89         if response.get('status_code') == 254:
 90             print("Passed authentication!")
 91             self.user = username
 92             self.current_path = '/'+username
 93 
 94             return True
 95         else:
 96             print(response.get("status_msg"))
 97 
 98     def get_response(self):
 99 
100         data = self.sock.recv(1024)
101         data = json.loads(data.decode())
102         return data
103 
104     def progress_percent(self,has,total,):
105 
106 
107         rate = float(has) / float(total)
108         rate_num = int(rate * 100)
109 
110         if self.last!=rate_num:
111 
112             sys.stdout.write("%s%% %s\r"%(rate_num,"#"*rate_num))
113 
114         self.last=rate_num
115 
116 
117     def _post(self,cmd_list):
118 
119         action,local_path,target_path=cmd_list
120 
121         if "/" in local_path:
122             local_path=os.path.join(self.mainPath,local_path.split("/"))
123         local_path=os.path.join(self.mainPath,local_path)
124 
125         file_name=os.path.basename(local_path)
126         file_size=os.stat(local_path).st_size
127 
128         data_header = {
129             'action':'post',
130             'file_name': file_name,
131             'file_size': file_size,
132             'target_path':target_path
133          }
134 
135         self.sock.send(json.dumps(data_header).encode())
136 
137         result_exist=str(self.sock.recv(1024),"utf8")
138         has_sent=0
139 
140         if result_exist=="800":
141             choice=input("the file exist ,is_continue?").strip()
142             if choice.upper()=="Y":
143                 self.sock.sendall(bytes("Y","utf8"))
144                 result_continue_pos=str(self.sock.recv(1024),"utf8")
145                 print(result_continue_pos)
146                 has_sent=int(result_continue_pos)
147 
148             else:
149                 self.sock.sendall(bytes("N","utf8"))
150 
151         elif result_exist=="801":
152             print(STATUS_CODE[801])
153             return
154 
155 
156         file_obj=open(local_path,"rb")
157         file_obj.seek(has_sent)
158         start=time.time()
159 
160         while has_sent<file_size:
161 
162             data=file_obj.read(1024)
163             self.sock.sendall(data)
164             has_sent+=len(data)
165 
166             self.progress_percent(has_sent,file_size)
167 
168         file_obj.close()
169         end=time.time()
170         print("cost %s s"% (end-start))
171         print("post success!")
172 
173     def _post_md5(self,cmd_list):
174 
175 
176         if len(cmd_list)==3:
177 
178             action,local_path,target_path=cmd_list
179         else:
180             action,local_path,target_path,is_md5=cmd_list
181 
182         if "/" in local_path:
183             local_path=os.path.join(self.mainPath,local_path.split("/"))
184         local_path=os.path.join(self.mainPath,local_path)
185 
186         file_name=os.path.basename(local_path)
187         file_size=os.stat(local_path).st_size
188 
189         data_header = {
190             'action':'post_md5',
191             'file_name': file_name,
192             'file_size': file_size,
193             'target_path':target_path
194          }
195 
196         if self.__md5_required(cmd_list):
197             data_header['md5'] = True
198 
199 
200         self.sock.send(json.dumps(data_header).encode())
201 
202         result_exist=str(self.sock.recv(1024),"utf8")
203         has_sent=0
204 
205         if result_exist=="800":
206             choice=input("the file exist ,is_continue?").strip()
207             if choice.upper()=="Y":
208                 self.sock.sendall(bytes("Y","utf8"))
209                 result_continue_pos=str(self.sock.recv(1024),"utf8")
210                 print(result_continue_pos)
211                 has_sent=int(result_continue_pos)
212 
213             else:
214                 self.sock.sendall(bytes("N","utf8"))
215 
216         elif result_exist=="801":
217             print(STATUS_CODE[801])
218             return
219 
220 
221         file_obj=open(local_path,"rb")
222         file_obj.seek(has_sent)
223         start=time.time()
224 
225         if self.__md5_required(cmd_list):
226                 md5_obj = hashlib.md5()
227 
228                 while has_sent<file_size:
229 
230                     data=file_obj.read(1024)
231                     self.sock.sendall(data)
232                     has_sent+=len(data)
233                     md5_obj.update(data)
234                     self.progress_percent(has_sent,file_size)
235 
236                 else:
237                     print("post success!")
238                     md5_val = md5_obj.hexdigest()
239                     self.sock.recv(1024)#解决粘包
240                     self.sock.sendall(md5_val.encode("utf8"))
241                     response=self.sock.recv(1024).decode("utf8")
242                     print("response",response)
243                     if response=="900":
244                         print(STATUS_CODE[900])
245 
246         else:
247                 while has_sent<file_size:
248 
249                     data=file_obj.read(1024)
250                     self.sock.sendall(data)
251                     has_sent+=len(data)
252 
253                     self.progress_percent(has_sent,file_size)
254 
255                 else:
256                     file_obj.close()
257                     end=time.time()
258                     print("\ncost %s s"% (end-start))
259                     print("post success!")
260 
261 
262 
263     def __md5_required(self,cmd_list):
264         '''检测命令是否需要进行MD5验证'''
265         if '--md5' in cmd_list:
266             return True
267 
268 
269     def _ls(self,cmd_list):
270 
271         data_header = {
272             'action':'ls',
273         }
274         self.sock.send(json.dumps(data_header).encode())
275 
276         data = self.sock.recv(1024)
277 
278         print(data.decode("utf8"))
279 
280 
281     def _cd(self,cmd_list):
282 
283         data_header = {
284 
285              'action':'cd',
286 
287              'path':cmd_list[1]
288          }
289 
290         self.sock.send(json.dumps(data_header).encode())
291 
292         data = self.sock.recv(1024)
293         print(data.decode("utf8"))
294         self.current_path='/'+os.path.basename(data.decode("utf8"))
295 
296     def _mkdir(self,cmd_list):
297 
298         data_header = {
299             'action':'mkdir',
300              'dirname':cmd_list[1]
301          }
302 
303         self.sock.send(json.dumps(data_header).encode())
304         data = self.sock.recv(1024)
305         print(data.decode("utf8"))
306 
307     def _rmdir(self,cmd_list):
308         data_header = {
309              'action':'rm',
310              'target_path':cmd_list[1]
311          }
312         self.sock.send(json.dumps(data_header).encode())
313         data = self.sock.recv(1024)
314         print(data.decode("utf8"))
315 
316 
317     def _pwd(self,cmd_list):
318 
319         data_header = {
320             'action':'pwd',
321          }
322 
323         self.sock.send(json.dumps(data_header).encode())
324         data = self.sock.recv(1024)
325         print(data.decode("utf8"))
326 
327 
328 ch=ClientHanlder()
329 ch.interactive()
ftp_client.py

 

以上就是全部内容,但是注意,这个服务器打开,客户端上传下载都是用指令行完成的。

比如打开服务器,就先找到启动文件位置,然后python ftp_server.py start

客户端注册,就是Python ftp_client.py -s 127.0.0.1, -P 8088 -u 用户名 -p 密码。

上传就是 put 12.jpg(文件名)  images(目标位置)

等等

posted @ 2018-08-19 10:31  maxiaonong  阅读(699)  评论(0编辑  收藏  举报