socketserver+socket实现较为复杂的ftp,支持多用户在线
客户端(ftp_client.py)
1 import socketserver,json,hashlib,os 2 from pymongo import MongoClient 3 4 ''' 5 *****要点***** 6 1.面向对象编程 7 2.反射的利用 8 3.代码的高度解耦 9 4.md5加密传输认证 10 5.数据库查询验证 11 ************** 12 ''' 13 db = MongoClient('localhost', 27017) 14 curent_document = '' 15 #此类用于处理一系列客户端请求 16 class deal(): 17 def __init__(self,name): 18 self.name = name 19 20 #用户登录认证 21 def deal_login(self,data,client): 22 global logined_uname,db 23 BASE_DIR = ''#基目录 24 #选择数据库 25 d = db['DB_FOR_PYTHON'] 26 #选择集合 27 users = d['FTP_USERS'] 28 name = data['name'] 29 pwd = data['pwd'] 30 u = users.find_one({'name':name ,'pwd':pwd}) 31 #用户不存在,返回0 32 if u == None or u == '': 33 #使用find()和find_one返回的结果分别是cursor和一条记录 34 u1 = users.find_one({'name':name}) 35 if u1 == None or u1 == '': 36 #无此用户 37 client.send(b'0') 38 return '0'.encode() 39 else: 40 #密码有误 41 client.send(b'2') 42 return '2'.encode() 43 else: 44 #登录成功 45 client.send(b'1') 46 logined_uname = name 47 return '1'.encode() 48 49 #上传处理 50 def deal_upload(self,data,request): 51 #查出用户的磁盘配额大小,并检查用户上传文件大小,和要上传的文件加以对比,看是否可以继续上传 52 user1 = db.DB_FOR_PYTHON.FTP_USERS.find_one({'name':logined_uname}) 53 #用户的磁盘配额大小 54 space_limite = user1['space'] 55 print('当前用户磁盘总空间:{}'.format(str(space_limite))) 56 name = data['name'] 57 #上传文件大小 58 totalsize = data['size']*1024 59 m = hashlib.md5() 60 request.send('ok'.encode()) 61 cur_size = 0 62 cur_content =b'' 63 size = 0 64 global BASE_DIR 65 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 66 file_name = '' 67 #剩余磁盘大小 68 space = 0 69 u_dir = BASE_DIR+os.sep+'USERS_HOME'+os.sep+logined_uname.upper()+"_HOME" 70 if os.path.exists(u_dir): 71 # 用户总目录文件夹大小(bit计算) 72 real_space = (os.stat(u_dir).st_size)*1024 73 space = space_limite - real_space 74 file_name = u_dir+os.sep+name 75 else: 76 os.mkdir(u_dir) 77 space = space_limite 78 file_name = u_dir + os.sep + name 79 #如果上传资源的大小小于等于磁盘剩余空间,则继续 80 if space >= totalsize: 81 print('可以发送') 82 f = open(file_name,'wb') 83 while cur_size < totalsize: 84 if totalsize - cur_size >1024*1024: 85 size = 1024*1024 86 else: 87 size = totalsize - cur_size 88 print(size) 89 data = request.recv(size) 90 m.update(data) 91 cur_content += data 92 f.write(data) 93 cur_size += len(data)*1024 94 else: 95 print('haha') 96 f.close() 97 request.send(json.dumps({'state':'upload success!','md5':m.hexdigest()}).encode()) 98 return json.dumps({'state':'upload success!','md5':m.hexdigest()}).encode() 99 else: 100 request.send(json.dumps({'state':'upload fail,your filesize exceeding the rest of your own space!','rest':space,'filesize':totalsize,'md5':None}).encode()) 101 return json.dumps({'state':'upload fail,your filesize exceeding the rest of your own space!','rest':space,'filesize':totalsize,'md5':None}).encode() 102 #下载实现 103 def deal_download(self,data): 104 self.data = data['name'] 105 return self.data 106 #用户切换目录 107 def deal_cd(self,data): 108 global BASE_DIR,curent_document 109 #当前用户基目录 110 ROOT_DOC = BASE_DIR+os.sep+'USERS_HOME'+os.sep+logined_uname.upper()+"_HOME" 111 current_document = ROOT_DOC 112 #用户输入的目录 113 manual_enter = data['cd_document'] 114 current_document = os.chdir(manual_enter) if os.path.exists(manual_enter) else curent_document 115 print("用户要跳转的目录:",current_document) 116 pass 117 #查看文件 118 def deal_dir(self): 119 pass 120 #断点续传功能 121 def deal_cont(self): 122 pass 123 #面向对象的方式实现 124 class ftp_server(socketserver.BaseRequestHandler): 125 def handle(self): 126 logined_uname = '' 127 while True: 128 try: 129 #接收来自客户端的信息 130 data = self.request.recv(1024).decode() 131 data = json.loads(data) 132 print(data) 133 cmd = data['req'] 134 #创建服务端对象 135 d = deal('ser') 136 if hasattr(d,cmd): 137 c = getattr(d,cmd) 138 res = c(data,self.request).decode() 139 print(res) 140 except ConnectionResetError as e: 141 print('error:',e) 142 break 143 144 #主方法入口 145 if __name__ == '__main__': 146 HOST,PORT = 'localhost',9999 147 server = socketserver.ThreadingTCPServer((HOST,PORT),ftp_server) 148 server.serve_forever()
服务端(ftp_server.py)
1 # coding = utf-8 2 3 import socket,json,re,os,hashlib 4 login_uname = '' 5 class client_do(object): 6 def __init__(self,name): 7 self.name = name 8 #登录 9 def deal_login(self,data,client): 10 try: 11 global login_uname 12 global state 13 name = data.split()[1].split('\\')[0] 14 pwd = data.split()[1].split('\\')[1] 15 req = data.split()[0] 16 data = { 17 'req':req, 18 'name':name, 19 'pwd':pwd 20 } 21 client.send(json.dumps(data).encode()) 22 response = client.recv(1024).decode() 23 if response == '1': 24 login_uname = name 25 global lg_s 26 lg_s = '\033[32;1m $' + login_uname + '>>:\033[0m' 27 state = response 28 #返回结果可能为0(无此用户),1(验证成功),2(密码有误) 29 return response 30 except Exception as e: 31 print("登录出错:",e) 32 #上传 33 def deal_upload(self,mes,client): 34 try: 35 if state == '1': 36 filename = mes.split()[1] 37 if os.path.isfile(filename): 38 f = open(filename, 'rb') 39 filesize = os.stat(filename).st_size 40 print('filesize:', filesize) 41 data = { 42 'req': mes.split()[0], 43 'name': filename, 44 'size': filesize, 45 'overwrite': True 46 } 47 client.send(json.dumps(data).encode()) 48 # 接受服务器返回的信息,防止粘包,同时得到磁盘是否够传信息 49 res = client.recv(1024) 50 print('first response:', res.decode()) 51 # 准备发送信息 52 m = hashlib.md5() 53 for line in f: 54 client.send(line) 55 m.update(line) 56 md5_client = m.hexdigest() 57 f.close() 58 # 接受服务器请求处理结果 59 response_info = client.recv(1024).decode() 60 response_info = json.loads(response_info) 61 md5_server = response_info['md5'] 62 print('md5_client:{},\t md5_server:{}'.format(md5_client, md5_server)) 63 print("状态:{}".format(response_info['state'])) 64 #上传ok 65 return '1' 66 else: 67 #上传出错 68 return '0' 69 else: 70 print('请先完成登录') 71 except Exception as e: 72 print('上传出错:',e) 73 #用户切换目录 74 def deal_cd(self): 75 pass 76 #查看文件 77 def deal_dir(self): 78 pass 79 #断点续传功能 80 def deal_cont(self): 81 pass 82 client = socket.socket() 83 client.connect(('localhost',9999)) 84 state = 0 85 lg_s =">>:" 86 while True: 87 mes = input(lg_s) 88 if mes == '' or mes ==None:continue 89 cl_req = mes.split()[0] 90 c = client_do('client') 91 if hasattr(c,cl_req): 92 s = getattr(c,cl_req) 93 res = s(mes,client) 94 else: 95 print('无效的命令')
说明:
代码实现的功能有用户登录认证,上传的前提是要登录,文件上传,用户磁盘配额,断点续传和目录跳转功能暂未实现。后续实现补上
运行截图:
1.数据库截图:
2.客户端截图:
3.服务端截图: