• 开发一个支持多用户在线的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='
    '.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
    "%(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("
    cost %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(目标位置)

    等等

  • 相关阅读:
    I/O多路复用技术
    网络编程的异常及处理
    LINUX的signal
    网络编程小知识
    一个位压缩技巧
    加密技术[翻译]
    暴雪的hash算法[翻译]
    喜欢就好
    【PYTHON】编码是个细致活
    【Python3】POP3协议收邮件
  • 原文地址:https://www.cnblogs.com/maxiaonong/p/9500340.html
Copyright © 2020-2023  润新知