用Python编写一个ftb
程序文件结构
具体代码实现
服务端:
执行文件bin/ftb_server
import os,sys PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #os.path.dirname()取文件的上一级目录路径 sys.path.append(PATH) #导入的路径不在同一文件夹下,需要自己添加环境变量 from core import main if __name__ == "__main__": main.ArgvHandler()
配置文件conf/setting
import os,sys 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.txt")
主程序文件core/main
import os,sys import optparse #optparse模块用来给脚本传递命令参数,并用预定设置好的选项来解析传过来的参数 import socketserver from conf import setting from core import server class ArgvHandler: def __init__(self): self.op = optparse.OptionParser() #实例化 # self.op.add_option("-s","--server",dest="server") # self.op.add_option("-P","--port",dest="port") #用于增加设定输入的内容对应的关系 options,args = self.op.parse_args() # print(options) #将上面增加的设定做键值对应后封装成一个对象 # print(options.server) #调用这个对象中的属性 # print(args) #当输入的的值超出add_option的设置,把这些值放在一个列表中 self.verify_args(options,args) def verify_args(self,options,args): cmd = args[0] if hasattr(self,cmd): func = getattr(self,cmd) func() def start(self): print("the server is working..") s = socketserver.ThreadingTCPServer((setting.IP,setting.PORT),server.ServerHandler) s.serve_forever()
服务端程序core/server
import socketserver import json import configparser from conf import setting import os STATUS_CODE = { 250:"Invalid cmd format,e.g:{'action':'get','filename':'test.py','size':344}", 251:"Invalid cmd", 252:"Invalid auth data", 253:"Wrong username or password", 254:"Passed authentication", 255:"Filename doesn'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 ServerHandler(socketserver.BaseRequestHandler): def handle(self): while True: data = self.request.recv(1024).strip() data = json.loads(data.decode("utf-8")) """ {"action":"auth", "usename":"ss", "pwd":123 } """ if data.get("action"): #对接收的内容进行验证 if hasattr(self,data.get("action")): func = getattr(self,data.get("action")) #验证通过执行action键对应的函数 func(**data) else: print("Invalid cmd") else: print("not") def send_response(self,state_code): response = {"status_code":state_code} #将状态码回传给客户端 self.request.sendall(json.dumps(response).encode("utf-8")) def auth(self,**data): username = data["username"] password = data["password"] user = self.authenticate(username,password) if user: #将客户端传输的数据与数据库中的内容匹配判断,根据结果回传状态码 self.send_response(254) else: self.send_response(253) def authenticate(self,user,pwd): cfg = configparser.ConfigParser() #调用数据库 cfg.read(setting.ACCOUNT_PATH) #读取数据库中的内容 if user in cfg.sections(): if cfg[user]["password"] == pwd: self.user = user #将得到的用户名赋值给self.user,这样其他的函数也可以用这个用户名 self.mainPath = os.path.join(setting.BASE_DIR,"home",self.user) #将用户的路径拼接出来 return user #具体操作命令 def put(self,**data): print(data) file_name = data.get("file_name") file_size = data.get("file_size") target_path = data.get("target_path") abs_path = os.path.join(self.mainPath,target_path,file_name) #文件上传到哪个路径下,拼接出绝对路径 has_received = 0 if os.path.exists(abs_path): file_has_size = os.stat(abs_path).st_size if file_has_size < file_size: #文件已存在,但大小小于传过来的文件大小,断点续传 self.request.sendall("800".encode("utf-8")) choice = self.request.recv(1024).decode("utf-8") if choice == "Y": #选择续传 self.request.sendall(str(file_has_size).encode(("utf-8"))) #回传已有多少字节 has_received += file_has_size #将已有字节长度赋值给初始值 f = open(abs_path,"ab") #再在已有基础上写入 else: f = open(abs_path,"wb") #不续传,重新写入 else: self.request.sendall("801".encode("utf-8")) return #文件存在,不做操作,返回最初等待接收状态 else: self.request.sendall("802".encode("utf-8")) f = open(abs_path,"wb") while has_received < file_size: #判断接收到数据是否小于传输过来的文件大小,小于则继续传输 try: data = self.request.recv(1024) except Exception: break f.write(data) has_received += len(data) f.close() def ls(self,**data): file_list = os.listdir(self.mainPath) #列出当前目录下的所有文件 file_str = "\n".join(file_list) if not len(file_list): file_str = "<empty dir>" self.request.sendall(file_str.encode("utf-8")) def cd(self,**data): dirname = data.get("dirname") if dirname == "..": self.mainPath = os.path.dirname(self.mainPath) self.mainPath = os.path.join(self.mainPath,dirname) self.request.sendall(self.mainPath.encode("utf-8")) def makdir(self,**data): dirname = data.get("dirname") path = os.path.join(self.mainPath,dirname) if not os.path.exists(path): if "/" in dirname: os.makedirs(path) else: os.mkdir(path) self.request.sendall("create success".encode("utf-8")) else: self.request.sendall("dirname exist".encode("utf-8"))
客户端:
主程序ftb_client
import socket import optparse import configparser import json import os import sys STATUS_CODE={ 250:"Invalid cmd format,e.g:{'action':'get','filename':'test.py','size':344}", 251:"Invalid cmd", 252:"Invalid auth data", 253:"Wrong username or password", 254:"Passed authentication", 255:"Filename doesn'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(): def __init__(self): self.op = optparse.OptionParser() #实例化 self.op.add_option("-s","--server",dest="server") #设定输入的参数按照什么样的格式解析 self.op.add_option("-P","--port",dest="port") self.op.add_option("-u","--username",dest="username") self.op.add_option("-p","--password",dest="password") self.options,self.args = self.op.parse_args() #得到的第一个值是一个对象,是将输入的参数按照设定格式解析的键值对,第二个值是列表,包含未被设定格式的参数 self.verify_args(self.options,self.args) self.make_connection() self.mainPath = os.path.dirname(os.path.abspath(__file__)) self.last = 0 def verify_args(self,options,args): server = options.server port = options.port username = options.username password = options.password if int(port)<65535: return True else: exit("the port is in 0-65535") def make_connection(self): #客户端套接字流程 self.sock = socket.socket() self.sock.connect((self.options.server,int(self.options.port))) def interactive(self): if self.authenticate(): #判断是否验证成功 print("begin to interactive...") while True: cmd_info = input("[%s]" %self.current_dir).strip() #用户访问成功之后输入需要执行的操作 cmd_list = cmd_info.split() #将用户输入的内容分隔 if hasattr(self,cmd_list[0]): func = getattr(self,cmd_list[0]) func(*cmd_list) #具体命令操作 def put(self,*cmd_list): action,local_path,target_path = cmd_list local_path = os.path.join(self.mainPath,local_path) #将上传的文件的本地路径与自定义的mainPath目录拼接 file_name = os.path.basename(local_path) #basename取local_path路径下的文件名 file_size = os.stat(local_path).st_size #文件大小 data = { "action":"put", "file_name":file_name, "file_size":file_size, "target_path":target_path } self.sock.send(json.dumps(data).encode("utf-8")) is_exist = self.sock.recv(1024).decode("utf-8") #接收服务端返回的消息 has_sent = 0 if is_exist == "800": #文件不完整 choice = input("the file exist,but not enough,is continue?[Y/N]").strip() if choice.upper() == "Y": self.sock.sendall("Y".encode("utf-8")) continue_position = self.sock.recv(1024).decode("utf-8") has_sent += int(continue_position) else: self.sock.sendall("N".encode("utf-8")) elif is_exist == "801": #文件已存在 return else: pass f = open(local_path,"rb") f.seek(has_sent) #将光标移动到多少个字节处 while has_sent < file_size: #文件传输 data = f.read(1024) self.sock.send(data) has_sent += len(data) self.show_progress(has_sent,file_size) f.close() def show_progress(self,has,total): rate = float(has)/float(total) rate_num = int(rate * 100) if self.last != rate_num: sys.stdout.write("%s%% %s\r" %(rate_num,"#"*rate_num)) self.last = rate_num def ls(self,*cmd_list): data = { "action":"ls" } self.sock.sendall(json.dumps(data).encode("utf-8")) data = self.sock.recv(1024).decode("utf-8") print(data) def cd(self,*cmd_list): data = { "action":"cd", "dirname":cmd_list[1] } self.sock.sendall(json.dumps(data).encode("utf-8")) data = self.sock.recv(1024).decode("utf-8") self.current_dir = os.path.basename(data) def mkdir(self,*cmd_list): data = { "action":"mkdir", "dirname":cmd_list[1] } self.sock.send(json.dumps(data).encode("utf-8")) self.sock.recv(1024).decode("utf-8") def authenticate(self): #判断是否输入,再进行传输 if self.options.username is None or self.options.password is None: username = input("username:") password = input("password:") return self.get_auth_result(username,password) return self.get_auth_result(self.options.username,self.options.password) def response(self): #接收服务端回应的消息 data = self.sock.recv(1024).decode("utf-8") data = json.loads(data) #接收的状态码是字典格式{"status_code":state_code} return data def get_auth_result(self,user,pwd): #将数据传输到服务端 data = { "action":"auth", "username":user, "password":pwd } self.sock.send(json.dumps(data).encode("utf-8")) response = self.response() print(response["status_code"]) if response["status_code"] == 254: #根据回传的状态码判断是否验证正确 self.user = user self.current_dir = user print(STATUS_CODE[254]) return True #验证成功返回 else: print(STATUS_CODE[response["status_code"]]) ch = ClientHandler() ch.interactive()