客户端代码:
import os import hashlib BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) FILE_PATH = os.path.join(BASE_DIR, 'donwload') def make_md5_on_file(file_path): '''给文件制作md5 每次都要打开文件,要想办法保存到一个位置,当文件发生修改时更新这个md5''' m = hashlib.md5() with open(file_path, 'rb') as f: for line in f: bytes_str = line m.update(bytes_str) md5value = m.hexdigest() return md5value
import socket import optparse import json import struct import os import sys import shelve BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR) from conf import settings class FTPClient(object): address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = True max_pack_size = 8192 request_queue_size = 5 coding = 'utf-8' donwload_dir = settings.FILE_PATH def __init__(self, connect=True): ''' elf.options 这是这个模块生成的一个字典 self.args 这是用户输入的命令。和sys.argv 差不多 如果用户按照规则输入,那么相应的值 就会在字典中显示, 不按规则输入的值,就放倒了列表中''' parser = optparse.OptionParser() parser.add_option("-s", "--server", dest='server', help="ftp server ip_addr") parser.add_option("-P", "--port", type="int", dest="port", help="ftp server port") parser.add_option("-u", "--username", dest="username", help="username info") parser.add_option("-p", "--password", dest="password", help="password info") self.options, self.args = parser.parse_args() self.current_dir = None self.argv_verification() # 判断合法性 self.socket = self.make_connection() # 建立连接 self.shelve_obj = shelve.open('defect_file') # 得到下载时shelve文件对象 self.shelve_load_obj = shelve.open('unfinish_file') if connect: try: self.client_connect() except Exception: self.client_close() exit('以断开连接') def argv_verification(self): '''判断用户输入的合法性''' if not self.options.server or not self.options.port: exit('必须提供,ip 和 端口') def make_connection(self): '''由构造函数调用生成 socket 对象''' self.socket = socket.socket(self.address_family, self.socket_type) return self.socket def client_connect(self): '''由构造函数直接调用,激活客户端,连接服务端''' self.socket.connect((self.options.server, self.options.port)) # def client_close(self): '''由构造函数调用,关闭客户端''' self.socket.close() def auth(self): '''输入账户名,密码。服务端验证完成后,根据接收到的状态码,返回 Ture or False''' count = 0 while count < 3: username = input('username:').strip() if not username: continue self.current_dir = '\' + username pwd = input('password:').strip() cmd = { 'action_type': 'auth', 'username': username, 'password': pwd } self.send_head(cmd) head_dic = self.recv_head() if head_dic['status_code'] == 200: return True else: count += 1 print(head_dic['status_msg']) continue def send_head(self, cmd): '''发送客户端的报头,''' client_head_bytes = json.dumps(cmd).encode(self.coding) client_head_len = struct.pack('i', len(client_head_bytes)) self.socket.send(client_head_len) self.socket.send(client_head_bytes) def recv_head(self): '''接收服务端发来的报头''' obj = self.socket.recv(4) header_size = struct.unpack('i', obj)[0] header_json = self.socket.recv(header_size).decode(self.coding) header_dict = json.loads(header_json) return header_dict def send_file(self, file_path, head_dic, sercver_dir, md5val, send_size=0): '''发送文件''' self.shelve_load_obj[file_path] = [head_dic, sercver_dir, md5val] genertor = self.progress_bar(head_dic['filesize']) genertor.__next__() with open(file_path, 'rb') as f: f.seek(send_size) for line in f: self.socket.send(line) send_size += len(line) genertor.send(send_size) else: del self.shelve_load_obj[file_path] print('数据发送成功') header_dict = self.recv_head() print(header_dict['status_msg']) def recv_file(self, file_path, file_size, md5_value, filename, recv_size=0): '''接收服务端文件数据, 并 保存所有文件信息, 防止程序中断时,可以进行续传, 文件正常传完, 删除文件描述信息, 并改名''' file_abs_path = os.path.join(r'%s\%s' % (self.current_dir, filename)) # 拼接客户端在那个位置下载的文件 self.shelve_obj[file_abs_path] = [file_size, filename+'.download'] # 以绝对路径为键,保存文件大小的值 with open(file_path, 'ab') as f: while recv_size < file_size: line = self.socket.recv(self.max_pack_size) f.write(line) recv_size += len(line) progres(recv_size, file_size) # 进度条 md5value = settings.make_md5_on_file(file_path) if md5value == md5_value: del self.shelve_obj[file_abs_path] if filename not in os.listdir(self.donwload_dir): os.renames(file_path, os.path.join(self.donwload_dir, filename)) else: print('文件重名,未覆盖!', end='') print() def put(self, *args): '''上传文件''' cmd = args[0] filename = args[1] sercver_dir = '%s\%s' % (self.current_dir, filename) if not os.path.isfile(os.path.join(self.donwload_dir, filename)): print('file %s 不在donwload文件中' % filename) return else: filesize = os.path.getsize(os.path.join(self.donwload_dir, filename)) md5value = settings.make_md5_on_file(os.path.join(self.donwload_dir, filename)) head_dic = {'action_type': cmd, 'filesize': filesize, 'filename': filename, 'md5value': md5value} file_path = os.path.join(self.donwload_dir, filename) self.send_head(head_dic) self.send_file(file_path, head_dic, sercver_dir, md5value) def re_put(self, file_path, load_head_dic, sercver_dir, md5value): server_head_dic = self.recv_head() received_size = server_head_dic['file_size'] self.send_file(file_path, load_head_dic, sercver_dir, md5value, send_size=received_size) def progress_bar(self, total_size): '''生成器 方式的, 进度条展示''' current_percent = 0 last_percent = 0 while True: recvived_size = yield current_percent current_percent = int(recvived_size / total_size * 100) if current_percent > last_percent: print("#" * int(current_percent/2) + "{percent}%".format(percent=current_percent), flush=True, end=' ') last_percent = current_percent def get(self, *args): '''下载文件''' cmd = args[0] filename = args[1] client_head_dic = {'action_type': cmd, 'filename': filename} self.send_head(client_head_dic) server_head_dic = self.recv_head() if server_head_dic['status_code'] == 300: print(server_head_dic['status_msg']) return file_path = os.path.join(self.donwload_dir, '%s.download' % filename) # 存放文件路径 total_size = server_head_dic['filesize'] # 文件最大大小 md5_val = server_head_dic['md5value'] # 得到服务端发来的 MD5 值 self.recv_file(file_path, total_size, md5_val, filename) def re_get(self, head_dic): '''由程序自检之后,用户选择 是否进行续传。 如果续传 调用该方法。 发送报头: 1. 检测 已收到文件大小 2. 编辑报头,使用 self.socket.send() 发送报头 3. 接收文件,''' self.send_head(head_dic) server_head_dic = self.recv_head() if server_head_dic['status_code'] == 300: print(server_head_dic['status_msg']) return file_path = os.path.join(self.donwload_dir, head_dic['filename']) # 存放文件路径 total_size = head_dic['total_size'] md5_val = server_head_dic['md5value'] # 得到服务端发来的 MD5 值 self.recv_file(file_path, total_size, md5_val, server_head_dic['filename'], head_dic['received_file_size']) def ls(self, *args): '''显示当前目录下的文件''' cmd = args[0] client_head_dic = {'action_type': cmd} self.send_head(client_head_dic) server_head_dic = self.recv_head() if server_head_dic['status_code'] == 0: print(server_head_dic['dir_info']) if server_head_dic['status_code'] == 100: print(server_head_dic['dir_info']) def cd(self, *args): '''切换目录''' cmd = args[0] dirname = args[1] if dirname != '.': client_head_dic = {'action_type': cmd, 'dirname': dirname} self.send_head(client_head_dic) server_head_dic = self.recv_head() if server_head_dic['status_code'] == 400: print(server_head_dic['status_msg']) elif server_head_dic['status_code'] == 401: print(server_head_dic['status_msg']) self.current_dir = server_head_dic['dirn'] elif server_head_dic['status_code'] == 0: self.current_dir = server_head_dic['dirn'] else: print('输入错误') return def help_msg(self, *args): msg = '''command error!!! Correct format: get filename 下载文件 put filename 上传文件 ls 显示当前所在目录下的文件和子目录 cd dirname 切换到那个目录下''' print(msg) def unfinished_file_check(self): if not self.shelve_obj: print('没有未接收的文件'.center(30, '-')) return for index, abs_file in enumerate(self.shelve_obj.keys()): self.received_file_size = os.path.getsize(os.path.join(self.donwload_dir, self.shelve_obj[abs_file][1])) print('文件编号 %d 服务器文件保存地址 %s 文件总大小%s 文件名%s 已收到文件大小%s' % (index, abs_file, self.shelve_obj[abs_file][0], self.shelve_obj[abs_file][1], self.received_file_size)) while True: choice = input('选择想要继续下载的文件编号 [back] 退出:').strip() if not choice: continue if choice == 'back': break if choice.isdigit(): choice = int(choice) if choice >=0 and choice <= index: selected_file = list(self.shelve_obj.keys())[choice] # 通过索引,拿到想要的那个文件的 key total_size = self.shelve_obj[selected_file][0] # 文件总大小 file_name = self.shelve_obj[selected_file][1] # 文件名 size = self.received_file_size # 已收到的文件大小 head_dic = {'action_type': 're_get', 'abs_file': selected_file, 'total_size': total_size, 'received_file_size': size, 'filename': file_name} self.re_get(head_dic) def unsend_by_file_check(self): if not self.shelve_load_obj: print('没有未上传完整的文件'.center(30, '-')) return for index, abs_file in enumerate(self.shelve_load_obj.keys()): print('%s %s %s' % (index, abs_file, self.shelve_load_obj[abs_file])) while True: choice = input('选择想要继续上传的文件编号 [back] 退出:').strip() if not choice: continue if choice == 'back': break if choice.isdigit(): choice = int(choice) if choice >= 0 and choice <= index: # file_path, head_dic, sercver_dir, md5val file_path = list(self.shelve_load_obj.keys())[choice] filesize = self.shelve_load_obj[file_path][0]['filesize'] load_head_dic = self.shelve_load_obj[file_path][0] sercver_dir = self.shelve_load_obj[file_path][1].strip('\') md5value = self.shelve_load_obj[file_path][2] head_dic = {'action_type': 're_put', 'abs_file': sercver_dir, 'md5value': md5value, 'filesize': filesize} self.send_head(head_dic) self.re_put(file_path, load_head_dic, sercver_dir, md5value) def interactive(self): # 交互函数 '''处理 与 服务端 的所有交互,解析用户输入的指令''' if self.auth(): self.unfinished_file_check() self.unsend_by_file_check() while True: inp = input('[%s] Q退出:' % self.current_dir).strip() if not inp: continue cmds = inp.split() if inp != 'Q': if hasattr(self, cmds[0]): func = getattr(self, cmds[0]) func(*cmds) else: self.help_msg() continue else: self.shelve_obj.close() self.client_close() exit('谢谢使用') def make_dir(self, *args): '''在自己的目录下,创建文件夹''' print('在自己的目录下,创建文件夹') def humanbytes(B): '''这传代码 抄来的 T_T ''' B = float(B) KB = float(1024) MB = float(KB ** 2) # 1,048,576 GB = float(KB ** 3) # 1,073,741,824 TB = float(KB ** 4) # 1,099,511,627,776 if B < KB: return '{0} {1}'.format(B,'Bytes' if 0 == B > 1 else 'Byte') elif KB <= B < MB: return '{0:.2f} KB'.format(B/KB) elif MB <= B < GB: return '{0:.2f} MB'.format(B/MB) elif GB <= B < TB: return '{0:.2f} GB'.format(B/GB) elif TB <= B: return '{0:.2f} TB'.format(B/TB) def progres(recv_size, total_size): bar_length = 50 percent = float(recv_size) / float(total_size) hashes = '=' * int(percent * bar_length) spaces = ' ' * (bar_length - len(hashes)) sys.stdout.write(" 传输中: [%s] %d%% %s/%s " % (hashes + spaces, percent * 100, humanbytes(recv_size), humanbytes(total_size))) sys.stdout.flush() if __name__ == '__main__': client = FTPClient() client.interactive()
服务端代码:
import os import sys BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 入口程序必须先 指定 基础目录。以便于其余的调用程序,可以通过这个路径,进行相对的导入 sys.path.append(BASE_DIR) # 将基础目录添加到,路径列表中 if __name__ == '__main__': from core import management # print() # ['luffy_server.py', 'start'] # sys.argv 把用户终端输入的命令,拿到并把拿到的列表,交给 解析命令的类。传给__init__了
#['luffy_server.py', 'start']
argv_parser = management.ManagementTool(sys.argv) argv_parser.execute()
import os import socket BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) HOST = "127.0.0.1" # 服务端IP 用于服务端进行绑定使用 PORT = 8080 # 服务端 端口 ADDRESS_FAMILY = socket.AF_INET SOCKET_TYPE = socket.SOCK_STREAM ALLOW_REUSE_ADDRESS = True MAX_PACKET_SIZE = 8192 MAX_SOCKET_LISTEN = 5 CODING = 'utf-8' # 指定编码类型 USER_HOME_DIR = os.path.join(BASE_DIR, 'home') ACCOUNT_FILE = os.path.join(BASE_DIR, 'conf', 'accounts.ini')
import hashlib import configparser from .import settings def make_md5_on_file(file_path): '''给文件制作md5 每次都要打开文件,要想办法保存到一个位置,当文件发生修改时更新这个md5''' m = hashlib.md5() with open(file_path, 'rb') as f: for line in f: bytes_str = line m.update(bytes_str) md5value = m.hexdigest() return md5value def load_all_user_info(): '''由FTPserver调用,加载所有的用户数据到内存''' confing = configparser.ConfigParser() confing.read(settings.ACCOUNT_FILE) return confing
from core import main class ManagementTool(object): '''负责对 用户输入的指令,进行解析。并调用相应的模块处理''' def __init__(self, sys_argv): self.sys_argv = sys_argv self.verification() def verification(self): '''由构造函数调用 验证用户输入的指令是否合法,如果不合法。打印帮助信息''' if len(self.sys_argv) < 2: # sys.argv 列表中默认会带上文件名,所以长度必须不能小于2 self.help_msg() cmd = self.sys_argv[1] if not hasattr(self, cmd): print('无效语法') self.help_msg() def help_msg(self): msg = ''' start start FTP server stop stop FTP server restart restart FTP server createuser username create a ftp user 其余功能还没扩展呢''' exit(msg) # 如果命令输入的是错误的,就退出程序。因为 如果继续向后走的话,会因为没有这个命令报错。 # 虽然可以,加上循环。让用户继续输入。不过 再来一次就好了。没必要那么麻烦 def execute(self): '''进行解析并执行指令,写在这里可以进行扩展''' cmd = self.sys_argv[1] func = getattr(self, cmd) func() # 这里并没有传参数,因为sys.argv 是构造函数中的,相当于类中的一个全局变量。 # 其余函数 直接调用就好了,不需要还要在这里传参数 def start(self): '''启动FTP server''' server = main.FTPServer(self) # FTPServer 有可能用到当前这个程序的一些东西,那么就把实例本身 server.run_forever() # 当作一个参数 传给FTPServer 那就可以在FTPServer中使用这个类中的属性了 def stop(self): '''这是停止服务''' print('停止服务了') def restart(self): '''重新启动服务器''' print('重新启动服务器') def createuser(self): '''管理员创建用户使用的''' print('管理员专用')
1 import socket 2 import json 3 import hashlib 4 import struct 5 import os 6 import subprocess 7 from conf import settings 8 from conf import config_tool 9 10 11 class FTPServer(object): 12 '''处理与客户端所有的交互的 socket server 13 所需要的参数,从settings 和 实例化时 传进来''' 14 address_family = settings.ADDRESS_FAMILY 15 socket_type = settings.SOCKET_TYPE 16 allow_reuse_address = settings.ALLOW_REUSE_ADDRESS # 是否重用端口开关 默认 Fales 17 max_packet_size = settings.MAX_PACKET_SIZE # 最大传输流量8192 18 coding = settings.CODING # utf-8 19 request_queue_size = settings.MAX_SOCKET_LISTEN # 最大挂起数量5 20 server_dir = settings.USER_HOME_DIR # 总家目录 21 22 STATUS_CODE = { 23 0: 'normal ', 24 100: 'ls Error info', 25 101: 'current dir has no file at all', 26 200: 'Username Password Authentication Successful!', 27 201: 'wrong username or password', 28 300: 'The file was not find', 29 301: 'find the file', 30 302: 'The server did not receive the complete data', 31 303: 'The server receive the complete data', 32 400: 'The dirname was not find', 33 401: 'Has entered this directory', 34 500: "It's already on the top floor" 35 } 36 37 def __init__(self, management_instance, bind_and_acttivate=True): # 在management类中start的位置,将management的实例传进来 38 '''构造函数,可扩展 不可覆盖''' 39 self.management_instance = management_instance # 类的构造函数中,接收这个参数,这样就能够使用到传进来的实例的对象中的属性 40 self.socket = socket.socket(self.address_family, self.socket_type) 41 self.config_obj = config_tool.load_all_user_info() # 42 if bind_and_acttivate: 43 try: 44 self.server_bind() 45 self.server_activate() 46 except Exception: 47 self.server_close() 48 49 def server_bind(self): 50 """由构造函数调用以绑定套接字""" 51 if self.allow_reuse_address: 52 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 53 self.socket.bind((settings.HOST, settings.PORT)) 54 self.server_address = self.socket.getsockname() 55 56 def server_activate(self): 57 """由构造函数调用以激活服务器 """ 58 self.socket.listen(self.request_queue_size) 59 60 def get_request(self): 61 """从套接字获取请求和客户机地址。 """ 62 return self.socket.accept() 63 64 def server_close(self): 65 """调用以清理服务器。""" 66 self.socket.close() 67 68 def close_request(self): 69 """调用以清除单个请求""" 70 self.request.close() 71 72 def run_forever(self): 73 '''启动socket server 运行到天荒地老''' 74 print('starting luffyFTP server on %s:%s'.center(50, '-') % (settings.HOST, settings.PORT)) 75 while True: 76 try: 77 self.request, self.client_addr = self.get_request() 78 print('from client %s' % (self.client_addr,)) 79 # print(self.request, self.cline_addr) 80 self.handle() 81 except Exception: 82 print('客户端 %s 断开连接' % (self.client_addr,)) 83 self.close_request() 84 85 def handle(self): 86 '''接收客户端发来的报头 解析之后 进行相应的操作''' 87 while True: 88 raw_data = self.request.recv(4) 89 if not raw_data: 90 print('client %s disconnection' % self.client_addr) 91 del self.request, self.client_addr 92 break 93 data_len = struct.unpack('i', raw_data)[0] 94 date_json = self.request.recv(data_len).decode(self.coding) 95 data_dic = json.loads(date_json) 96 action_type = data_dic.get('action_type') 97 if action_type: # 不能为空 98 if hasattr(self, "_%s" % action_type): 99 func = getattr(self, "_%s" % action_type) 100 func(data_dic) 101 102 def send_response(self, status_code, *args, **kwargs): 103 '''打包发送状态码消息给客户端''' 104 data = kwargs 105 data['status_code'] = status_code 106 data['status_msg'] = self.STATUS_CODE[status_code] 107 bytes_data = json.dumps(data).encode('utf-8') 108 head_struct = struct.pack('i', len(bytes_data)) 109 110 self.request.send(head_struct) # 这里时通信循环干的事情了,一定要是self.request 111 self.request.send(bytes_data) 112 113 def authentication(self, username, password): 114 '''对用户名密码进行验证''' 115 if username in self.config_obj: 116 _password = self.config_obj[username]['password'] 117 md5_obj = hashlib.md5() 118 md5_obj.update(password.encode('utf-8')) 119 if md5_obj.hexdigest() == _password: 120 return True 121 122 def _auth(self, data): 123 '''处理用户认证请求''' 124 if self.authentication(data.get('username'), data.get('password')): 125 self.home_dir = os.path.join(self.server_dir, data.get('username')) 126 self.userhome_dir = os.path.join(self.server_dir, data.get('username')) 127 self.send_response(status_code=200,) 128 else: 129 self.send_response(status_code=201) 130 131 def _put(self, data): 132 '''用户进行上传,''' 133 file_path = os.path.normpath(os.path.join(self.userhome_dir, data['filename'])) 134 filesize = data['filesize'] 135 cilent_md5 = data['md5value'] 136 result = self.recv_file(file_path, cilent_md5, filesize) 137 if result: 138 self.send_response(status_code=303) 139 else: 140 self.send_response(status_code=302) 141 142 def _get(self, data): 143 '''用户进行下载''' 144 file_path = os.path.normpath(os.path.join(self.userhome_dir, data['filename'])) 145 if not os.path.isfile(file_path): 146 self.send_response(status_code=300) 147 else: 148 filesize = os.path.getsize(file_path) 149 md5value = config_tool.make_md5_on_file(file_path) 150 self.send_response(status_code=301, filesize=filesize, md5value=md5value, filename=data['filename']) 151 self.send_file(file_path) 152 153 def recv_file(self, file_path, cilent_md5, filesize, recv_size=0): 154 '''接收文件,需要文件存放路径,MD5值,文件大小, 已接受的文件大小''' 155 156 with open(file_path, 'ab') as f: 157 while recv_size < filesize: 158 recv_data = self.request.recv(self.max_packet_size) 159 if not recv_data: break 160 f.write(recv_data) 161 recv_size += len(recv_data) 162 md5value = config_tool.make_md5_on_file(file_path) 163 if md5value == cilent_md5: 164 return True 165 166 def send_file(self, file_path, send_size=0): 167 with open(file_path, 'rb') as f: 168 f.seek(send_size) 169 for line in f: 170 self.request.send(line) 171 172 def _re_get(self, data): 173 abs_file = data['abs_file'].strip('\') 174 file_path = os.path.normpath(os.path.join(settings.USER_HOME_DIR, abs_file)) 175 if not os.path.isfile(file_path): 176 self.send_response(status_code=300) 177 if os.path.getsize(file_path) != data['total_size']: 178 self.send_response(status_code=300) 179 else: 180 md5value = config_tool.make_md5_on_file(file_path) 181 self.send_response(status_code=301, md5value=md5value, filename=os.path.basename(file_path)) 182 self.send_file(file_path, send_size=data['received_file_size']) 183 184 def _re_put(self, data): 185 '''用于用户上传,没有传完整,续传使用''' 186 abs_file = data['abs_file'].strip('\') 187 file_path = os.path.normpath(os.path.join(settings.USER_HOME_DIR, abs_file)) 188 md5value = data['md5value'] 189 190 if not os.path.isfile(file_path): 191 self.send_response(status_code=300) 192 else: 193 received_file_size = os.path.getsize(file_path) # 返回给客户端,已经收到了多少字节 194 self.send_response(status_code=301, file_size=received_file_size) 195 filesize = data['filesize'] - received_file_size 196 result = self.recv_file(file_path, md5value, filesize) 197 if result: 198 self.send_response(status_code=303) 199 else: 200 self.send_response(status_code=302) 201 202 def _ls(self, data): 203 '''给客户端显示当前文件下有哪些内容''' 204 cmd_boj = subprocess.Popen('dir %s' % self.userhome_dir, shell=True, stdout=subprocess.PIPE, 205 stderr=subprocess.PIPE) 206 stdout = cmd_boj.stdout.read() 207 stderr = cmd_boj.stderr.read() 208 cmd_result = (stdout + stderr).decode('GBK') 209 if not cmd_result: 210 self.send_response(status_code=101) 211 else: 212 self.send_response(status_code=0, dir_info=cmd_result) 213 214 def _cd(self, data): 215 '''进入用户想要进入的目录当中''' 216 dirname = data['dirname'] 217 if not dirname.startswith('.'): 218 if os.path.isdir(os.path.join(self.userhome_dir, dirname)): 219 self.userhome_dir = os.path.join(self.userhome_dir, dirname) 220 self.client_dir = self.userhome_dir.replace(settings.USER_HOME_DIR, '') 221 self.send_response(status_code=401, dirn=self.client_dir) 222 else: 223 self.send_response(status_code=400) 224 else: 225 li = dirname.split('\') 226 count = 0 227 while count < len(li): 228 '''循环 如果 当前路径 已经到用户的家目录,就返回500 已经到达最顶层''' 229 if self.userhome_dir == self.home_dir: 230 self.send_response(status_code=500) 231 else: 232 self.userhome_dir = os.path.dirname(self.userhome_dir) 233 self.client_dir = self.userhome_dir.replace(settings.USER_HOME_DIR, '') 234 self.send_response(status_code=0, dirn=self.client_dir) 235 count += 1 236 237 def _make_dir(self): 238 '''创建用户想要创建的文件夹''' 239 print('创建用户想要创建的文件夹')