开发一个支持多用户在线的FTP程序
一,项目题目:开发一个支持多用户在线的FTP程序
二,项目要求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 1. 用户加密认证 2. 允许同时多用户登录 3. 每个用户有自己的家目录 ,且只能访问自己的家目录 4. 对用户进行磁盘配额,每个用户的可用空间不同 5. 允许用户在ftp server上随意切换目录 6. 允许用户查看当前目录下文件 7. 允许上传和下载文件,保证文件一致性(md5) 8. 文件传输过程中显示进度条 9. 附加功能:支持文件的断点续传 |
三,注意事项:
1 2 3 | 基本要求. 完成 1 , 2 , 3 , 5 , 6 , 7 , 8 实力选手. 完成 上条 及需求 4 , 大神操作. 完成 9 且项目目录结构良好、代码逻辑清晰, |
四,项目分析:
1,用户加密认证
1 2 3 4 | 这个肯定需要用到configparser 和hashlib模块,用md5进行加密,服务端与用户端 进行交互前,肯定需要进行认证,在服务端进行认证,客户端需要发送用户名及密码,但 是为了安全起见,服务端数据库中的密码应该是加密后的密文,客户端登陆认证时也应该 发送密文到服务端,服务端接受到密文与数据库中对应的密文进行比较。 |
2,查看自己的当前目录下的文件
1 2 | 这个只需要写一个 dir 就ok 简单的说,使用configparse模块就可以完成 |
3,文件传输中显示进度条
1 2 3 | 下载的进度条比较好实现,我们可以从服务端受到将要下载的文件的大小, 上传的进度条,我们可以利用文件操作的tell()方法,获取当前指针位置(字节) |
4,小编的主要思路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | - 1 对于此项目,最初的想法是写出上传,和下载文件的程序,包括客户端和服务端。 - 2 在此基础上扩展程序,包括提出开始程序到 bin 里面,配置文件在config里面 - 3 然后把上传文件和下载文件的程序进行断点续传的程序重构 - 4 在此基础上,对文件进行加密 - 5 增加功能,包括设置进度条,增加查看功能,增加目录功能,删除文件功能,切换目录功能等 - 6 然后再设置磁盘分配功能,完善内容 - 7 然后添加用户登陆,包括对用户的密码加密等功能 - 8 写完后检查程序 |
五,项目流程图
六,README文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | ## 作者:zhanzhengrecheng ## 版本:示例版本 v0.1 ## 程序介绍: - 实现了支持多用户在线的FTP程序 常用功能 - 功能全部用python的基础知识实现,用到了socket\hashlib\configparse\os\sys\pickle\函数\模块\类知识 ## 概述 本次作业文件夹一共包含了以下 5 个文件: - 流程图: FTP_homework思路流程图 - 程序结构图:整个FTP_homework的程序文件结构 - 程序结构文件:整个FTP_homework的程序文件结构 - 程序文件: FTP_homework - 程序说明文件:README.md ## 程序要求 - 1. 用户加密认证 - 2. 允许同时多用户登录 - 3. 每个用户有自己的家目录 ,且只能访问自己的家目录 - 4. 对用户进行磁盘配额,每个用户的可用空间不同 - 5. 允许用户在ftp server上随意切换目录 - 6. 允许用户查看当前目录下文件 - 7. 允许上传和下载文件,保证文件一致性(md5) - 8. 文件传输过程中显示进度条 - 9. 附加功能:支持文件的断点续传 ## 本项目思路 - 1 对于此项目,最初的想法是写出上传,和下载文件的程序,包括客户端和服务端。 - 2 在此基础上扩展程序,包括提出开始程序到 bin 里面,配置文件在config里面 - 3 然后把上传文件和下载文件的程序进行断点续传的程序重构 - 4 在此基础上,对文件进行加密 - 5 增加功能,包括设置进度条,增加查看功能,增加目录功能,删除文件功能,切换目录功能等 - 6 然后再设置磁盘分配功能,完善内容 - 7 然后添加用户登陆,包括对用户的密码加密等功能 - 8 写完后检查程序 ##### 备注(程序结构) > 目前还不会把程序树放在README.md里面,所以做出程序结构的txt版本和图片版本,放在文件外面方便查看 ## 对几个实例文件的说明 ### 几个实例文件全是为了上传和下载使用,自己随便找的素材 ## 不足及其改进的方面 ### 每次程序从用户登陆到使用只能完成一次功能,不能重复使用 ## 程序结构 │ FTP_homework │ __init__.py │ ├─client # 客户端程序入口 │ │ __init__.py │ ├─ bin # 可执行程序入口目录 │ │ run.py │ │ __init__.py │ ├─config # 配置文件目录 │ │ │ settings.py # 配置文件 │ │ │ __init__.py │ ├─core # 主要逻辑程序目录 │ │ │ ftp_client.py # client端主程序模块 │ │ │ __init__.py │ ├─download # 下载内容模块 │ │ a.jpg │ │ a.txt │ │ c.mp4 │ └─upload # 上传内容模块 │ a.txt │ aa.avi └─server # 服务端程序入口 ├─ bin │ run.py # 可执行程序入口目录 │ __init__.py ├─config # 配置文件目录 │ │ accounts.ini # 账号密码配置文件 │ │ settings.py # 配置文件 │ │ __init__.py ├─core # 主要逻辑程序目录 │ │ ftp_server.py # server端主程序模块 │ │ main.py # 主程序模块 │ │ user_handle.py # 用户注册登录模块 └─home # 家目录 │ __init__.py ├─curry # curry用户的家目录 │ │ aa.avi │ └─test └─james # james用户的家目录 │ a.jpg │ aa.avi │ c.mp4 └─test1 |
七,程序结构图
八,程序代码
1,server
1.1 bin
run.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # _*_ coding: utf-8 _*_ import os import sys BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR) from core import ftp_server from core import main from config import settings if __name__ = = '__main__' : a = main.Manager() a.interactive() |
1.2config
settings.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # _*_ coding: utf-8 _*_ import os import sys import socket BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR) ACCOUNTS_FILE = os.path.join(BASE_DIR, 'config' , 'accounts.ini' ) address_family = socket.AF_INET socket_type = socket.SOCK_STREAM BIND_HOST = '127.0.0.1' BIND_PORT = 9999 ip_port = (BIND_HOST,BIND_PORT) coding = 'utf-8' listen_count = 5 max_recv_bytes = 8192 allow_reuser_address = False |
1.3core
ftp_server.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 | # _*_ coding: utf-8 _*_ import socket import struct import json import os import pickle import subprocess import hashlib from config import settings from core.user_handle import UserHandle class FTPServer(): def __init__( self ,server_address,bind_and_listen = True ): self .server_address = server_address self .socket = socket.socket(settings.address_family,settings.socket_type) if bind_and_listen: try : self .server_bind() self .server_listen() except Exception: self .server_close() def server_bind( self ): allow_reuse_address = False if allow_reuse_address: self .socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) self .socket.bind( self .server_address) def server_listen( self ): self .socket.listen(settings.listen_count) def server_close( self ): self .socket.close() def server_accept( self ): return self .socket.accept() def conn_close( self , conn): conn.close() def getfile_md5( self ): '''获取文件的md5''' return hashlib.md5( self .readfile()).hexdigest() def readfile( self ): '''读取文件,得到文件内容的bytes类型''' with open ( self .file_path, 'rb' ) as f: filedata = f.read() return filedata def send_filedata( self ,exist_file_size = 0 ): """下载时,将文件打开,send(data)""" with open ( self .file_path, 'rb' ) as f: f.seek(exist_file_size) while True : data = f.read( 1024 ) if data: self .conn.send(data) else : break def get( self , cmds): ''' 下载,首先查看文件是否存在,然后上传文件的报头大小,上传文件,以读的方式发开文件 找到下载的文件 发送 header_size 发送 header_bytes file_size 读文件 rb 发送 send(line) 若文件不存在,发送0 client提示:文件不存在 :param cmds: :return: ''' if len (cmds) > 1 : filename = cmds[ 1 ] self .file_path = os.path.join(os.getcwd(), filename) if os.path.isfile( self .file_path): file_size = os.path.getsize( self .file_path) obj = self .conn.recv( 4 ) exist_file_size = struct.unpack( 'i' , obj)[ 0 ] header = { 'filename' : filename, 'filemd5' : self .getfile_md5(), 'file_size' : file_size } header_bytes = pickle.dumps(header) self .conn.send(struct.pack( 'i' , len (header_bytes))) self .conn.send(header_bytes) if exist_file_size: # 表示之前被下载过 一部分 if exist_file_size ! = file_size: self .send_filedata(exist_file_size) else : print ( '\033[31;1mbreakpoint and file size are the same\033[0m' ) else : # 文件第一次下载 self .send_filedata() else : print ( '\033[31;1merror\033[0m' ) self .conn.send(struct.pack( 'i' , 0 )) else : print ( "\033[31;1muser does not enter file name\033[0m" ) def recursion_file( self , dir ): """递归查询用户目录下的所有文件,算出文件的大小""" res = os.listdir( dir ) for i in res: path = os.path.join( dir ,i) if os.path.isdir(path): self .recursion_file(path) elif os.path.isfile(path): self .home_bytes_size + = os.path.getsize(path) def current_home_size( self ): """得到当前用户目录的大小,字节/M""" self .home_bytes_size = 0 self .recursion_file( self .homedir_path) home_m_size = round ( self .home_bytes_size / 1024 / 1024 , 1 ) def put( self ,cmds): """从client上传文件到server当前工作目录下 """ if len (cmds) > 1 : obj = self .conn.recv( 4 ) state_size = struct.unpack( 'i' , obj)[ 0 ] if state_size = = 0 : print ( "\033[31;1mfile does not exist!\033[0m" ) else : # 算出了home下已被占用的大小self.home_bytes_size self .current_home_size() header_bytes = self .conn.recv(struct.unpack( 'i' , self .conn.recv( 4 ))[ 0 ]) header_dic = pickle.loads(header_bytes) filename = header_dic.get( 'filename' ) file_size = header_dic.get( 'file_size' ) file_md5 = header_dic.get( 'file_md5' ) self .file_path = os.path.join(os.getcwd(),filename) if os.path.exists( self .file_path): self .conn.send(struct.pack( 'i' , 1 )) has_size = os.path.getsize( self .file_path) if has_size = = file_size: print ( "\033[31;1mfile already does exist!\033[0m" ) self .conn.send(struct.pack( 'i' , 0 )) else : print ( '\033[31;1mLast file not finished,this time continue\033[0m' ) self .conn.send(struct.pack( 'i' , 1 )) if self .home_bytes_size + int (file_size - has_size)> self .quota_bytes: print ( '\033[31;1mSorry exceeding user quotas\033[0m' ) self .conn.send(struct.pack( 'i' , 0 )) else : self .conn.send(struct.pack( 'i' , 1 )) self .conn.send(struct.pack( 'i' , has_size)) with open ( self .file_path, 'ab' ) as f: f.seek(has_size) self .write_file(f, has_size, file_size) self .verification_filemd5(file_md5) else : self .conn.send(struct.pack( 'i' , 0 )) print ( '\033[31;1mSorry file does not exist now first put\033[0m' ) if self .home_bytes_size + int (file_size) > self .quota_bytes: print ( '\033[31;1mSorry exceeding user quotas\033[0m' ) self .conn.send(struct.pack( 'i' , 0 )) else : self .conn.send(struct.pack( 'i' , 1 )) with open ( self .file_path, 'wb' ) as f: recv_size = 0 self .write_file(f, recv_size, file_size) self .verification_filemd5(file_md5) else : print ( "\033[31;1muser does not enter file name\033[0m" ) def write_file( self ,f,recv_size,file_size): '''上传文件时,将文件内容写入到文件中''' while recv_size < file_size: res = self .conn.recv(settings.max_recv_bytes) f.write(res) recv_size + = len (res) self .conn.send(struct.pack( 'i' , recv_size)) # 为了进度条的显示 def verification_filemd5( self ,filemd5): # 判断文件内容的md5 if self .getfile_md5() = = filemd5: print ( '\033[31;1mCongratulations download success\033[0m' ) self .conn.send(struct.pack( 'i' , 1 )) else : print ( '\033[31;1mSorry download failed\033[0m' ) self .conn.send(struct.pack( 'i' , 0 )) def ls( self ,cmds): '''查看当前工作目录下,先返回文件列表的大小,在返回查询的结果''' print ( "\033[34;1mview current working directory\033[0m" ) subpro_obj = subprocess.Popen( 'dir' ,shell = True , stdout = subprocess.PIPE, stderr = subprocess.PIPE) stdout = subpro_obj.stdout.read() stderr = subpro_obj.stderr.read() self .conn.send(struct.pack( 'i' , len (stdout + stderr))) self .conn.send(stdout) self .conn.send(stderr) def mkdir( self ,cmds): '''增加目录 在当前目录下,增加目录 1.查看目录名是否已经存在 2.增加目录成功,返回 1 2.增加目录失败,返回 0''' print ( "\033[34;1madd working directory\033[0m" ) if len (cmds) > 1 : mkdir_path = os.path.join(os.getcwd(),cmds[ 1 ]) if not os.path.exists(mkdir_path): os.mkdir(mkdir_path) print ( '\033[31;1mCongratulations add directory success\033[0m' ) self .conn.send(struct.pack( 'i' , 1 )) else : print ( "\033[31;1muser directory already does exist\033[0m" ) self .conn.send(struct.pack( 'i' , 0 )) else : print ( "\033[31;1muser does not enter file name\033[0m" ) def cd( self ,cmds): '''切换目录 1.查看是否是目录名 2.拿到当前目录,拿到目标目录, 3.判断homedir是否在目标目录内,防止用户越过自己的home目录 eg: ../../.... 4.切换成功,返回 1 5.切换失败,返回 0''' print ( "\033[34;1mSwitch working directory\033[0m" ) if len (cmds) > 1 : dir_path = os.path.join(os.getcwd(),cmds[ 1 ]) if os.path.isdir(dir_path): #os.getcwd 获取当前工作目录 previous_path = os.getcwd() #os.chdir改变当前脚本目录 os.chdir(dir_path) target_dir = os.getcwd() if self .homedir_path in target_dir: print ( '\033[31;1mCongratulations switch directory success\033[0m' ) self .conn.send(struct.pack( 'i' , 1 )) else : print ( '\033[31;1mSorry switch directory failed\033[0m' ) # 切换失败后,返回到之前的目录下 os.chdir(previous_path) self .conn.send(struct.pack( 'i' , 0 )) else : print ( '\033[31;1mSorry switch directory failed,the directory is not current directory\033[0m' ) self .conn.send(struct.pack( 'i' , 0 )) else : print ( "\033[31;1muser does not enter file name\033[0m" ) def remove( self ,cmds): """删除指定的文件,或者空文件夹 1.删除成功,返回 1 2.删除失败,返回 0 """ print ( "\033[34;1mRemove working directory\033[0m" ) if len (cmds) > 1 : file_name = cmds[ 1 ] file_path = os.path.join(os.getcwd(),file_name) if os.path.isfile(file_path): os.remove(file_path) self .conn.send(struct.pack( 'i' , 1 )) elif os.path.isdir(file_path): # 删除空目录 if not len (os.listdir(file_path)): os.removedirs(file_path) print ( '\033[31;1mCongratulations remove success\033[0m' ) self .conn.send(struct.pack( 'i' , 1 )) else : print ( '\033[31;1mSorry remove directory failed\033[0m' ) self .conn.send(struct.pack( 'i' , 0 )) else : print ( '\033[31;1mSorry remove directory failed\033[0m' ) self .conn.send(struct.pack( 'i' , 0 )) else : print ( "\033[31;1muser does not enter file name\033[0m" ) def get_recv( self ): '''从client端接收发来的数据''' return pickle.loads( self .conn.recv(settings.max_recv_bytes )) def handle_data( self ): '''处理接收到的数据,主要是将密码转化为md5的形式''' user_dic = self .get_recv() username = user_dic[ 'username' ] password = user_dic[ 'password' ] md5_obj = hashlib.md5() md5_obj.update(password) check_password = md5_obj.hexdigest() def auth( self ): ''' 处理用户的认证请求 1,根据username读取accounts.ini文件,然后查看用户是否存在 2,将程序运行的目录从bin.user_auth修改到用户home/username方便之后查询 3,把客户端返回用户的详细信息 :return: ''' while True : user_dic = self .get_recv() username = user_dic[ 'username' ] password = user_dic[ 'password' ] md5_obj = hashlib.md5(password.encode( 'utf-8' )) check_password = md5_obj.hexdigest() user_handle = UserHandle(username) # 判断用户是否存在 返回列表, user_data = user_handle.judge_user() if user_data: if user_data[ 0 ][ 1 ] = = check_password: self .conn.send(struct.pack( 'i' , 1 )) # 登录成功返回 1 self .homedir_path = os.path.join(settings.BASE_DIR, 'home' ,username) # 将程序运行的目录名修改到 用户home目录下 os.chdir( self .homedir_path) # 将用户配额的大小从M 改到字节 self .quota_bytes = int (user_data[ 2 ][ 1 ]) * 1024 * 1024 user_info_dic = { 'username' :username, 'homedir' :user_data[ 1 ][ 1 ], 'quota' :user_data[ 2 ][ 1 ] } # 用户的详细信息发送到客户端 self .conn.send(pickle.dumps(user_info_dic)) return True else : self .conn.send(struct.pack( 'i' , 0 )) # 登录失败返回 0 else : self .conn.send(struct.pack( 'i' , 0 )) # 登录失败返回 0 def server_link( self ): print ( "\033[31;1mwaiting client .....\033[0m" ) while True : # 链接循环 self .conn, self .client_addr = self .server_accept() while True : # 通信循环 try : self .server_handle() except Exception: break self .conn_close( self .conn) def server_handle( self ): '''处理与用户的交互指令''' if self .auth(): print ( "\033[32;1m-------user authentication successfully-------\033[0m" ) res = self .conn.recv(settings.max_recv_bytes) # 解析命令,提取相应的参数 cmds = res.decode(settings.coding).split() if hasattr ( self , cmds[ 0 ]): func = getattr ( self , cmds[ 0 ]) func(cmds) |
main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | # _*_ coding: utf-8 _*_ from core.user_handle import UserHandle from core.ftp_server import FTPServer from config import settings class Manager(): ''' 主程序,包括启动server,创建用户,退出 :return: ''' def start_ftp( self ): '''启动server端''' server = FTPServer(settings.ip_port) server.server_link() server.close() def create_user( self ): '''创建用户,执行创建用户的类''' username = input ( "\033[32;1mplease input your username>>>\033[0m" ).strip() UserHandle(username).add_user() def logout( self ): ''' 退出登陆 :return: ''' print ( "\033[32;1m-------Looking forward to your next login-------\033[0m" ) exit() def interactive( self ): '''交互函数''' msg = '''\033[32;1m 1 启动ftp服务端 2 创建用户 3 退出 \033[0m''' menu_dic = { "1" : 'start_ftp' , "2" : 'create_user' , "3" : 'logout' , } exit_flag = False while not exit_flag: print (msg) user_choice = input ( "Please input a command>>>" ).strip() if user_choice in menu_dic: getattr ( self ,menu_dic[user_choice])() else : print ( "\033[31;1myou choice doesn't exist\033[0m" ) |
user_handle.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | # _*_ coding: utf-8 _*_ import configparser import hashlib import os from config import settings class UserHandle(): ''' 创建用户名称,密码 如果用户存在,则返回,如果用户不存在,则注册成功 ''' def __init__( self ,username): self .username = username self .config = configparser.ConfigParser() self .config.read(settings.ACCOUNTS_FILE) @property def password( self ): '''生成用户的默认密码 ''' password_inp = input ( "\033[32;1mplease input your password>>>\033[0m" ).strip() md5_obj = hashlib.md5() md5_obj.update(password_inp.encode()) md5_password = md5_obj.hexdigest() return md5_password @property def disk_quota( self ): '''生成每个用户的磁盘配额''' quota = input ( '\033[32;1mplease input Disk quotas>>>:\033[0m' ).strip() if quota.isdigit(): return quota else : exit( '\033[31;1mdisk quotas must be integer\033[0m' ) def add_user( self ): """创建用户,存到accounts.ini""" if not self .config.has_section( self .username): print ( '\033[31;1mcreating username is :%s \033[0m' % self .username) self .config.add_section( self .username) self .config. set ( self .username, 'password' , self .password) self .config. set ( self .username, 'homedir' , 'home/' + self .username) self .config. set ( self .username, 'quota' , self .disk_quota) with open (settings.ACCOUNTS_FILE, 'w' ) as f: self .config.write(f) os.mkdir(os.path.join(settings.BASE_DIR, 'home' , self .username)) # 创建用户的home文件夹 print ( '\033[1;32msuccessfully create userdata\033[0m' ) else : print ( '\033[1;31musername already existing\033[0m' ) def judge_user( self ): """判断用户是否存在""" if self .config.has_section( self .username): return self .config.items( self .username) |
2,client
2.1bin
run.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # _*_ coding: utf-8 _*_ import os import sys BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR) from core import ftp_client from config import settings if __name__ = = '__main__' : run = ftp_client.FTPClient(settings.ip_port) run.execute() |
2.2config
settings.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # _*_ coding: utf-8 _*_ import os import sys import socket BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR) # 下载的文件存放路径 down_filepath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'download' ) # 上传的文件存放路径 upload_filepath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'upload' ) #绑定的IP地址 BIND_HOST = '127.0.0.1' #绑定的端口号 BIND_PORT = 9999 ip_port = (BIND_HOST,BIND_PORT) address_family = socket.AF_INET socket_type = socket.SOCK_STREAM coding = 'utf-8' listen_count = 5 max_recv_bytes = 8192 allow_reuser_address = False |
2.3core
ftp_client.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | # _*_ coding: utf-8 _*_ import socket import struct import json import os import sys import pickle import hashlib from config import settings class FTPClient: def __init__( self ,server_address,connect = True ): self .server_address = server_address self .socket = socket.socket(settings.address_family,settings.socket_type) if connect: try : self .client_connect() except Exception: self .client_close() def client_connect( self ): try : self .socket.connect( self .server_address) except Exception as e: print ( "\033[31;1merror:%s\033[0m" % e) exit( "\033[31;1m\nThe server is not activated \033[0m" ) def client_close( self ): self .socket.close() def readfile( self ): '''读取文件''' with open ( self .file_path, 'rb' ) as f: filedata = f.read() return filedata def appendfile_content( self ,file_path,temp_file_size,file_size): '''追加文件内容''' with open (file_path, 'ab' ) as f: f.seek(temp_file_size) get_size = temp_file_size while get_size < file_size: res = self .socket.recv(settings.max_recv_bytes) f.write(res) get_size + = len (res) self .progress_bar( 1 ,get_size,file_size) #1表示下载 def getfile_md5( self ): '''对文件内容进行加密,也就是保持文件的一致性''' md5 = hashlib.md5( self .readfile()) print ( "md5是:\n" ,md5.hexdigest()) return md5.hexdigest() def progress_bar( self ,num,get_size,file_size): float_rate = float (get_size) / float (file_size) rate_num = round (float_rate * 100 , 2 ) if num = = 1 : #1表示下载 sys.stdout.write( '\033[31;1m\rfinish downloaded perentage:{0}%\033[0m' . format (rate_num)) elif num = = 2 : #2表示上传 sys.stdout.write( '\033[31;1m\rfinish uploaded perentage:{0}%\033[0m' . format (rate_num)) sys.stdout.flush() def recv_file_header( self ,header_size): """接收文件的header, filename file_size file_md5""" header_types = self .socket.recv(header_size) header_dic = pickle.loads(header_types) print (header_dic, type (header_dic)) total_size = header_dic[ 'file_size' ] filename = header_dic[ 'filename' ] filemd5 = header_dic[ 'filemd5' ] return (filename,total_size,filemd5) def verification_filemd5( self ,filemd5): # 判断下载下来的文件MD5值和server传过来的MD5值是否一致 if self .getfile_md5() = = filemd5: print ( '\033[31;1mCongratulations download success\033[0m' ) else : print ( '\033[31;1mSorry download failed,download again support breakpoint continuation\033[0m' ) def write_file( self ,f,get_size,file_size): '''下载文件,将内容写入文件中''' while get_size < file_size: res = self .socket.recv(settings.max_recv_bytes) f.write(res) get_size + = len (res) self .progress_bar( 1 ,get_size,file_size) #1表示下载 def get( self ,cmds): """从server下载文件到client """ if len (cmds) > 1 : filename = cmds[ 1 ] self .file_path = os.path.join(settings.down_filepath, filename) if os.path.isfile( self .file_path): #如果文件存在,支持断电续传 temp_file_size = os.path.getsize( self .file_path) self .socket.send(struct.pack( 'i' ,temp_file_size)) header_size = struct.unpack( 'i' , self .socket.recv( 4 ))[ 0 ] if header_size: filename,file_size,filemd5 = self .recv_file_header(header_size) if temp_file_size = = file_size: print ( '\033[34;1mFile already does exist\033[0m' ) else : print ( '\033[34;1mFile now is breakpoint continuation\033[0m' ) self .appendfile_content( self .file_path,temp_file_size) self .verification_filemd5(filemd5) else : print ( "\033[34;1mFile was downloaded before,but now server's file is not exist\033[0m" ) else : #如果文件不存在,则是直接下载 self .socket.send(struct.pack( 'i' , 0 )) obj = self .socket.recv( 1024 ) header_size = struct.unpack( 'i' , obj)[ 0 ] if header_size = = 0 : print ( "\033[31;1mfile does not exist!\033[0m" ) else : filename, file_size, filemd5 = self .recv_file_header(header_size) download_filepath = os.path.join(settings.down_filepath, filename) with open (download_filepath, 'wb' ) as f: get_size = 0 self .write_file(f, get_size, file_size) self .verification_filemd5(filemd5) else : print ( "\033[31;1muser does not enter file name\033[0m" ) def ls( self ,cmds): '''查看当前工作目录,文件列表''' print ( "\033[34;1mview current working directory\033[0m" ) obj = self .socket.recv( 4 ) dir_size = struct.unpack( 'i' ,obj)[ 0 ] recv_size = 0 recv_bytes = b'' while recv_size <dir_size: temp_bytes = self .socket.recv(settings.max_recv_bytes) recv_bytes + = temp_bytes recv_size + = len (temp_bytes) print (recv_bytes.decode( 'gbk' )) def mkdir( self ,cmds): '''增加目录 1,server返回1 增加成功 2,server返回2 增加失败''' print ( "\033[34;1madd working directory\033[0m" ) obj = self .socket.recv( 4 ) res = struct.unpack( 'i' ,obj)[ 0 ] if res: print ( '\033[31;1mCongratulations add directory success\033[0m' ) else : print ( '\033[31;1mSorry add directory failed\033[0m' ) def cd( self ,cmds): '''切换目录''' print ( "\033[34;1mSwitch working directory\033[0m" ) if len (cmds) > 1 : obj = self .socket.recv( 4 ) res = struct.unpack( 'i' , obj)[ 0 ] if res: print ( '\033[31;1mCongratulations switch directory success\033[0m' ) else : print ( '\033[31;1mSorry switch directory failed\033[0m' ) else : print ( "\033[31;1muser does not enter file name\033[0m" ) def remove( self ,cmds): '''表示删除文件或空文件夹''' print ( "\033[34;1mRemove working directory\033[0m" ) obj = self .socket.recv( 4 ) res = struct.unpack( 'i' , obj)[ 0 ] if res: print ( '\033[31;1mCongratulations remove success\033[0m' ) else : print ( '\033[31;1mSorry remove directory failed\033[0m' ) def open_sendfile( self ,file_size,recv_size = 0 ): '''打开要上传的文件(由于本程序上传文件的原理是先读取本地文件,再写到上传地址的文件)''' with open ( self .file_path, 'rb' ) as f: # send_bytes = b'' # send_size = 0 f.seek(recv_size) while True : data = f.read( 1024 ) if data: self .socket.send(data) obj = self .socket.recv( 4 ) recv_size = struct.unpack( 'i' , obj)[ 0 ] self .progress_bar( 2 , recv_size, file_size) else : break success_state = struct.unpack( 'i' , self .socket.recv( 4 ))[ 0 ] if success_state: print ( '\033[31;1mCongratulations upload success\033[0m' ) else : print ( '\033[31;1mSorry upload directory failed\033[0m' ) def put_situation( self ,file_size,condition = 0 ): '''上传的时候有两种情况,文件已经存在,文件不存在''' quota_state = struct.unpack( 'i' , self .socket.recv( 4 ))[ 0 ] if quota_state: if condition: obj = self .socket.recv( 4 ) recv_size = struct.unpack( 'i' , obj)[ 0 ] self .open_sendfile(file_size,recv_size) else : self .open_sendfile(file_size) else : print ( '\033[31;1mSorry exceeding user quotas\033[0m' ) def put( self ,cmds): """往server端登录的用户目录下上传文件 """ if len (cmds) > 1 : filename = cmds[ 1 ] self .file_path = os.path.join(settings.upload_filepath, filename) if os.path.isfile( self .file_path): # 如果文件存在,支持断电续传 self .socket.send(struct.pack( 'i' , 1 )) file_size = os.path.getsize( self .file_path) header_dic = { 'filename' : os.path.basename(filename), 'file_md5' : self .getfile_md5(), 'file_size' : file_size } header_bytes = pickle.dumps(header_dic) self .socket.send(struct.pack( 'i' , len (header_bytes))) self .socket.send(header_bytes) state = struct.unpack( 'i' , self .socket.recv( 4 ))[ 0 ] if state: #已经存在 has_state = struct.unpack( 'i' , self .socket.recv( 4 ))[ 0 ] if has_state: self .put_situation(file_size, 1 ) else : # 存在的大小 和文件大小一致 不必再传 print ( "\033[31;1mfile already does exist!\033[0m" ) else : # 第一次传 self .put_situation(file_size) else : # 文件不存在 print ( "\033[31;1mfile does not exist!\033[0m" ) self .socket.send(struct.pack( 'i' , 0 )) else : print ( "\033[31;1muser does not enter file name\033[0m" ) def get_recv( self ): '''从client端接受发来的数据''' return pickle.loads( self .socket.recv(settings.max_recv_bytes)) def login( self ): ''' 登陆函数,当登陆失败超过三次,则退出 用户密码发送到server短 接受server端返回的信息,如果成功返回1,失败返回0 :return: 如果用户账号密码正确,则返回用户数据的字典 ''' retry_count = 0 while retry_count < 3 : username = input ( '\033[34;1mplease input Username:\033[0m' ).strip() if not username: continue password = input ( '\033[34;1mplease input Password:\033[0m' ).strip() user_dic = { 'username' :username, 'password' :password } #将用户信息发送到客户端,然后接受客户端的数据 data = pickle.dumps(user_dic) self .socket.send(pickle.dumps(user_dic)) #为了防止出现黏包问题,所以先解压报头,读取报头,再读数据 obj = self .socket.recv( 4 ) res = struct.unpack( 'i' ,obj)[ 0 ] #此处,如果返回的是代码4001,则成功 4002则失败 if res: print ( "\033[32;1m-----------------welcome to ftp client-------------------\033[0m" ) user_info_dic = self .get_recv() recv_username = user_info_dic[ 'username' ] return True else : print ( "\033[31;1mAccount or Passwordoes not correct!\033[0m" ) retry_count + = 1 def execute( self ): ''' 执行,或者实施 :return: ''' if self .login(): while True : try : self .help_info() inp = input ( "Please input a command>>>" ).strip() if not inp: continue self .socket.send(inp.encode(settings.coding)) cmds = inp.split() if hasattr ( self , cmds[ 0 ]): func = getattr ( self , cmds[ 0 ]) func(cmds) break else : print ( '\033[31;1mNo such command ,please try again\033[0m' ) except Exception as e: # server关闭了 print ( '\033[31;1m%s\033[0m' % e) break def help_info( self ): print ( '''\033[34;1m get + (文件名) 表示下载文件 put + (文件名) 表示上传文件 ls 表示查询当前目录下的文件列表(只能访问自己的文件列表) mkdir + (文件名) 表示创建文件夹 cd + (文件名) 表示切换目录(只能在自己的文件列表中切换) remove + (文件名) 表示删除文件或空文件夹 \033[0m''' ) |
不经一番彻骨寒 怎得梅花扑鼻香
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步