心得:
1.要实现多用户同时登陆,需要使用多进程,不能使用多线程,因为多线程数据是共享的,在用户切换目录的时候,会出现问题,而多进程由于数据是隔离的,就没有这个问题。
2.切换目录和删除文件的时候,需要检查要切换或删除的目录是否超出了用户的家目录。需要用到
user_full_home = os.path.abspath(os.path.join(MyServer.base_dir, user_home))
file_abs_path = os.path.abspath(os.path.join(MyServer.base_dir, now_path, filename))
if os.path.exists(filename) and user_full_home in file_abs_path:
pass
3.以下代码是有服务器压力的问题的,进程启动的越多,对服务器的压力会越大,所以最好的办法是启用多线程,思路:客户端虽然都有自己的家目录,但是服务端可以通过路径拼接的方式,而不是真正的os.chdir到具体的目录里,可解决此问题。
FTP/Readme.md
### 新增功能 - pwd 查看当前路径 - ls 查看当前目录下的文件和文件夹 - rm file 删除文件 - mkdir folder 新建文件夹 - touch filename 新建文件 - cd folder 进入指定目录 - cd .. 进入上一目录 - quit 退出 ### 执行顺序 - 先执行FTP_Server/ftp_server.py - 再执行FTP_Server/ftp_client.py ### 测试账号 - 用户名 lily 密码 123456 - 注册或登录后,可执行上传文件和下载命令 - put a.mp4 - 上传a.mp4 - a.mp4提前放到FTP_Client目录下 - put 1.jpg - 上传1.jpg - 1.jpg提前放到FTP_Client目录下 - get a.mp4 - 下载a.mp4 - a.mp4会下载到FTP_Client/download目录下 - get 1.jpg - 下载1.jpg - 1.jpg会下载到FTP_Client/download目录下 ### 流程 - 注册流程  - 登录流程  ###FTP项目开发 - 功能: - 多用户同时登陆 - 上传/下载文件 - 传输过程中现实进度条 - 不同用户使用自己的目录 - 每个用户拥有1G的磁盘空间 - 充分使用面向对象知识 - 思路 - 先完成客户端用户注册功能 - 注册时检查用户名s是否存在请求 ```python {'action': '_user_isexists', 'username': 'aaa', 'result': 0} ``` - 注册时检查用户名是否存在响应(不存在) ```python {'action': '_user_isexists', 'username': 'aaa', 'result': 0} ``` - 注册时检查用户名是否存在响应(存在) ```python {'action': '_user_isexists', 'username': 'aaa', 'result': 1} ``` - 注册提交数据 ```python {'action': '_register', 'result': 0, 'data': {'username': 'aaa', 'password': '123456', 'quotaSize': '50M', 'userHome': 'home/aaa'}} ``` - 注册成功返回响应 ```python {'action': '_register', 'result': 1, 'data': {'username': 'aaa', 'quotaSize': '50M', 'userHome': 'home/aaa'}} ``` - 再完成用户登录功能 - 请求 ```python {'action': '_login', 'username': 'lily', 'password': 'aaa', 'result': 0} ``` - 响应 ```python {'action': '_login', 'username': 'lily', 'password': '123456', 'result': 1, 'userHome': 'home/lily', 'quota_size': '50M'} ``` - 用户注册和登录时有自己的家目录和磁盘空间大小 - 注册时,用户名 密码 默认磁盘空间50M 注册或登录成功后默认进入家目录 - 用户信息保存在FTP_Server/db/user.json - 格式 ```python {"username": "lily", "password": "1822219a532beacb9615e36dcaba3a6c", "quotaSize": "50M", "userHome": "home/lily"} ``` - 实现客户端上传和下载文件的功能 - 注册或登录成功后,自动进入用户的家目录 - 上传文件 - put 文件名 - 客户端判断文件是否存在 - 客户端获取文件名和文件大小 - 服务端判断用户磁盘是否够用 ```python {'action': '_file_upload', 'file_name': 'dydt.png', 'file_size': 39542, 'quota_size': '50M', 'user_home': 'home/lily'} ``` - 一切检查就绪,可以上传文件 ```python {"result": 1} # 上传完成 ``` - 将FTP_Client下的文件上传到用户的家目录 - 下载文件 - get 文件名 - 客户端发送要下载的文件消息 - 服务端收到消息,拿到文件名,判断文件是否存在,存在则下载,不存在返回 - 将用户家目录里的文件下载到FTP_Client/download/目录下 - 上传文件的时候,需要判断磁盘空间是否满了 - 上传和下载文件的时候,有进度条
FTP/reg.png
FTP/login.png
FTP/FTP_Server/ftp_server.py
import sys import os import struct import json import hashlib class MyServer(): base_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, base_dir) db_file = os.path.join(base_dir, "db/user.json") def __init__(self, conn): self.request = conn def talk(self): while 1: dic = self.recv_msg() print("start", dic) if dic.get('action', "not_find").upper() == "_QUIT": self.request.close() break elif hasattr(self, dic.get('action', "not_find")): fn = getattr(self, dic.get('action', "not_find")) if callable(fn): fn(dic) else: print(fn) else: self.printC("方法没找到!", "red") def _register(self, dic): """ 用户注册 :param dic: :return: """ user_dic = dic.get("data") user_home = user_dic.get("userHome") if not os.path.exists(user_home): os.makedirs(user_home) password = user_dic.get("password") user_dic["password"] = MyServer.get_md5(password) user_dic_json = json.dumps(user_dic) with open(MyServer.db_file, "a", encoding="utf-8") as f: f.write(f"{user_dic_json} ") dic["result"] = 1 os.chdir(user_home) del dic["data"]["password"] print(dic) self.send_msg(dic) def _login(self, dic): username = dic.get("username") password = dic.get("password") password = MyServer.get_md5(password) with open(MyServer.db_file, "r", encoding="utf-8") as f: for line in f: line = line.strip() user_dic = json.loads(line) if user_dic.get("username") == username and user_dic.get("password") == password: dic["result"] = 1 dic["userHome"] = user_dic.get("userHome") dic["quota_size"] = user_dic.get("quotaSize") print("now_path:", os.getcwd()) os.chdir(dic["userHome"]) print(dic) del dic["password"] self.send_msg(dic) def _user_isexists(self, dic): username = dic.get("username") with open(MyServer.db_file, "r", encoding="utf-8") as f: for line in f: line = line.strip() user_dic = json.loads(line) if user_dic.get("username") == username: dic["result"] = 1 print(dic) self.send_msg(dic) def send_msg(self, dic): """ 发送信息 :param dic: :return: """ dic_json = json.dumps(dic) dic_json_bs = dic_json.encode("utf-8") self.request.send(struct.pack("i", len(dic_json_bs))) self.request.send(dic_json_bs) def recv_msg(self): """ 接收消息 :return: """ recv_msg_head = self.request.recv(4) recv_msg_len = struct.unpack("i", recv_msg_head)[0] print(recv_msg_len) recv_msg_content = self.request.recv(recv_msg_len).decode("utf-8") return json.loads(recv_msg_content) def _file_upload(self, dic): """ 文件上传 :param dic: :return: """ file_name = dic.get("file_name") file_size = dic.get("file_size") if dic.get("quota_size").endswith("M"): quota_size = int(dic.get("quota_size")[:-1]) * 1024 * 1024 else: quota_size = int(dic.get("quota_size")) * 1024 * 1024 used_quota_size = MyServer.getPathSize("./") if quota_size > used_quota_size + file_size: self.send_msg({"space": 1}) f = open(file_name, "wb") while file_size > 0: recv_msg = self.request.recv(1024) f.write(recv_msg) file_size -= len(recv_msg) else: self.send_msg({"result": 1}) # 上传完成 else: self.send_msg({"space": 0}) # 空间不足 def _file_download(self, dic): file_name = dic.get("filename") if os.path.exists(file_name) and os.path.isfile(file_name): file_size = os.path.getsize(file_name) self.send_msg({"exists": 1, "file_size": file_size}) with open(file_name, "rb") as f: for line in f: self.request.send(line) print("文件下载完成!") else: self.send_msg({"exists": 0}) def _get_pwd(self, dic): now_path = os.getcwd() dic["now_path"] = now_path.replace(MyServer.base_dir, "") self.send_msg(dic) def _get_list(self, dic): dic["file_list"] = os.listdir() self.send_msg(dic) def _remove_file(self, dic): filename = dic.get("filename") user_home = dic.get("user_home") now_path = dic.get("now_path") user_full_home = os.path.abspath(os.path.join(MyServer.base_dir, user_home)) file_abs_path = os.path.abspath(os.path.join(MyServer.base_dir, now_path, filename)) print("user_full_home", user_full_home) print("file_abs_path", file_abs_path) if os.path.exists(filename) and user_full_home in file_abs_path: if os.path.isfile(filename): os.remove(filename) dic["result"] = 1 else: if MyServer.isempty(filename) == 0: os.removedirs(filename) dic["result"] = 1 else: dic["result"] = "notempty" else: dic["result"] = "notexists" self.send_msg(dic) def _make_dir(self, dic): folder = dic.get("folder") if os.path.exists(folder): dic["result"] = "isexists" else: os.mkdir(folder) dic["result"] = 1 self.send_msg(dic) def _touch_file(self, dic): filename = dic.get("filename") if os.path.exists(filename): dic["result"] = "isexists" else: open(filename, "w", encoding="utf-8").close() dic["result"] = 1 self.send_msg(dic) def _change_path(self, dic): file_path = dic.get("file_path") user_home = dic.get("user_home") now_path = dic.get("now_path") user_full_home = os.path.abspath(os.path.join(MyServer.base_dir, user_home)) new_abs_path = os.path.abspath(os.path.join(MyServer.base_dir, now_path, file_path)) if os.path.exists(file_path): if file_path.startswith(".."): if user_full_home in new_abs_path: os.chdir(file_path) dic["result"] = 1 else: dic["result"] = "path_notexists" elif file_path == "/": os.chdir(os.path.join(MyServer.base_dir, user_home)) dic["result"] = 1 else: os.chdir(file_path) dic["result"] = 1 now_path = os.getcwd() dic["now_path"] = now_path.replace(MyServer.base_dir, "") else: dic["result"] = "path_notexists" print(dic) self.send_msg(dic) @staticmethod def getPathSize(path): """ 统计目录的总大小 """ total_size = 0 gen = os.walk(path) # 拿到一个生成器 for root, dirs, files in gen: for d in dirs: dir_path = os.path.join(root, d) total_size += os.path.getsize(dir_path) print(dir_path) for f in files: file_path = os.path.join(root, f) total_size += os.path.getsize(file_path) print(file_path) return total_size @staticmethod def isempty(path): """ 判断目录是否为空 """ total = 0 gen = os.walk(path) # 拿到一个生成器 for root, dirs, files in gen: for d in dirs: total += 1 for f in files: total += 1 return total @staticmethod def get_md5(str1): md5_obj = hashlib.md5("我是盐,你想咋滴".encode("utf-8")) md5_obj.update(str1.encode("utf-8")) return md5_obj.hexdigest() def printC(self, str1, color="black"): '''添加颜色输出 color: red:红色 gre:绿色 yel:黄色 ''' col_type = 30 if color == "red": col_type = 31 elif color == "gre": col_type = 32 elif color == "yel": col_type = 33 print("