zoukankan      html  css  js  c++  java
  • FTP

    一、程序介绍:

    需求:

    支持多用户在线的FTP程序
    
    要求:
    1、用户加密认证
    2、允许同时多用户登录
    3、每个用户有自己的家目录 ,且只能访问自己的家目录
    4、对用户进行磁盘配额,每个用户的可用空间不同
    5、允许用户在ftp server上随意切换目录
    6、允许用户查看当前目录下文件
    7、允许上传和下载文件,保证文件一致性
    8、文件传输过程中显示进度条
    9、附加功能:支持文件的断点续传
    
    实现功能:
    用户加密认证
    允许同时多用户登录
    每个用户有自己的家目录 ,且只能访问自己的家目录
    允许上传和下载文件,保证文件一致性
    文件传输过程中显示进度条
    

    程序结构:

    FTP服务端
    FtpServer  #服务端主目录
    ├── bin  #启动目录
    │   └── ftp_server.py #启动文件
    ├── conf  #配置文件目录
    │   ├── accounts.cfg #用户存储
    │   └── settings.py #配置文件
    ├── core  #程序主逻辑目录
    │   ├── ftp_server.py #功能文件
    │   └── main.py #主逻辑文件
    ├── home  #用户家目录
    │   ├── test001  #用户目录
    │   └── test002  #用户目录
    └── log  #日志目录
    
    FTP客户端
    FtpClient  #客户端主目录
    └── ftp_client.py #客户端执行文件


    二、流程图






    三、代码

    FtpServer

    bin/ftp_server.py
    复制代码
     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 
     4 import os
     5 import sys
     6 
     7 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
     8 sys.path.append(BASE_DIR)
     9 
    10 from core import main
    11 
    12 if __name__ == '__main__':
    13     main.ArvgHandler()
    复制代码
     
    conf/accounts.cfg
    复制代码
    1 [DEFAULT]
    2 
    3 [test001]
    4 Password = 123
    5 Quotation = 100
    6 
    7 [test002]
    8 Password = 123
    9 Quotation = 100
    复制代码
     
    conf/settings.py
    复制代码
     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 
     4 import os
     5 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
     6 
     7 USER_HOME = "%s/home" % BASE_DIR
     8 LOG_DIR = "%s/log" % BASE_DIR
     9 LOG_LEVEL = "DEBUG"
    10 
    11 ACCOUNT_FILE = "%s/conf/accounts.cfg" % BASE_DIR
    12 
    13 HOST = "127.0.0.1"
    14 PORT = 9999
    复制代码
     
    core/ftp_server.py
    复制代码
      1 #!/usr/bin/env python
      2 #_*_coding:utf-8_*_
      3 
      4 import socketserver
      5 import json
      6 import configparser
      7 import os
      8 import hashlib
      9 from conf import settings
     10 
     11 STATUS_CODE = {
     12     250:"Invalid cmd format, e.g:{'action':'get','filename':'test.py','size':344}",
     13     251:"Invalid cmd",
     14     252:"Invalid auth data",
     15     253:"Wrong username or password",
     16     254:"Passed authentication",
     17     255:"filename doesn't provided",
     18     256:"File doesn't exist on server",
     19     257:"ready to send file",
     20     258:"md5 verification",
     21 }
     22 
     23 '''
     24 250:“无效的cmd格式,例如:{'action':'get','filename':'test.py','size':344}”,
     25 251:“无效的CMD”,
     26 252:“验证数据无效”,
     27 253:“错误的用户名或密码”,
     28 254:“通过身份验证”,
     29 255:“文件名不提供”,
     30 256:“服务器上不存在文件”,
     31 257:“准备发送文件”,
     32 258:“md5验证”,
     33 '''
     34 
     35 class FTPHandler(socketserver.BaseRequestHandler):
     36 
     37     def handle(self):
     38         '''接收客户端消息(用户,密码,action)'''
     39         while True:
     40             self.data = self.request.recv(1024).strip()
     41             print(self.client_address[0])
     42             print(self.data)
     43             # self.request.sendall(self.data.upper())
     44 
     45             if not self.data:
     46                 print("client closed...")
     47                 break
     48             data = json.loads(self.data.decode())  #接收客户端消息
     49             if data.get('action') is not None:  #action不为空
     50                 print("---->", hasattr(self, "_auth"))
     51                 if hasattr(self, "_%s" % data.get('action')): #客户端action 符合服务端action
     52                     func = getattr(self, "_%s" % data.get('action'))
     53                     func(data)
     54                 else:  #客户端action 不符合服务端action
     55                     print("invalid cmd")
     56                     self.send_response(251)  # 251:“无效的CMD”
     57             else:  #客户端action 不正确
     58                 print("invalid cmd format")
     59                 self.send_response(250) # 250:“无效的cmd格式,例如:{'action':'get','filename':'test.py','size':344}”
     60 
     61     def send_response(self,status_code,data=None):
     62         '''向客户端返回数据'''
     63         response = {'status_code':status_code,'status_msg':STATUS_CODE[status_code]}
     64         if data:
     65             response.update(data)
     66         self.request.send(json.dumps(response).encode())
     67 
     68     def _auth(self,*args,**kwargs):
     69         '''核对服务端 发来的用户,密码'''
     70         # print("---auth",args,kwargs)
     71         data = args[0]
     72         if data.get("username") is None or data.get("password") is None: #客户端的用户和密码有一个为空 则返回错误
     73             self.send_response(252)  # 252:“验证数据无效”
     74 
     75         user = self.authenticate(data.get("username"),data.get("password")) #把客户端的用户密码进行验证合法性
     76         if user is None: #客户端的数据为空 则返回错误
     77             self.send_response(253)  # 253:“错误的用户名或密码”
     78         else:
     79             print("password authentication",user)
     80             self.user = user
     81             self.send_response(254)  # 254:“通过身份验证”
     82 
     83     def authenticate(self,username,password):
     84         '''验证用户合法性,合法就返回数据,核对本地数据'''
     85         config = configparser.ConfigParser()
     86         config.read(settings.ACCOUNT_FILE)
     87         if username in config.sections():  #用户匹配成功
     88             _password = config[username]["Password"]
     89             if _password == password:  #密码匹配成功
     90                 print("pass auth..",username)
     91                 config[username]["Username"] = username
     92                 return config[username]
     93 
     94     def _put(self,*args,**kwargs):
     95         "client send file to server"
     96         data = args[0]
     97         base_filename = data.get('filename')
     98         file_obj = open(base_filename, 'wb')
     99         data = self.request.recv(4096)
    100         file_obj.write(data)
    101         file_obj.close()
    102 
    103     def _get(self,*args,**kwargs):
    104         '''get 下载方法'''
    105         data = args[0]
    106         if data.get('filename') is None:
    107             self.send_response(255)  # 255:“文件名不提供”,
    108         user_home_dir = "%s/%s" %(settings.USER_HOME,self.user["Username"]) #当前连接用户的目录
    109         file_abs_path = "%s/%s" %(user_home_dir,data.get('filename'))  #客户端发送过来的目录文件
    110         print("file abs path",file_abs_path)
    111 
    112         if os.path.isfile(file_abs_path):  #客户端目录文件名 存在服务端
    113             file_obj = open(file_abs_path,'rb')  # 用bytes模式打开文件
    114             file_size = os.path.getsize(file_abs_path)  #传输文件的大小
    115             self.send_response(257,data={'file_size':file_size}) #返回即将传输的文件大小 和状态码
    116 
    117             self.request.recv(1)  #等待客户端确认
    118 
    119             if data.get('md5'): #有 --md5 则传输时加上加密
    120                 md5_obj = hashlib.md5()
    121                 for line in file_obj:
    122                     self.request.send(line)
    123                     md5_obj.update(line)
    124                 else:
    125                     file_obj.close()
    126                     md5_val = md5_obj.hexdigest()
    127                     self.send_response(258,{'md5':md5_val})
    128                     print("send file done....")
    129             else:  #没有 --md5  直接传输文件
    130                 for line in file_obj:
    131                     self.request.send(line)
    132                 else:
    133                     file_obj.close()
    134                     print("send file done....")
    135 
    136         else:
    137             self.send_response(256) # 256:“服务器上不存在文件”=
    138 
    139 
    140     def _ls(self,*args,**kwargs):
    141         pass
    142 
    143     def _cd(self,*args,**kwargs):
    144         pass
    145     
    146 
    147 if __name__ == '__main__':
    148     HOST, PORT = "127.0.0.1", 9999
    复制代码
     
    core/main.py
    复制代码
     1 #!/usr/bin/env python
     2 #_*_coding:utf-8_*_
     3 
     4 import optparse
     5 from core.ftp_server import FTPHandler
     6 import socketserver
     7 from conf import settings
     8 
     9 class ArvgHandler(object):
    10     def __init__(self):
    11         self.parser = optparse.OptionParser()
    12         # parser.add_option("-s","--host",dest="host",help="server binding host address")
    13         # parser.add_option("-p","--port",dest="port",help="server binding port")
    14         (options, args) = self.parser.parse_args()
    15         # print("parser",options,args)
    16         # print(options.host,options.port)
    17         self.verify_args(options, args)
    18 
    19     def verify_args(self,options,args):
    20         '''校验并调用相应功能'''
    21         if hasattr(self,args[0]):
    22             func = getattr(self,args[0])
    23             func()
    24         else:
    25             self.parser.print_help()
    26 
    27     def start(self):
    28         print('---going to start server---')
    29 
    30         server = socketserver.ThreadingTCPServer((settings.HOST, settings.PORT), FTPHandler)
    31 
    32         server.serve_forever()
    复制代码
     
    FtpClient
    ftp_client.py
    复制代码
      1 #!/usr/bin/env python
      2 #_*_coding:utf-8_*_
      3 
      4 import socket
      5 import os
      6 import sys
      7 import optparse
      8 import json
      9 import hashlib
     10 
     11 STATUS_CODE = {
     12     250:"Invalid cmd format, e.g:{'action':'get','filename':'test.py','size':344}",
     13     251:"Invalid cmd",
     14     252:"Invalid auth data",
     15     253:"Wrong username or password",
     16     254:"Passed authentication",
     17     255:"filename doesn't provided",
     18     256:"File doesn't exist on server",
     19     257:"ready to send file",
     20 }
     21 
     22 class FTPClient(object):
     23     def __init__(self):
     24         parser = optparse.OptionParser()
     25         parser.add_option("-s","--server",dest="server",help="ftp server ip_addr")
     26         parser.add_option("-P","--port",type="int",dest="port",help="ftp server port")
     27         parser.add_option("-u","--username",dest="username",help="username")
     28         parser.add_option("-p","--password",dest="password",help="password")
     29 
     30         self.options,self.args = parser.parse_args()
     31         self.verify_args(self.options,self.args)
     32         self.make_connection()
     33 
     34     def make_connection(self):
     35         '''远程连接'''
     36         self.sock = socket.socket()
     37         self.sock.connect((self.options.server,self.options.port))
     38 
     39     def verify_args(self,options,args):
     40         '''校验参数合法性'''
     41         if options.username is not None and options.password is not None:  #用户和密码,两个都不为空
     42             pass
     43         elif options.username is None and options.password is None: #用户和密码,两个都为空
     44             pass
     45         else:  #用户和密码,有一个为空
     46             # options.username is None or options.password is None:  #用户和密码,有一个为空
     47             exit("Err: username and password must be provided together...")
     48 
     49         if options.server and options.port:
     50             # print(options)
     51             if options.port >0 and options.port <65535:
     52                 return True
     53             else:
     54                 exit("Err:host port must in 0-65535")
     55 
     56     def authenticate(self):
     57         '''用户验证,获取客户端输入信息'''
     58         if self.options.username:  #有输入信息 发到远程判断
     59             print(self.options.username,self.options.password)
     60             return self.get_auth_result(self.options.username,self.options.password)
     61         else:  #没有输入信息 进入交互式接收信息
     62             retry_count = 0
     63             while retry_count <3:
     64                 username = input("username: ").strip()
     65                 password = input("password: ").strip()
     66                 return self.get_auth_result(username,password)
     67                 # retry_count +=1
     68 
     69     def get_auth_result(self,user,password):
     70         '''远程服务器判断 用户,密码,action '''
     71         data = {'action':'auth',
     72                 'username':user,
     73                 'password':password,}
     74 
     75         self.sock.send(json.dumps(data).encode())  #发送 用户,密码,action 到远程服务器  等待远程服务器的返回结果
     76         response = self.get_response()  #获取服务器返回码
     77         if response.get('status_code') == 254: #通过验证的服务器返回码
     78             print("Passed authentication!")
     79             self.user = user
     80             return True
     81         else:
     82             print(response.get("status_msg"))
     83 
     84     def get_response(self):
     85         '''得到服务器端回复结果,公共方法'''
     86         data = self.sock.recv(1024)
     87         data = json.loads(data.decode())
     88         return data
     89 
     90     def interactive(self):
     91         '''交互程序'''
     92         if self.authenticate(): #认证成功,开始交互
     93             print("--start interactive iwth u...")
     94             while True: #循环 输入命令方法
     95                 choice = input("[%s]:"%self.user).strip()
     96                 if len(choice) == 0:continue
     97                 cmd_list = choice.split()
     98                 if hasattr(self,"_%s"%cmd_list[0]): #反射判断 方法名存在
     99                     func = getattr(self,"_%s"%cmd_list[0]) #反射 方法名
    100                     func(cmd_list)  #执行方法
    101                 else:
    102                     print("Invalid cmd.")
    103 
    104     def _md5_required(self,cmd_list):
    105         '''检测命令是否需要进行MD5的验证'''
    106         if '--md5' in cmd_list:
    107             return True
    108 
    109     def show_progress(self,total):
    110         '''进度条'''
    111         received_size = 0
    112         current_percent = 0
    113         while received_size < total:
    114             if int((received_size / total) * 100) > current_percent :
    115                 print("#",end="",flush=True)
    116                 current_percent = (received_size / total) * 100
    117             new_size = yield
    118             received_size += new_size
    119 
    120     def _get(self,cmd_list):
    121         ''' get 下载方法'''
    122         print("get--",cmd_list)
    123         if len(cmd_list) == 1:
    124             print("no filename follows...")
    125             return
    126         #客户端操作信息
    127         data_header = {
    128             'action':'get',
    129             'filename':cmd_list[1],
    130         }
    131 
    132         if self._md5_required(cmd_list):  #命令请求里面有带 --md5
    133             data_header['md5'] = True  #将md5加入 客户端操作信息
    134 
    135         self.sock.send(json.dumps(data_header).encode()) #发送客户端的操作信息
    136         response = self.get_response()  #接收服务端返回的 操作信息
    137         print(response)
    138 
    139         if response["status_code"] ==257: #服务端返回的状态码是:传输中
    140             self.sock.send(b'1')  # send confirmation to server
    141             base_filename = cmd_list[1].split('/')[-1] #取出要接收的文件名
    142             received_size = 0  #本地接收总量
    143             file_obj = open(base_filename,'wb') #bytes模式写入
    144 
    145             if self._md5_required(cmd_list): #命令请求里有 --md5
    146                 md5_obj = hashlib.md5()
    147 
    148                 progress = self.show_progress(response['file_size'])
    149                 progress.__next__()
    150 
    151                 while received_size < response['file_size']: #当接收的量 小于 文件总量 就循环接收文件
    152                     data = self.sock.recv(4096) #一次接收4096
    153                     received_size += len(data) #本地接收总量每次递增
    154 
    155                     try:
    156                         progress.send(len(data))
    157                     except StopIteration as e:
    158                         print("100%")
    159 
    160                     file_obj.write(data) #把接收的数据 写入文件
    161                     md5_obj.update(data) #把接收的数据 md5加密
    162                 else:
    163                     print("--->file rece done<---") #成功接收文件
    164                     file_obj.close() #关闭文件句柄
    165                     md5_val = md5_obj.hexdigest()
    166                     md5_from_server = self.get_response()  #获取服务端发送的 md5
    167                     if md5_from_server['status_code'] ==258:  #状态码为258
    168                         if md5_from_server['md5'] == md5_val:  #两端 md5值 对比
    169                             print("%s 文件一致性校验成功!" %base_filename)
    170                     # print(md5_val,md5_from_server)
    171             else:  #没有md5校验 直接收文件
    172                 progress = self.show_progress(response['file_size'])
    173                 progress.__next__()
    174 
    175                 while received_size < response['file_size']: #当接收的量 小于 文件总量 就循环接收文件
    176                     data = self.sock.recv(4096) #一次接收4096
    177                     received_size += len(data) #本地接收总量每次递增
    178                     file_obj.write(data) #把接收的数据 写入文件
    179                     try:
    180                         progress.send(len(data))
    181                     except StopIteration as e:
    182                         print("100%")
    183                 else:
    184                     print("--->file rece done<---") #成功接收文件
    185                     file_obj.close() #关闭文件句柄
    186 
    187     def _put(self,cmd_list):
    188         ''' put 下载方法'''
    189         print("put--", cmd_list)
    190         if len(cmd_list) == 1:
    191             print("no filename follows...")
    192             return
    193         # 客户端操作信息
    194         data_header = {
    195             'action': 'put',
    196             'filename': cmd_list[1],
    197         }
    198         self.sock.send(json.dumps(data_header).encode())  # 发送客户端的操作信息
    199         self.sock.recv(1)
    200         file_obj = open(cmd_list[1],'br')
    201         for line in file_obj:
    202             self.sock.send(line)
    203 
    204 
    205 if __name__ == '__main__':
    206     ftp = FTPClient()
    207     ftp.interactive()
  • 相关阅读:
    Hibernate读书笔记Hibernate的关联映射之11关联映射
    Hibernate读书笔记Hibernate的关联映射之NN关联映射 .
    Hibernate读书笔记Hibernate的关联映射之N1关联映射
    Hibernate读书笔记继承映射
    String类型转换的三种方法分析
    使用using关键字对连接进行简化
    Hibernate读书笔记继承映射
    关于ORACLE的ora12505报错以及连接问题的解决及相关资料
    10天学会Web标准建站
    Oracle中大对象(lob)处理方法
  • 原文地址:https://www.cnblogs.com/wangcheng9418/p/9300320.html
Copyright © 2011-2022 走看看