标签:tar on() write 循环 UNC received 直接 rac 远程服务
支持多用户在线的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()
标签:tar on() write 循环 UNC received 直接 rac 远程服务
原文地址:https://www.cnblogs.com/wangcheng9418/p/9300320.html