基于socketsever下实现的FTP
1 # ### 客户端client 2 import socket 3 import json 4 import struct 5 import os 6 7 sk = socket.socket() 8 sk.connect( ("127.0.0.1" , 9000) ) 9 10 11 def myrecv(info_len = 1024,sign=False): 12 # 要考虑黏包 13 if sign: 14 # (1) 先接受发送数据的实际长度 15 info_len = sk.recv(4) # 1 16 # unpack 返回的是元组(1999,) 17 info_len = struct.unpack("i",info_len)[0] 18 19 # (2) 接受服务端返回的数据 20 file_info = sk.recv(info_len).decode() # 2 21 file_dic = json.loads(file_info) 22 return file_dic 23 24 25 26 # 处理收发数据的逻辑 27 def auth(opt): 28 """ 29 opt: login 登录 or register 注册 30 """ 31 usr = input("username: ").strip() 32 pwd = input("password: ").strip() 33 dic = {"user":usr,"pwd":pwd,"operate":opt} 34 str_dic = json.dumps(dic) 35 # 把数据发送给服务端 36 sk.send(str_dic.encode()) 37 return myrecv() 38 39 40 # 1.注册 41 # res = auth("register") 42 # print(res) 43 44 def register(): 45 res = auth("register") 46 return res 47 48 # 2.登录 49 # res = auth("login") 50 # print(res) 51 def login(): 52 res = auth("login") 53 return res 54 55 # 3.退出 56 def myexit(): 57 opt_dic = {"operate":"myexit"} 58 sk.send(json.dumps(opt_dic).encode()) 59 exit("欢迎下次再来 ... ") 60 # myexit() 61 62 # 4.下载 63 def download(): 64 operate_dict = { 65 "operate":"download", 66 "filename":"ceshivideo.mp4" 67 } 68 69 opt_str = json.dumps(operate_dict) 70 sk.send(opt_str.encode("utf-8")) 71 72 # (1) 为了避免黏包,接受两次数据 73 res = myrecv(sign=True) 74 print(res) # {'result': True, 'info': '改文件存在'} 75 if res["result"]: 76 # 创建文件夹 77 try: 78 os.mkdir("mydownload") 79 except: 80 pass 81 82 # (2) 接受文件名字 和 文件大小 83 dic = myrecv(sign=True) 84 print(dic,"<2>") # {'filename': 'ceshivideo.mp4', 'filesize': 54822153} <2> 85 86 # (3) 接受文件数据 87 with open("./mydownload/" + dic["filename"],mode="wb") as fp: 88 while dic["filesize"]: 89 content = sk.recv(1024) 90 fp.write(content) 91 dic["filesize"] -= len(content) 92 print("客户端下载完毕") 93 else: 94 print("文件不存在") 95 96 97 98 99 # 第一套界面 100 operate_lst1 = [ ("登录",login) , ("注册",register) ,("退出",myexit)] 101 # 第二套界面 102 operate_lst2 = [("下载",download),("退出",myexit)] 103 """ 104 ( 1, ('登录', <function login at 0x00000234890402F0>) ) 105 (2, ('注册', <function register at 0x0000023489040268>)) 106 (3, ('退出', <function myexit at 0x0000023489040378>)) 107 """ 108 def main(operate_lst): 109 for i,tup in enumerate(operate_lst,start=1): 110 print(i,tup[0]) 111 112 num = int(input("请选择要执行的号码:>>>").strip()) 113 # 调用函数 114 res = operate_lst[num-1][1]() # login() 115 # 返回结果 116 return res 117 118 119 120 # 循环调用1号操作界面 121 while True: 122 # 开启第一套操作界面 123 res = main(operate_lst1) 124 print(res) 125 # 开启第二套操作界面 126 if res["result"]: 127 while True: 128 res = main(operate_lst2) 129 print(res) 130 131 sk.close()
1 # ### 服务端 server 2 import socketserver 3 import json 4 import os 5 import hashlib 6 import struct 7 8 # print(os.getcwd()) 9 print(__file__) 10 print(os.path.dirname(__file__)) 11 base_path = os.path.dirname(__file__) 12 13 # 获取当前账户文件所在的完整绝对路径 14 userinfo = os.path.join(base_path,"db","userinfo.txt") 15 print(userinfo) 16 """""" 17 class Auth(): 18 19 @staticmethod 20 def md5(usr,pwd): 21 md5_obj = hashlib.md5(usr.encode()) 22 md5_obj.update(pwd.encode()) 23 return md5_obj.hexdigest() 24 25 # 注册 26 @classmethod 27 def register(cls,opt_dic) : 28 # opt_dic = {'user': 'lisi', 'pwd': '222', 'operate': 'register'} 29 # 1.监测数据库当中是否存在该用户 30 with open(userinfo,mode="r",encoding="utf-8") as fp: 31 for line in fp: 32 username = line.split(":")[0] 33 if username == opt_dic["user"]: 34 return {"result":False,"info":"用户名已经存在了"} 35 36 # 2.当前这个用户允许注册 37 with open(userinfo,mode="a+",encoding="utf-8") as fp: 38 # 用户名:密码 换行 39 fp.write("%s:%s\n" % (opt_dic["user"] , cls.md5(opt_dic["user"],opt_dic["pwd"]))) 40 41 """ 42 当用户上传文件的时候,动态给用户创建文件夹 43 """ 44 45 # 3.直接返回对应的状态 46 return {"result":True,"info":"注册成功"} 47 48 # 登录 49 @classmethod 50 def login(cls,opt_dic): 51 # opt_dic = {'user': 'lisi', 'pwd': '222', 'operate': 'register'} 52 with open(userinfo,mode="r",encoding="utf-8") as fp: 53 for line in fp: 54 username,password = line.strip().split(":") 55 if username == opt_dic["user"] and password == cls.md5(opt_dic["user"],opt_dic["pwd"]): 56 return {"result":True,"info":"登陆成功"} 57 return {"result":False,"info":"登录失败"} 58 59 # 退出 60 @classmethod 61 def myexit(cls,opt_dic): 62 return {"result":"myexit"} 63 64 class FTPServer(socketserver.BaseRequestHandler): 65 def handle(self): 66 while True: 67 # 接受服务端发送过来的数据 68 opt_dic = self.myrecv() 69 print(opt_dic) 70 71 # 判断类当中是否含有register这个方法,如果有就反射 72 if hasattr(Auth,opt_dic["operate"]): 73 # getattr 反射出对应的register函数 74 res = getattr(Auth,opt_dic["operate"])(opt_dic) 75 76 # 判断是myexit,代表退出 77 if res["result"] == "myexit": 78 return 79 80 # 把返回值发送给客户端 81 self.mysend(res) 82 83 # 等待处理用户第二套操作界面的逻辑 84 if res["result"]: 85 while True: 86 opt_dic = self.myrecv() 87 print(opt_dic) 88 89 # 如果operate接受的是myexit 直接终止handle函数; 90 if opt_dic["operate"] == "myexit": 91 return 92 93 # 可以反射 类 或者 对象 或者 模块响应的属性和方法 94 if hasattr(self,opt_dic["operate"]): 95 getattr(self,opt_dic["operate"])(opt_dic) 96 97 98 # 如果Auth当中没有该操作的应急处理 99 else: 100 dic = {"result":False,"info":"没有该操作"} 101 self.mysend(dic) 102 103 104 105 106 # 接受数据的方法 107 def myrecv(self): 108 info = self.request.recv(1024) 109 opt_str = info.decode() 110 opt_dic = json.loads(opt_str) 111 return opt_dic 112 113 # 发送数据的方法 114 def mysend(self,send_info,sign=False): 115 # 先把字典变成字符串,然后变成字节流发送出去 116 send_info = json.dumps(send_info).encode() 117 118 # 增加代码 119 if sign: 120 # 通过pack转化成居右固定4个字节长度的字节流 121 res = struct.pack("i",len(send_info)) 122 # 把接下来要发送的数据长度发送给客户端 123 self.request.send(res) # 1 124 125 # 发送出去 126 self.request.send(send_info) # 2 127 128 # 下载方法 129 def download(self,opt_dic): 130 # {'operate': 'download', 'filename': 'ceshivideo.mp4'} 131 # print(opt_dic) 132 # 先判断是否存在该文件 133 filename = opt_dic["filename"] 134 file_abs = os.path.join(base_path,"myvideo",filename) 135 # print(filename) # ceshivideo.mp4 136 # print(file_abs) # D:/gongxiang8/day31/ftp\myvideo\ceshivideo.mp4 137 138 if os.path.exists(file_abs): 139 # 1.先告诉用户,可以操作,存在该文件 140 dic = {"result":True,"info":"改文件存在"} 141 self.mysend(dic,sign=True) 142 143 # 2.把对应的文件名,以及文件的大小发过去 144 filesize = os.path.getsize(file_abs) 145 dic = {"filename":filename,"filesize":filesize} 146 self.mysend(dic,sign=True) 147 148 149 # 3.真正开始发送文件内容 150 with open(file_abs,mode="rb") as fp: 151 while filesize: 152 # 一次性最多读取1024个字节 153 content = fp.read(1024) 154 self.request.send(content) 155 filesize -= len(content) 156 print("服务器下载完毕") 157 158 else: 159 dic = {"result":False,"info":"改文件不存在"} 160 self.mysend(dic,True) 161 162 163 164 165 166 167 168 myserver = socketserver.ThreadingTCPServer( ("127.0.0.1" , 9000) , FTPServer) 169 # 让一个端口绑定多个程序(测试时使用) 170 socketserver.TCPServer.allow_reuse_address = True 171 myserver.serve_forever()
zsir 日常分享