基于socketserver开发多线程ftp
完成功能:
- 用户加密认证
- 允许同时多用户登录
- 每个用户有自己的家目录 ,且只能访问自己的家目录
- 对用户进行磁盘配额,每个用户的可用空间不同
- 允许用户在ftp server上随意切换目录
- 允许用户查看当前目录下文件
- 允许上传和下载文件
- 文件传输过程中显示进度条
- 附加功能:支持文件的断点续传
目录结构:
ftpclient:
ftp_client.py
ftpserver:
bin
ftp_server.py 启动文件
conf
settings 配置文件
account.cfg 用户信息
core 存放逻辑代码
main 主程序入口
server 逻辑处理
home用户家目录
ftp客户端代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# _*_ coding:utf-8 _*_ # Auother Jerry import socket import optparse, os, sys import json # 服务端返回码对应内容 STATUS_CODE = { 250: 'Invalid cmd format,e.g:{"action":"get","filename":"test.py","size":443}', 251: "Invalid cmd", 252: 'Invalid auth data', 253: 'Wrong username or password', 254: 'Passwed authentication', 255: "Filename doest't provided", 256: "File doesn't exist on server", 257: "ready to send file", 258: 'md5 verification', 800: 'the file exist,but not enough,is continue?', 801: 'the file exist!', 802: 'ready to receive datas', 900: 'md5 valdate success' } class ClientHandler(object): def __init__(self): self.parser = optparse.OptionParser() self.sock = None self.user = None self.mainPath = os.path.dirname(os.path.abspath(__file__)) self.parser.add_option("-s", "--server", dest="server") self.parser.add_option("-P", "--port", dest="port") self.parser.add_option("-u", "--username", dest="username") self.parser.add_option("-p", "--password", dest="password") self.options, self.args = self.parser.parse_args() self.verify_args() self.make_connect() # 确认端口在0~65535之间 def verify_args(self): port = self.options.port if int(port) > 0 and int(port) < 65535: return True else: exit('port must in 0~65535') # 创建连文件 def make_connect(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((self.options.server, int(self.options.port))) def authenticate(self): if self.options.username is None and self.options.password is None: username = input('username: ').strip() password = input('password: ').strip() elif self.options.username: username = self.options.username if self.options.password is None: password = input('password: ').strip() else: password = self.options.password else: print(self.parser.print_help()) return return self.get_auth_result(username, password) def interactive(self): if self.authenticate(): while True: cmd_info = input('[%s]' % self.user).strip() if cmd_info == 'q': exit() elif len(cmd_info) == 0:continue cmd_list = cmd_info.split() if hasattr(self, cmd_list[0]): func = getattr(self, cmd_list[0]) func(*cmd_list) def response(self): ret = self.sock.recv(1024).decode('utf-8') data = json.loads(ret) return data def get_auth_result(self, username, password): data = { "action": "auth", "username": username, "password": password } self.sock.send(json.dumps(data).encode('utf-8')) ret = self.sock.recv(1024).decode('utf-8') data = json.loads(ret) status_code = data['status_code'] if status_code == 254: print(STATUS_CODE[254]) self.user = username return True else: print(STATUS_CODE[status_code]) def put(self, *args): action, local_file, target_path = args local_file = os.path.join(self.mainPath, local_file) if local_file: file_name = os.path.basename(local_file) file_size = os.stat(local_file).st_size data = { "action": "put", "file_name": file_name, "file_size": file_size, "target_path": target_path } print('send data info ', data) self.sock.send(json.dumps(data).encode('utf-8')) code = int(self.sock.recv(1024).decode("utf-8")) print('receive code ', code) send_size = 0 with open(local_file, 'rb') as f: if code == 800: print(STATUS_CODE[code]) choice = input('the file exist,but not enough,is continue?Y|N').strip() self.sock.sendall(choice.encode('utf-8')) if choice == 'Y' or choice == 'y': current_seek = self.sock.recv(1024).decode('utf-8') send_size = int(current_seek) f.seek(int(current_seek)) elif code == 801: print(STATUS_CODE[code]) return else: print(STATUS_CODE[code]) while send_size < file_size: send_data = f.read(1024) self.sock.send(send_data) send_size += len(send_data) self.show_process(send_size,file_size) def show_process(self, send_size, file_size): per = int(float(send_size) / float(file_size) * 100) if send_size == file_size: sys.stdout.write('succeed! %s\r' % '#' * per) else: sys.stdout.write('%s%%%s\r' % (per, '#' * per)) def ls(self,*args): data = { 'action': 'ls', } self.sock.send(json.dumps(data).encode('utf-8')) res = self.sock.recv(1024).decode('utf-8') print(res) def cd(self,*args): data = { "action": "cd", 'cd_path': args[1] } self.sock.send(json.dumps(data).encode('utf-8')) res = self.sock.recv(1024).decode('utf-8') print('[%s]' % res) client = ClientHandler() client.interactive()
ftp服务端代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# _*_ coding:utf-8 _*_ # Auother Jerry import os,sys BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR) from core import main if __name__ == '__main__': main.ArgvHandler()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
[DEFAULT] [jerry] username=jerry password=123 [root] username=root password=123
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# _*_ coding:utf-8 _*_ # Auother Jerry import os BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) IP='127.0.0.1' PORT=8080 ACCOUNT_PATH = os.path.join(BASE_DIR,'conf','accounts.cfg') STATUS_CODE = { 250:'Invalid cmd format,e.g:{"action":"get","filename":"test.py","size":443}', 251:"Invalid cmd", 252:'Invalid auth data', 253:'Wrong username or password', 254:'Passwed authentication', 255:"Filename doest't provided", 256:"File doesn't exist on server", 257:"ready to send file", 258:'md5 verification', 800:'the file exist,but not enough,is continue?', 801:'the file exist!', 802:'ready to receive datas', 900: 'md5 valdate success' } HOME_DIR = os.path.join(BASE_DIR,'home')
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# _*_ coding:utf-8 _*_ # Auother Jerry import optparse import socketserver from conf import settings from core import server class ArgvHandler(object): def __init__(self): self.parser = optparse.OptionParser() options, args = self.parser.parse_args() self.verify_args(options, args) def verify_args(self, options, args): cmd = args[0] print('first argv: ', cmd) if cmd: if hasattr(self, cmd): func = getattr(self, cmd) func() def start(self): print('start socketserver') sk = socketserver.ThreadingTCPServer((settings.IP, settings.PORT), server.ServerHandler) sk.serve_forever()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# _*_ coding:utf-8 _*_ # Auother Jerry import socketserver import json, os import configparser from conf import settings class ServerHandler(socketserver.BaseRequestHandler): def handle(self): try: while True: data = self.request.recv(1024).decode('utf-8') data = json.loads(data) print(data) if data.get('action', None): if hasattr(self, data.get('action')): func = getattr(self, data.get('action')) func(**data) except Exception as e: print(e) def auth(self, **kwargs): username = kwargs['username'] password = kwargs['password'] if self.authenticate(username, password): self.send_response(254) else: self.send_response(253) def authenticate(self, username, password): cfg = configparser.ConfigParser() cfg.read(settings.ACCOUNT_PATH) if username in cfg.sections(): if cfg[username]['password'] == password: self.user = username self.home = os.path.join(settings.HOME_DIR, self.user) self.mainPath = self.home if not os.path.exists(self.home): os.makedirs(self.home) return username def send_response(self, state_code): response = {'status_code': state_code} self.request.send(json.dumps(response).encode('utf-8')) def put(self, **kwargs): file_name = kwargs['file_name'] target_path = kwargs['target_path'] file_size = kwargs['file_size'] abs_path = os.path.join(self.home, target_path, file_name) print('ready receive file ', abs_path) send_size = 0 if os.path.exists(abs_path): file_has_size = os.stat(abs_path).st_size if file_has_size < file_size: f = open(abs_path, 'ab') self.request.sendall("800".encode('utf-8')) choice = self.request.recv(1024).decode('utf-8') if choice == 'Y' or choice == 'y': current_seek = f.tell() self.request.sendall(str(current_seek).encode('utf-8')) else: pass else: self.request.sendall('801'.encode('utf-8')) return else: if not os.path.exists(os.path.dirname(abs_path)): print('make dir ', os.path.dirname(abs_path)) os.makedirs(os.path.dirname(abs_path)) self.request.sendall('802'.encode('utf-8')) f = open(abs_path, 'wb') print('start store file') while send_size < file_size: data = self.request.recv(1024) f.write(data) send_size += len(data) f.close() def ls(self, **kwargs): file_list = os.listdir(self.mainPath) if not len(file_list): file_str = '<empty dir>' else: file_str = '\r'.join(file_list) self.request.send(file_str.encode('utf-8')) def cd(self, **kwargs): cd_path = kwargs['cd_path'] if cd_path == '..': if self.mainPath == self.home: self.mainPath = self.mainPath else: self.mainPath = os.path.dirname(self.mainPath) elif cd_path == '.': pass else: if os.path.exists(os.path.join(self.mainPath, cd_path)): self.mainPath = os.path.join(self.mainPath, cd_path) else: pass self.request.send(os.path.basename(self.mainPath).encode('utf-8'))
验证:
用户登录
上传