FTP

Posted wangcheng9418

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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()

















以上是关于FTP的主要内容,如果未能解决你的问题,请参考以下文章

七个办法只有一个有效:200 PORT command successful. Consider using PASV.425 Failed to establish connection.(代码片段

Maven FTP部署:无法创建目录

FTP传大文件又慢又麻烦,有没有更好的替代传输方案?

微信小程序代码片段

VSCode自定义代码片段——CSS选择器

谷歌浏览器调试jsp 引入代码片段,如何调试代码片段中的js