ftpclient目录
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket import os,sys import hashlib import struct class Myclient(): '''ftp客户端''' def __init__(self,ip_port): self.ip_port = ip_port def connect(self): '''连接服务器''' self.client = socket.socket() 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) continue else:print("[%s]用户密码认证成功"%status_code) self.interactive() def interactive(self): '''开始交互''' while True: command = input("->>").strip() if not command:continue #if command == "exit":break command_str = command.split()[0] if hasattr(self,command_str): # 执行命令 func = getattr(self,command_str) func(command) else:print("[%s]命令不存在" %401) def get(self,command): '''下载文件''' self.client.sendall(command.encode()) #发送要执行的命令 status_code = self.client.recv(1024).decode() if status_code == "201": #命令可执行 filename = command.split()[1] # 文件名存在,判断是否续传 if os.path.isfile(filename): revice_size = os.stat(filename).st_size #文件已接收大小 self.client.sendall("403".encode()) response = self.client.recv(1024) self.client.sendall(str(revice_size).encode()) #发送已接收文件大小 status_code = self.client.recv(1024).decode() # 文件大小不一致,续传 if status_code == "205": print("继续上次上传位置进行续传") self.client.sendall("000".encode()) # 文件大小一致,不续传,不下载 elif status_code == "405": print("文件已经存在,大小一致") return # 文件不存在 else: self.client.sendall("402".encode()) revice_size = 0 file_size = self.client.recv(1024).decode() #文件大小 file_size = int(file_size) self.client.sendall("000".encode()) with open(filename,"ab") as file: #开始接收 #file_size 为文件总大小 file_size +=revice_size m = hashlib.md5() while revice_size < file_size: minus_size = file_size - revice_size if minus_size > 1024: size = 1024 else: size = minus_size data = self.client.recv(size) revice_size += len(data) file.write(data) m.update(data) self.__progress(revice_size,file_size,"下载中") #进度条 new_file_md5 = m.hexdigest() #生成新文件的md5值 server_file_md5 = self.client.recv(1024).decode() if new_file_md5 == server_file_md5: #md5值一致 print(" 文件具有一致性") 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) #收到ack确认 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: m = hashlib.md5() for line in file: m.update(line) send_size = file.tell() self.client.sendall(line) self.__progress(send_size, file_size, "上传中") # 进度条 self.client.sendall(m.hexdigest().encode()) #发送文件md5值 status_code = self.client.recv(1024).decode() #返回状态码 if status_code == "203": print(" 文件具有一致性") else:print("[%s] Error!"%(status_code)) else: print("[402] Error") else: print("[401] Error") def dir(self,command): '''查看当前目录下的文件''' self.__universal_method_data(command) pass def pwd(self,command): '''查看当前用户路径''' self.__universal_method_data(command) pass def mkdir(self,command): '''创建目录''' self.__universal_method_none(command) pass def cd(self,command): '''切换目录''' self.__universal_method_none(command) pass def __progress(self, trans_size, file_size,mode): ''' 显示进度条 trans_size: 已经传输的数据大小 file_size: 文件的总大小 mode: 模式 ''' bar_length = 100 #进度条长度 percent = float(trans_size) / float(file_size) hashes = '=' * int(percent * bar_length) #进度条显示的数量长度百分比 spaces = ' ' * (bar_length - len(hashes)) #定义空格的数量=总长度-显示长度 sys.stdout.write( " %s:%.2fM/%.2fM %d%% [%s]"%(mode,trans_size/1048576,file_size/1048576,percent*100,hashes+spaces)) sys.stdout.flush() def __universal_method_none(self,command): '''通用方法,无data输出''' 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): '''通用方法,有data输出''' self.client.sendall(command.encode()) #发送要执行的命令 status_code = self.client.recv(1024).decode() if status_code == "201": #命令可执行 self.client.sendall("000".encode()) #系统交互 data = self.client.recv(1024).decode() print(data) else:print("[%s] Error!" % (status_code)) if __name__ == "__main__": ip_port =("127.0.0.1",9999) #服务端ip、端口 client = Myclient(ip_port) #创建客户端实例 client.start() #开始连接
################################################################################
ftpserver目录
bin目录ftpserver.py
#!/usr/bin/env python # -*- coding:utf-8 -*- import os,hashlib import json,sys BASE_DIR = os.path.dirname(os.path.dirname((os.path.abspath(__file__)))) sys.path.append(BASE_DIR) print(sys.path) from conf import settings from modules import auth_user from modules import sokect_server def create_db(): '''创建用户数据库文件''' user_database={} encryption = auth_user.User_operation() limitsize = settings.LIMIT_SIZE for k,v in settings.USERS_PWD.items(): username = k password = encryption.hash(v) user_db_path = settings.DATABASE + r"\%s.db"%username user_home_path = settings.HOME_PATH + r"\%s"%username user_database["username"] = username user_database["password"] = password user_database["limitsize"] = limitsize 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_dir(): '''创建用户属主目录''' for username in settings.USERS_PWD: user_home_path = settings.HOME_PATH + r"\%s" %username if not os.path.isdir(user_home_path): os.popen("mkdir %s" %user_home_path) if __name__ == "__main__": '''初始化系统数据并启动程序''' create_db() #创建数据库 create_dir() #创建属主目录 #启动ftp服务 server = sokect_server.socketserver.ThreadingTCPServer(settings.IP_PORT, sokect_server.Myserver) server.serve_forever()
conf目录settings
#!/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 = os.path.join(BASE_DIR,"database") #用户属主目录 HOME_PATH = os.path.join(BASE_DIR,"home") #用户字典 USERS_PWD = {"test1":"123456","test2":"123456","test3":"123456"} #磁盘配额 10M LIMIT_SIZE = 10240000 #ftp服务端口 IP_PORT = ("0.0.0.0",9999)
datebase ,home空
modules目录auth_user.py,socket_server.py
#!/usr/bin/env python # -*- coding:utf-8 -*- import json import sys,os import hashlib from conf import settings class User_operation(): '''对登录信息进行认证,登录成功返回用户名,失败返回None''' def authentication(self,login_info): list = login_info.split(":") #对信息进行分割 login_name = list[0] login_passwd = self.hash(list[1]) DB_FILE = settings.DATABASE + r"\%s.db"%login_name if os.path.isfile(DB_FILE): user_database = self.cat_database(DB_FILE) #用户数据库信息 if login_name == user_database["username"]: if login_passwd == 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 def hash(self,passwd): '''对密码进行md5加密''' m = hashlib.md5() m.update(passwd.encode("utf-8")) return m.hexdigest()
#!/usr/bin/env python # -*- coding:utf-8 -*- #-Author-Lian import socketserver import sys,os import hashlib from os.path import join, getsize from conf import settings from modules import auth_user class Myserver(socketserver.BaseRequestHandler): '''ftp服务端''' def handle(self): try: self.conn = self.request while True: login_info = self.conn.recv(1024).decode() # 接收客户端发的的账号密码信息 result = self.authenticat(login_info) status_code = result[0] self.conn.sendall(status_code.encode()) if status_code == "400": continue self.user_db = result[1] #当前登录用户信息 self.current_path = self.user_db["homepath"] #用户当前目录 self.home_path = self.user_db["homepath"] #用户宿主目录 while True: command = self.conn.recv(1024).decode() command_str = command.split()[0] if hasattr(self,command_str): func = getattr(self,command_str) func(command) else:self.conn.sendall("401".encode()) except ConnectionResetError as e: self.conn.close() print(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): '''下载文件''' if len(command.split()) > 1: filename = command.split()[1] file_path = self.current_path + r"\%s"%filename if os.path.isfile(file_path): #文件是否存在 self.conn.sendall("201".encode()) #命令可执行 file_size = os.stat(file_path).st_size # 文件总大小 status_code = self.conn.recv(1024).decode() # 客户端存在此文件 if status_code == "403": self.conn.sendall("000".encode()) #系统交互 has_send_size = self.conn.recv(1024).decode() has_send_size = int(has_send_size) # 客户端文件不完整可续传 if has_send_size < file_size: self.conn.sendall("205".encode()) file_size -= has_send_size #续传文件大小 response = self.conn.recv(1024) # 等待响应 # 客户端文件完整不可续传、不提供下载 else: self.conn.sendall("405".encode()) return # 客户端不存在此文件 elif status_code == "402": has_send_size = 0 with open(file_path,"rb") as file: self.conn.sendall(str(file_size).encode()) #发送文件大小 response = self.conn.recv(1024) #等待响应 file.seek(has_send_size) m = hashlib.md5() for line in file: m.update(line) self.conn.sendall(line) self.conn.sendall(m.hexdigest().encode()) #发送文件md5值 else:self.conn.sendall("402".encode()) else:self.conn.sendall("401".encode()) def put(self,command): '''上传文件''' filename = os.path.basename(command.split()[1]) print(filename) file_path = self.current_path + r"\%s" % filename print(file_path) self.conn.sendall("000".encode()) #发送确认 file_size = self.conn.recv(1024).decode() # 文件大小 file_size = int(file_size) limit_size = self.user_db["limitsize"] #磁盘额度 used_size = self.__getdirsize(self.home_path) #已用空间大小 if limit_size >= file_size+used_size: self.conn.sendall("202".encode()) with open(file_path, "wb") as file: # 开始接收 revice_size = 0 m = hashlib.md5() while revice_size < file_size: minus_size = file_size - revice_size if minus_size > 1024: size = 1024 else: size = minus_size data = self.conn.recv(size) revice_size += len(data) file.write(data) m.update(data) new_file_md5 = m.hexdigest() # 生成新文件的md5值 server_file_md5 = self.conn.recv(1024).decode() if new_file_md5 == server_file_md5: # md5值一致 self.conn.sendall("203".encode()) else:self.conn.sendall("404".encode()) def dir(self,command): '''查看当前目录下的文件''' if len(command.split()) == 1: self.conn.sendall("201".encode()) response = self.conn.recv(1024) send_data = os.popen("dir %s"%self.current_path) self.conn.sendall(send_data.read().encode()) else:self.conn.sendall("401".encode()) def pwd(self,command): '''查看当前用户路径''' if len(command.split()) == 1: self.conn.sendall("201".encode()) response = self.conn.recv(1024) send_data = self.current_path self.conn.sendall(send_data.encode()) else:self.conn.sendall("401".encode()) def mkdir(self,command): '''创建目录''' if len(command.split()) > 1: dir_name = command.split()[1] #目录名 dir_path = self.current_path + r"\%s"%dir_name #目录路径 if not os.path.isdir(dir_path): #目录不存在 self.conn.sendall("201".encode()) response = self.conn.recv(1024) os.popen("mkdir %s"%dir_path) else:self.conn.sendall("403".encode()) else:self.conn.sendall("401".encode()) def cd(self,command): '''切换目录''' if len(command.split()) > 1: dir_name = command.split()[1] #目录名 dir_path = self.current_path + r"\%s" %dir_name #目录路径 user_home_path = settings.HOME_PATH + r"\%s"%self.user_db["username"] #宿主目录 if dir_name == ".." and len(self.current_path)>len(user_home_path): self.conn.sendall("201".encode()) response = self.conn.recv(1024) self.current_path = os.path.dirname(self.current_path) #返回上一级目录 elif os.path.isdir(dir_path) : self.conn.sendall("201".encode()) response = self.conn.recv(1024) if dir_name != "." and dir_name != "..": self.current_path += r"\%s"%dir_name #切换目录 else:self.conn.sendall("402".encode()) else:self.conn.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