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.(代码片段