今天为止,大多数的功能已经得到实现了,目前还缺三个部分内容,一个是账号注册问题(缺少configpraser模块知识,得抓紧看),二是上传和下载后文件一致性验证的问题,三是用户存储文件大小的限制。
但是目前已经可以基本实现一个网盘的功能,所有部分的代码如下:
bin文件:
ftp_server.py
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() #提供程序入口
mian.py
import socketserver import optparse from conf import settings 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','--p',dest = 'port') # 解析模块,暂时未使用到,是argv输入进来的参数以键值对形式保存,打印出来像字典,但是不是字典,通过.的方式调用 options,args = self.op.parse_args() # 假设在运行的初期输入的参数为 -s 127.0.0.1 -p 8080 # print(options) # {'server' : '127.0.0.1' , 'port' : 8080} # print(type(options)) # options 的数据类型是optparse的实例对象 # print(args) # 用于接收后面那些没有标识符作为前缀的参数 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('==========welcome ftp_server==========') s = socketserver.ThreadingTCPServer((settings.IP , settings.PORT),server.ServerHandler) s.serve_forever() print('.....')
server.py
import socketserver import json import configparser from conf import settings import os,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 ServerHandler(socketserver.BaseRequestHandler): def handle(self): while True: data = self.request.recv(1024).strip() data = json.loads(data.decode('utf-8')) ''' {'action' : 'auth' , 'username' : 'xiao', 'password' : 123 } ''' if data.get('action'): if hasattr(self,data.get('action')): func = getattr(self,data.get('action')) func(**data) else: print('func error') self.request.send('func error'.encode('utf-8')) else: print('Invalid cmd') self.request.send('Invalid cmd'.encode('utf-8')) def send_reponse(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_reponse(254) else: self.send_reponse(253) def authenticate(self,user,pwd): cfg = configparser.ConfigParser() cfg.read(settings.ACCOUNT_PATH) if user in cfg.sections(): if cfg[user]['password'] == pwd: self.user = user # 登录成功之后保存了用户的登录信息 self.mainPath = os.path.join(settings.BASE_DIR,'home',self.user) # print('passed authentication') # 表示登录成功 return user def put(self,**data): print('data',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: # 表示文件已存在,且比较完整,返回给客户端801的信息 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 as e: break f.write(data) has_received += len(data) f.close() def ls(self,**data): #相当于dos命中的dir,显示当前文件目录下的所有文件 file_list = os.listdir(self.mainPath) file_str = " ".join(file_list) # file_list 是一个列表的形式,通过.join方法让其自动的实现换行显示 if not len(file_list): # 表示如果该文件目录为空的情况 file_str = "<empty dir>" self.request.sendall(file_str.encode("utf8")) def cd(self,**data): dirname=data.get("dirname") if dirname=="..": self.mainPath=os.path.dirname(self.mainPath) else: self.mainPath=os.path.join(self.mainPath,dirname) self.request.sendall(self.mainPath.encode("utf8")) def mkdir(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("utf8")) else: self.request.sendall("dirname exist".encode("utf8"))
以上是服务端的主要内容,客户端部分信息明显要少很多
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" } # tcp_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # tcp_client.connect(('127.0.0.1',8080)) # tcp_client.send('ok'.encode('utf-8')) # 以上是easy模式下的 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 ): # 对port端口进行校验 server = options.server port = options.port # username = options.username # password = options.password if int(options.port) > 0 and 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))) print('已连接') def interractive(self): if self.authenticate(): print('==========begin to interactive==========') cmd_info = input('[%s]'%self.user).strip() #比如会输入 put 12.png images 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): # 传入命令: put 12.png images action, local_path, target_path = cmd_list local_path = os.path.join(self.mainPath , local_path) file_name = os.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.sendall(json.dumps(data).encode('utf-8')) is_exist = self.sock.recv(1024).decode('utf-8') # 下面对接收到的信息进行处理(转入服务端的书写) has_sent = 0 if is_exist == '800': # 表示文件存在不完整,即断点续传问题 choice = input('文件已存在,但文件不完整(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': # 表示文件已经存在,不用上传,所以就跳出put方法,重新转入输入命令那一行 # 即 interractive 方法 print("the file exist") return else: pass # 下面表示文件不存在的情况,就直接开始上传了 f = open(local_path, "rb") f.seek(has_sent) while has_sent < file_size: data = f.read(1024) self.sock.sendall(data) has_sent += len(data) self.show_progress(has_sent, file_size) f.close() print("put success!") def show_progress(self,has,total): rate = float(has)/float(total) rate_num = int(100*rate) # if self.last != rate_num sys.stdout.write("%s%% %s "%(rate_num,"-"*rate_num)) def authenticate(self): if self.options.username is None or self.options.password is None: username = input('username>>>') password = input('password>>>') print('ok') return self.get_auth_result(username,password) else: return self.get_auth_reult(self.options.username,self.options.password) def response(self): data = self.sock.recv(1024).decode('utf-8') data = json.loads(data) return data def get_auth_result(self,user ,pwd): data = { 'action' : 'auth', 'username' : user, 'password' : pwd } self.sock.sendall((json.dumps(data)).encode('utf-8')) response = self.response() print('response是',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['status_code']) def ls(self,*cmd_list): # 相当于dos命中的dir,显示当前文件目录下的所有文件 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("utf8")) data = self.sock.recv(1024).decode("utf8") print(os.path.basename(data)) self.current_dir = os.path.basename(data) ch = ClientHandler() ch.interractive()
其实里面有很多的重复代码可以进行提炼,这个初级版本也是一边看视频一边写出来的,有很多不完善的地方,亟需改进
下一步,自己晾个一两天,然后自己试一试根据自己的分析进行编写,如果还是弄不出来,就得好好抄写几遍了。