如何组建FTP服务器?支持多用户
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何组建FTP服务器?支持多用户相关的知识,希望对你有一定的参考价值。
我是个菜鸟,架设局域网FTP服务器需要什么要求?用什么软件最好?
另外我的IIS为什么没有FTP的选项?
iis的那个需要安装时选择ftp,默认是没有的。不过我不建议用iis自带的ftp服务器,很烂,配置多用户的非常麻烦。 参考技术A 我也推荐 Serv-U 给你一个绿盟专用的,很好用没有后门Serv-U FTP Server V6.0.0.2_绿色汉化特别版_绿盟专用无后门无漏洞版本 http://www.xdowns.com/soft/1/98/2006/Soft_31503.html 参考技术B IIS安装时要详细选择上FTp才会安装上,你选择安装IIS时,FTP不会自动选择上的,可以看我上传的图片把选项选择. 参考技术C 使用serv-u吧,iis架设会很复杂的,要给windows建立好多用户,权限,用serv-u方便的多
开发一个支持多用户在线的FTP程序
一,项目题目:开发一个支持多用户在线的FTP程序
二,项目要求:
1.用户加密认证 2.允许同时多用户登录 3.每个用户有自己的家目录 ,且只能访问自己的家目录 4.对用户进行磁盘配额,每个用户的可用空间不同 5.允许用户在ftp server上随意切换目录 6.允许用户查看当前目录下文件 7.允许上传和下载文件,保证文件一致性(md5) 8.文件传输过程中显示进度条 9.附加功能:支持文件的断点续传
三,注意事项:
基本要求. 完成1,2,3,5,6,7,8 实力选手. 完成 上条 及需求4 , 大神操作. 完成 9 且项目目录结构良好、代码逻辑清晰,
四,项目分析:
1,用户加密认证
这个肯定需要用到configparser 和hashlib模块,用md5进行加密,服务端与用户端 进行交互前,肯定需要进行认证,在服务端进行认证,客户端需要发送用户名及密码,但 是为了安全起见,服务端数据库中的密码应该是加密后的密文,客户端登陆认证时也应该 发送密文到服务端,服务端接受到密文与数据库中对应的密文进行比较。
2,查看自己的当前目录下的文件
这个只需要写一个dir就ok 简单的说,使用configparse模块就可以完成
3,文件传输中显示进度条
下载的进度条比较好实现,我们可以从服务端受到将要下载的文件的大小, 上传的进度条,我们可以利用文件操作的tell()方法,获取当前指针位置(字节)
4,小编的主要思路
- 1 对于此项目,最初的想法是写出上传,和下载文件的程序,包括客户端和服务端。 - 2 在此基础上扩展程序,包括提出开始程序到bin里面,配置文件在config里面 - 3 然后把上传文件和下载文件的程序进行断点续传的程序重构 - 4 在此基础上,对文件进行加密 - 5 增加功能,包括设置进度条,增加查看功能,增加目录功能,删除文件功能,切换目录功能等 - 6 然后再设置磁盘分配功能,完善内容 - 7 然后添加用户登陆,包括对用户的密码加密等功能 - 8 写完后检查程序
五,项目流程图
六,README文件
## 作者: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
# _*_ 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
# _*_ 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
# _*_ 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
# _*_ 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
# _*_ 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
# _*_ 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
# _*_ 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
# _*_ 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\'\'\')
以上是关于如何组建FTP服务器?支持多用户的主要内容,如果未能解决你的问题,请参考以下文章