
正文
用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 mainif __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 = 8080ACCOUNT_PATH = os.path.join(BASE_DIR,"conf","accounts.cfg.txt")
主程序文件core/main
import os,sysimport optparse #optparse模块用来给脚本传递命令参数,并用预定设置好的选项来解析传过来的参数
import socketserver
from conf import setting
from core import serverclass 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 osSTATUS_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("".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("".encode("utf-8"))
return #文件存在,不做操作,返回最初等待接收状态 else:
self.request.sendall("".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 sysSTATUS_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 == "": #文件不完整
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 == "": #文件已存在
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()







