聲明:html
該項目參考學習地址:python
http://www.cnblogs.com/lianzhilei/p/5869205.html , 感謝博主分享,若有侵權,當即刪除。數據庫
做業:開發一個支持多用戶在線的FTP程序json
要求:windows
程序:服務器
一、READMEsocket
# 做者介紹: author: hkey # 博客地址: # 功能實現: 做業:開發一個支持多用戶在線的FTP程序 要求: 用戶加密認證 容許同時多用戶登陸 每一個用戶有本身的家目錄 ,且只能訪問本身的家目錄 對用戶進行磁盤配額,每一個用戶的可用空間不一樣 容許用戶在ftp server上隨意切換目錄 容許用戶查看當前目錄下文件 容許上傳和下載文件,保證文件一致性 文件傳輸過程當中顯示進度條 附加功能:支持文件的斷點續傳 # 目錄結構: FTP/ ├── ftp_client/ # ftp客戶端程序 │ ├── ftp_client.py # 客戶端主程序 │ ├── __init__.py └── ftp_server/ # ftp服務端程序 ├── conf/ # 配置文件目錄 │ ├── __init__.py │ └── settings.py ├── database/ # 用戶數據庫 │ ├── hkey.db │ └── xiaofei.db ├── ftp_server.py ├── home/ # 用戶家目錄 │ ├── hkey/ │ └── xiaofei/ ├── __init__.py ├── log/ └── modules/ # 程序核心功能目錄 ├── auth_user.py ├── __init__.py └── socket_server.py # 功能實現: 1. 初始化配置在conf下的settings.py 文件裏聲明,第一次運行建立用戶家目錄(home/)和數據文件(database/) 2. 每一個用戶的磁盤配額爲10M, 在conf/settings.py 中聲明, 能夠修改 3. 本程序適用於windows,命令:cd / mkdir / pwd / dir / put / get 4. 實現了get下載續傳的功能: 服務器存在文件, 客戶端不存在,直接下載; 服務器存在文件, 客戶端也存在文件,比較大小, 一致則不傳,不一致則追加續傳; # 狀態碼: 400 用戶認證失敗 401 命令不正確 402 文件不存在 403 建立文件已經存在 404 磁盤空間不夠 405 不續傳 200 用戶認證成功 201 命令能夠執行 202 磁盤空間夠用 203 文件具備一致性 205 續傳
# 做者介紹: author: hkey # 博客地址: http://www.cnblogs.com/hukey/p/8909046.html # 功能實現: 做業:開發一個支持多用戶在線的FTP程序 要求: 用戶加密認證 容許同時多用戶登陸 每一個用戶有本身的家目錄 ,且只能訪問本身的家目錄 對用戶進行磁盤配額,每一個用戶的可用空間不一樣 容許用戶在ftp server上隨意切換目錄 容許用戶查看當前目錄下文件 容許上傳和下載文件,保證文件一致性 文件傳輸過程當中顯示進度條 附加功能:支持文件的斷點續傳 # 目錄結構: FTP/ ├── ftp_client/ # ftp客戶端程序 │ ├── ftp_client.py # 客戶端主程序 │ ├── __init__.py └── ftp_server/ # ftp服務端程序 ├── conf/ # 配置文件目錄 │ ├── __init__.py │ └── settings.py ├── database/ # 用戶數據庫 │ ├── hkey.db │ └── xiaofei.db ├── ftp_server.py ├── home/ # 用戶家目錄 │ ├── hkey/ │ └── xiaofei/ ├── __init__.py ├── log/ └── modules/ # 程序核心功能目錄 ├── auth_user.py ├── __init__.py └── socket_server.py # 功能實現: 1. 初始化配置在conf下的settings.py 文件裏聲明,第一次運行建立用戶家目錄(home/)和數據文件(database/) 2. 每一個用戶的磁盤配額爲10M, 在conf/settings.py 中聲明, 能夠修改 3. 本程序適用於windows,命令:cd / mkdir / pwd / dir / put / get 4. 實現了get下載續傳的功能: 服務器存在文件, 客戶端不存在,直接下載; 服務器存在文件, 客戶端也存在文件,比較大小, 一致則不傳,不一致則追加續傳; # 狀態碼: 400 用戶認證失敗 401 命令不正確 402 文件不存在 403 建立文件已經存在 404 磁盤空間不夠 405 不續傳 200 用戶認證成功 201 命令能夠執行 202 磁盤空間夠用 203 文件具備一致性 205 續傳 000 系統交互碼
二、程序的結構ide
三、ftp客戶端程序學習
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket import os, json, sys class Myclient(object): def __init__(self, ip_port): self.client = socket.socket() self.ip_port = ip_port def connect(self): self.client.connect(self.ip_port) def start(self): self.connect() while True: username = input('輸入用戶名:').strip() password = input('輸入密碼:').strip() login_info = '%s:%s' % (username, password) self.client.sendall(login_info.encode()) status_code = self.client.recv(1024).decode() if status_code == '400': print('[%s] 用戶密碼錯誤!' % status_code) elif status_code == '200': print('[%s] 登陸成功!' % status_code) self.interactive() def interactive(self): while True: command = input('-->').strip() if not command: continue command_str = command.split()[0] if hasattr(self, command_str): func = getattr(self, command_str) func(command) def get(self, command): self.client.sendall(command.encode()) status_code = self.client.recv(1024).decode() if status_code == '201': filename = command.split()[1] print(filename) if os.path.isfile(filename): self.client.sendall('403'.encode()) revice_size = os.stat(filename).st_size response = self.client.recv(1024) self.client.sendall(str(revice_size).encode()) status_code = self.client.recv(1024).decode() print('-----------------') if status_code == '205': print('[%s] 續傳' % status_code) self.client.sendall('000'.encode()) elif status_code == '405': print('[%s] 文件一致。' % status_code) return else: self.client.sendall('402'.encode()) revice_size = 0 file_size = self.client.recv(1024).decode() file_size = int(file_size) response = self.client.sendall('000'.encode()) with open(filename, 'ab') as file: while revice_size != file_size: data = self.client.recv(1024) revice_size += len(data) file.write(data) self.__progress(revice_size, file_size, '下載中') else: print('[%s] Error!' % status_code) def put(self, command): if len(command.split()) > 1: filename = command.split()[1] if os.path.isfile(filename): self.client.sendall(command.encode()) response = self.client.recv(1024) file_size = os.stat(filename).st_size self.client.sendall(str(file_size).encode()) status_code = self.client.recv(1024).decode() if status_code == '202': with open(filename, 'rb') as file: for line in file: send_size = file.tell() self.client.sendall(line) self.__progress(send_size, file_size, '上傳中') elif status_code == '404': print('[%s] Error!' % status_code) else: print('[401] Error!') def dir(self, command): self.__universal_method_data(command) def pwd(self, command): self.__universal_method_data(command) def mkdir(self, command): self.__universal_method_none(command) def cd(self, command): self.__universal_method_none(command) def __universal_method_none(self, command): self.client.sendall(command.encode()) status_code = self.client.recv(1024).decode() if status_code == '201': self.client.sendall('000'.encode()) else: print('[%s] Error!' % status_code) def __universal_method_data(self, command): self.client.sendall(command.encode()) status_code = self.client.recv(1024).decode() print(status_code) if status_code == '201': self.client.sendall('000'.encode()) result = self.client.recv(1024).decode() print(result) else: print('[%s] Error!' % status_code) def __progress(self, trans_size, file_size, mode): bar_length = 100 percent = float(trans_size) / float(file_size) hashes = '=' * int(percent * bar_length) spaces = ' ' * int(bar_length - len(hashes)) sys.stdout.write('\r%s:%.2fM/%.2fM %d%% [%s]' \ % (mode, trans_size/1048576, file_size/1048576, percent*100, hashes+spaces)) if __name__ == '__main__': client = Myclient(('localhost', 8000)) client.start()
四、ftp服務端啓動程序加密
#!/usr/bin/env python # -*- coding:utf-8 -*- import os, json from conf import settings from modules import socket_server def create_db(): user_database = {} limit_size = settings.LIMIT_SIZE for k, v in settings.USER_INFO.items(): username = k password = v user_home_path = settings.HOME_DIR + r'\%s' % username user_db_path = settings.DATABASE_DIR + r'\%s.db' % username user_database['username'] = username user_database['password'] = password user_database['limitsize'] = limit_size user_database['homepath'] = user_home_path if not os.path.isfile(user_db_path): with open(user_db_path, 'w') as file: file.write(json.dumps(user_database)) def create_home(): for username in settings.USER_INFO: user_home_path = settings.HOME_DIR + r'\%s' % username if not os.path.isdir(user_home_path): os.popen('mkdir %s' % user_home_path) if __name__ == '__main__': create_db() create_home() server = socket_server.socketserver.ThreadingTCPServer(settings.IP_PORT, socket_server.Myserver) server.serve_forever()
五、conf配置文件
#!/usr/bin/env python # -*- coding:utf-8 -*- import os, sys # 程序主目錄 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, BASE_DIR) # 數據庫目錄 DATABASE_DIR = os.path.join(BASE_DIR, 'database') # 用戶家目錄 HOME_DIR = os.path.join(BASE_DIR, 'home') # 用戶配額 LIMIT_SIZE = 10240000 # 用戶信息 USER_INFO = {'hkey': '123456', 'xiaofei': 'abc'} # ip and port IP_PORT = ('localhost', 8000)
六、database 用戶數據庫(初始化生成,生成程序 - conf/settings.py)
{"homepath": "E:\\py_code\\FTP\\ftp_server\\home\\hkey", "username": "hkey", "limitsize": 10240000, "password": "123456"}
{"homepath": "E:\\py_code\\FTP\\ftp_server\\home\\xiaofei", "username": "xiaofei", "limitsize": 10240000, "password": "abc"}
七、modules 核心功能模塊
#!/usr/bin/env python # -*- coding:utf-8 -*- import json, os from conf import settings class User_operation(object): def authentication(self, login_info): username, password = login_info.split(':') DB_FILE = settings.DATABASE_DIR + r'\%s' % username if os.path.isfile(DB_FILE): user_database = self.cat_database(DB_FILE) if username == user_database['username'] and \ password == user_database['password']: return user_database def cat_database(self, DB_FILE): with open(DB_FILE, 'r') as file: data = json.loads(file.read()) return data
#!/usr/bin/env python # -*- coding:utf-8 -*- import socketserver import os, json from os.path import getsize, join from modules import auth_user from conf import settings class Myserver(socketserver.BaseRequestHandler): def handle(self): try: while True: login_info = self.request.recv(1024).decode() print(login_info) result = self.authenticat(login_info) status_code = result[0] self.request.sendall(status_code.encode()) if status_code == '400': continue self.user_db = result[1] self.user_current_path = self.user_db['homepath'] self.user_home_path = self.user_db['homepath'] while True: command = self.request.recv(1024).decode() command_str = command.split()[0] if hasattr(self, command_str): func = getattr(self, command_str) func(command) else: self.request.sendall('401'.encode()) except ConnectionResetError as e: self.request.close() print('Error:', e) def authenticat(self, login_info): auth = auth_user.User_operation() result = auth.authentication(login_info) if result: return '200', result else: return '400', result def get(self, command): print('func: get()') if len(command.split()) > 1: filename = command.split()[1] user_file_path = self.user_current_path + r'\%s' % filename if os.path.isfile(user_file_path): print(user_file_path) self.request.sendall('201'.encode()) file_size = os.stat(user_file_path).st_size status_code = self.request.recv(1024).decode() if status_code == '403': self.request.sendall('000'.encode()) has_send_data = int(self.request.recv(1024).decode()) if has_send_data < file_size: self.request.sendall('205'.encode()) response = self.request.recv(1024).decode() else: self.request.sendall('405'.encode()) return else: has_send_data = 0 self.request.sendall(str(file_size).encode()) response = self.request.recv(1024) with open(user_file_path, 'rb') as file: file.seek(has_send_data) self.request.sendall(file.read()) else: self.request.sendall('402'.encode()) else: self.request.sendall('401'.encode()) def put(self, command): filename = command.split()[1] file_path = self.user_current_path + r'\%s' % filename self.request.sendall('000'.encode()) file_size = self.request.recv(1024).decode() file_size = int(file_size) limit_size = self.user_db['limitsize'] used_size = self.__getdirsize(self.user_home_path) if limit_size >= file_size + used_size: self.request.sendall('202'.encode()) revice_size = 0 with open(file_path, 'wb') as file: while revice_size != file_size: data = self.request.recv(1024) revice_size += len(data) file.write(data) else: self.request.sendall('404'.encode()) def dir(self, command): if len(command.split()) == 1: self.request.sendall('201'.encode()) response = self.request.recv(1024) send_data = os.popen('dir %s' % self.user_current_path) self.request.sendall(send_data.read().encode()) else: self.request.sendall('401'.encode()) def pwd(self, command): if len(command.split()) == 1: self.request.sendall('201'.encode()) response = self.request.recv(1024) self.request.sendall(self.user_current_path.encode()) else: self.request.sendall('401'.encode()) def mkdir(self, command): if len(command.split()) > 1: dir_name = command.split()[1] dir_path = self.user_current_path + r'\%s' % dir_name if not os.path.isdir(dir_path): self.request.sendall('201'.encode()) response = self.request.recv(1024) os.popen('mkdir %s' % dir_path) else: self.request.sendall('403'.encode()) else: self.request.sendall('401'.encode()) def cd(self, command): print(command) if len(command.split()) > 1: dir_name = command.split()[1] dir_path = self.user_current_path + r'\%s' % dir_name user_home_path = settings.HOME_DIR + r'\%s' % self.user_db['username'] if dir_name == '..' and len(self.user_current_path) > len(user_home_path): self.request.sendall('201'.encode()) response = self.request.recv(1024) self.user_current_path = os.path.dirname(self.user_current_path) elif os.path.isdir(dir_path): self.request.sendall('201'.encode()) response = self.request.recv(1024) if dir_name != '.' and dir_name != '..': self.user_current_path += r'\%s' % dir_name else: self.request.sendall('402'.encode()) else: self.request.sendall('401'.encode()) def __getdirsize(self, home_path): size = 0 for root, dirs, files in os.walk(home_path): size += sum([getsize(join(root, name)) for name in files]) return size
程序運行效果圖:
get 續傳功能