Python开发【项目】:FTP程序
作业:开发一个支持多用户在线的FTP程序
要求:
- 用户加密认证
- 允许同时多用户登录
- 每个用户有自己的家目录 ,且只能访问自己的家目录
- 对用户进行磁盘配额,每个用户的可用空间不同
- 允许用户在ftp server上随意切换目录
- 允许用户查看当前目录下文件
- 允许上传和下载文件,保证文件一致性
- 文件传输过程中显示进度条
- 附加功能:支持文件的断点续传(仅下载)
程序
1、最最重要的readme:
### 作者介绍:
* author:lzl
### 博客地址:
* http://www.cnblogs.com/lianzhilei/p/5813986.html ### 功能实现
作业:开发一个支持多用户在线的FTP程序
要求:
用户加密认证
允许同时多用户登录
每个用户有自己的家目录 ,且只能访问自己的家目录
对用户进行磁盘配额,每个用户的可用空间不同
允许用户在ftp server上随意切换目录
允许用户查看当前目录下文件
允许上传和下载文件,保证文件一致性
文件传输过程中显示进度条
附加功能:支持文件的断点续传 ### 目录结构:
FTP
│
├── ftpclient #客户端程序
│ ├── __init__.py
│ └── ftpclient.py #客户端主程序
└── ftpserver #服务端程序
├── README.txt
├── ftpserver.py #服务端入口程序
├── conf #配置文件目录
│ ├── __init__.py
│ └── setting.py
├── modules #程序核心目录
│ ├── __init__.py
│ ├── auth_user.py #用户认证模块
│ └── sokect_server.py #sokectserver模块
├── database #用户数据库
│ ├── alex.db
│ ├── lzl.db
│ └── eric.db
├── home #用户宿主目录
│ ├── alex
│ ├── lzl
│ └── eric
└── log
├── __init__.py
└── log #待扩展.... ### 功能实现 1、conf目录下settings.py模块记录可操作用户信息,根据用户信息生成用户字典和宿主目录,已经生成的不再新建
2、每个用户的宿主目录磁盘空间配额默认为10M,可在settings.py模块里进行修改
3、程序运行在windows系统上,程序要求全部实现,下面是具体命令操作
4、切换目录:cd .. 返回上一级目录 cd dirname 进入dirname
用户登录后默认进入宿主目录,只可在宿主目录下随意切换 5、创建目录:mkdir dirname
在当前目录下创建目录,如果目录存在则报错,不存在创建 6、查看当前路径: pwd 7、查看当前路径下的文件名和目录名: dir 8、下载文件(可续传):get filename
①、服务端当前目录存在此文件,客户端不存在此文件,直接下载
②、服务端当前目录存在此文件,客户端存在此文件名,之前下载中断,文件可续传,进行续传
③、服务端当前目录存在此文件,客户端存在此文件名,大小与服务端一致,不下载 9、上传文件:put filename
判断宿主目录磁盘空间是否够用,可以,上传文件;否则,报错 ### 状态码 400 用户认证失败
401 命令不正确
402 文件不存在
403 创建文件已经存在
404 磁盘空间不够
405 不续传 200 用户认证成功
201 命令可以执行
202 磁盘空间够用
203 文件具有一致性
205 续传 000 系统交互码
Readme
2、程序目录结构:
3、ftp客户端
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian import socket
import os,sys
import hashlib class Myclient():
'''ftp客户端'''
def __init__(self,ip_port):
self.ip_port = ip_port def connect(self):
'''连接服务器'''
self.client = socket.socket()
self.client.connect(self.ip_port) def start(self):
'''程序开始'''
self.connect()
while True:
username = input("输入用户名:").strip()
password = input("输入密码:").strip()
login_info = ("%s:%s" %(username, password))
self.client.sendall(login_info.encode()) #发送用户密码信息
status_code = self.client.recv(1024).decode() #返回状态码
if status_code == "":
print("[%s]用户密码认证错误"%status_code)
continue
else:print("[%s]用户密码认证成功"%status_code)
self.interactive() def interactive(self):
'''开始交互'''
while True:
command = input("->>").strip()
if not command:continue
#if command == "exit":break
command_str = command.split()[0]
if hasattr(self,command_str): # 执行命令
func = getattr(self,command_str)
func(command)
else:print("[%s]命令不存在"%401) def get(self,command):
'''下载文件'''
self.client.sendall(command.encode()) #发送要执行的命令
status_code = self.client.recv(1024).decode()
if status_code == "": #命令可执行
filename = command.split()[1] # 文件名存在,判断是否续传
if os.path.isfile(filename):
revice_size = os.stat(filename).st_size #文件已接收大小
self.client.sendall("".encode())
response = self.client.recv(1024)
self.client.sendall(str(revice_size).encode()) #发送已接收文件大小
status_code = self.client.recv(1024).decode() # 文件大小不一致,续传
if status_code == "":
print("继续上次上传位置进行续传")
self.client.sendall("".encode()) # 文件大小一致,不续传,不下载
elif status_code == "":
print("文件已经存在,大小一致")
return # 文件不存在
else:
self.client.sendall("".encode())
revice_size = 0 file_size = self.client.recv(1024).decode() #文件大小
file_size = int(file_size)
self.client.sendall("".encode()) with open(filename,"ab") as file: #开始接收
#file_size 为文件总大小
file_size +=revice_size
m = hashlib.md5()
while revice_size < file_size:
minus_size = file_size - revice_size
if minus_size > 1024:
size = 1024
else:
size = minus_size
data = self.client.recv(size)
revice_size += len(data)
file.write(data)
m.update(data)
self.__progress(revice_size,file_size,"下载中") #进度条
new_file_md5 = m.hexdigest() #生成新文件的md5值
server_file_md5 = self.client.recv(1024).decode()
if new_file_md5 == server_file_md5: #md5值一致
print("\n文件具有一致性")
else:print("[%s] Error!"%(status_code)) def put(self,command):
'''上传文件'''
if len(command.split()) > 1:
filename = command.split()[1]
#file_path = self.current_path + r"\%s"%filename
if os.path.isfile(filename): #文件是否存在
self.client.sendall(command.encode()) #发送要执行的命令
response = self.client.recv(1024) #收到ack确认 file_size = os.stat(filename).st_size # 文件大小
self.client.sendall(str(file_size).encode()) # 发送文件大小
status_code = self.client.recv(1024).decode() # 等待响应,返回状态码
if status_code == "":
with open(filename,"rb") as file:
m = hashlib.md5()
for line in file:
m.update(line)
send_size = file.tell()
self.client.sendall(line)
self.__progress(send_size, file_size, "上传中") # 进度条
self.client.sendall(m.hexdigest().encode()) #发送文件md5值
status_code = self.client.recv(1024).decode() #返回状态码
if status_code == "":
print("\n文件具有一致性")
else:print("[%s] Error!"%(status_code))
else:
print("[402] Error")
else: print("[401] Error") def dir(self,command):
'''查看当前目录下的文件'''
self.__universal_method_data(command)
pass def pwd(self,command):
'''查看当前用户路径'''
self.__universal_method_data(command)
pass def mkdir(self,command):
'''创建目录'''
self.__universal_method_none(command)
pass def cd(self,command):
'''切换目录'''
self.__universal_method_none(command)
pass def __progress(self, trans_size, file_size,mode):
'''
显示进度条
trans_size: 已经传输的数据大小
file_size: 文件的总大小
mode: 模式
'''
bar_length = 100 #进度条长度
percent = float(trans_size) / float(file_size)
hashes = '=' * int(percent * bar_length) #进度条显示的数量长度百分比
spaces = ' ' * (bar_length - len(hashes)) #定义空格的数量=总长度-显示长度
sys.stdout.write(
"\r%s:%.2fM/%.2fM %d%% [%s]"%(mode,trans_size/1048576,file_size/1048576,percent*100,hashes+spaces))
sys.stdout.flush() def __universal_method_none(self,command):
'''通用方法,无data输出'''
self.client.sendall(command.encode()) # 发送要执行的命令
status_code = self.client.recv(1024).decode()
if status_code == "": # 命令可执行
self.client.sendall("".encode()) # 系统交互
else:
print("[%s] Error!" % (status_code)) def __universal_method_data(self,command):
'''通用方法,有data输出'''
self.client.sendall(command.encode()) #发送要执行的命令
status_code = self.client.recv(1024).decode()
if status_code == "": #命令可执行
self.client.sendall("".encode()) #系统交互
data = self.client.recv(1024).decode()
print(data)
else:print("[%s] Error!" % (status_code)) if __name__ == "__main__":
ip_port =("127.0.0.1",9999) #服务端ip、端口
client = Myclient(ip_port) #创建客户端实例
client.start() #开始连接
ftpclient.py
4、ftp服务端
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian import os,hashlib
import json
from conf import settings
from modules import auth_user
from modules import sokect_server def create_db():
'''创建用户数据库文件'''
user_database={}
encryption = auth_user.User_operation()
limitsize = settings.LIMIT_SIZE
for k,v in settings.USERS_PWD.items():
username = k
password = encryption.hash(v)
user_db_path = settings.DATABASE + r"\%s.db"%username
user_home_path = settings.HOME_PATH + r"\%s"%username
user_database["username"] = username
user_database["password"] = password
user_database["limitsize"] = limitsize
user_database["homepath"] = user_home_path
if not os.path.isfile(user_db_path):
with open(user_db_path,"w") as file:
file.write(json.dumps(user_database)) def create_dir():
'''创建用户属主目录'''
for username in settings.USERS_PWD:
user_home_path = settings.HOME_PATH + r"\%s" %username
if not os.path.isdir(user_home_path):
os.popen("mkdir %s" %user_home_path) if __name__ == "__main__":
'''初始化系统数据并启动程序'''
create_db() #创建数据库
create_dir() #创建属主目录
#启动ftp服务
server = sokect_server.socketserver.ThreadingTCPServer(settings.IP_PORT, sokect_server.Myserver)
server.serve_forever()
ftpserver.py
5、conf配置文件
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian import os,sys #程序主目录文件
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
#添加环境变量
sys.path.insert(0,BASE_DIR) #数据库目录
DATABASE = os.path.join(BASE_DIR,"database") #用户属主目录
HOME_PATH = os.path.join(BASE_DIR,"home") #用户字典
USERS_PWD = {"alex":"","lzl":"","eric":""} #磁盘配额 10M
LIMIT_SIZE = 10240000 #ftp服务端口
IP_PORT = ("0.0.0.0",9999)
settings.py
6、database用户数据库(系统初始化自动生成)
{"username": "alex", "password": "e10adc3949ba59abbe56e057f20f883e", "limitsize": 10240000, "homepath": "C:\\Users\\L\\PycharmProjects\\s14\\homework\\Day8\\FTP\\ftpserver\\home\\alex"}
lzl.db
{"username": "eric", "password": "e9510081ac30ffa83f10b68cde1cac07", "limitsize": 10240000, "homepath": "C:\\Users\\L\\PycharmProjects\\s14\\homework\\Day8\\FTP\\ftpserver\\home\\eric"}
eric.db
{"username": "alex", "password": "e10adc3949ba59abbe56e057f20f883e", "limitsize": 10240000, "homepath": "C:\\Users\\L\\PycharmProjects\\s14\\homework\\Day8\\FTP\\ftpserver\\home\\alex"}
alex.db
7、modules模块dd
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian import json
import sys,os
import hashlib
from conf import settings class User_operation():
'''对登录信息进行认证,登录成功返回用户名,失败返回None'''
def authentication(self,login_info):
list = login_info.split(":") #对信息进行分割
login_name = list[0]
login_passwd = self.hash(list[1])
DB_FILE = settings.DATABASE + r"\%s.db"%login_name
if os.path.isfile(DB_FILE):
user_database = self.cat_database(DB_FILE) #用户数据库信息
if login_name == user_database["username"]:
if login_passwd == user_database["password"]:
return user_database def cat_database(self,DB_FILE):
#获取数据库信息
with open(DB_FILE,"r") as file:
data = json.loads(file.read())
return data def hash(self,passwd):
'''对密码进行md5加密'''
m = hashlib.md5()
m.update(passwd.encode("utf-8"))
return m.hexdigest()
auth_user.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian import socketserver
import sys,os
import hashlib
from os.path import join, getsize
from conf import settings
from modules import auth_user class Myserver(socketserver.BaseRequestHandler):
'''ftp服务端'''
def handle(self):
try:
self.conn = self.request
while True:
login_info = self.conn.recv(1024).decode() # 接收客户端发的的账号密码信息
result = self.authenticat(login_info)
status_code = result[0]
self.conn.sendall(status_code.encode())
if status_code == "":
continue
self.user_db = result[1] #当前登录用户信息
self.current_path = self.user_db["homepath"] #用户当前目录
self.home_path = self.user_db["homepath"] #用户宿主目录 while True:
command = self.conn.recv(1024).decode()
command_str = command.split()[0]
if hasattr(self,command_str):
func = getattr(self,command_str)
func(command)
else:self.conn.sendall("".encode())
except ConnectionResetError as e:
self.conn.close()
print(e) def authenticat(self,login_info):
'''认证用户'''
auth = auth_user.User_operation() # 创建认证实例
result = auth.authentication(login_info) # 认证用户
if result:return "",result
else:return "",result def get(self,command):
'''下载文件'''
if len(command.split()) > 1:
filename = command.split()[1]
file_path = self.current_path + r"\%s"%filename
if os.path.isfile(file_path): #文件是否存在
self.conn.sendall("".encode()) #命令可执行
file_size = os.stat(file_path).st_size # 文件总大小
status_code = self.conn.recv(1024).decode() # 客户端存在此文件
if status_code == "":
self.conn.sendall("".encode()) #系统交互
has_send_size = self.conn.recv(1024).decode()
has_send_size = int(has_send_size)
# 客户端文件不完整可续传
if has_send_size < file_size:
self.conn.sendall("".encode())
file_size -= has_send_size #续传文件大小
response = self.conn.recv(1024) # 等待响应 # 客户端文件完整不可续传、不提供下载
else:
self.conn.sendall("".encode())
return
# 客户端不存在此文件
elif status_code == "":
has_send_size = 0 with open(file_path,"rb") as file:
self.conn.sendall(str(file_size).encode()) #发送文件大小
response = self.conn.recv(1024) #等待响应
file.seek(has_send_size)
m = hashlib.md5()
for line in file:
m.update(line)
self.conn.sendall(line)
self.conn.sendall(m.hexdigest().encode()) #发送文件md5值
else:self.conn.sendall("".encode())
else:self.conn.sendall("".encode()) def put(self,command):
'''上传文件'''
filename = command.split()[1]
file_path = self.current_path + r"\%s" % filename
self.conn.sendall("".encode()) #发送确认
file_size = self.conn.recv(1024).decode() # 文件大小
file_size = int(file_size)
limit_size = self.user_db["limitsize"] #磁盘额度
used_size = self.__getdirsize(self.home_path) #已用空间大小
if limit_size >= file_size+used_size:
self.conn.sendall("".encode())
with open(file_path, "wb") as file: # 开始接收
revice_size = 0
m = hashlib.md5()
while revice_size < file_size:
minus_size = file_size - revice_size
if minus_size > 1024:
size = 1024
else:
size = minus_size
data = self.conn.recv(size)
revice_size += len(data)
file.write(data)
m.update(data)
new_file_md5 = m.hexdigest() # 生成新文件的md5值
server_file_md5 = self.conn.recv(1024).decode()
if new_file_md5 == server_file_md5: # md5值一致
self.conn.sendall("".encode())
else:self.conn.sendall("".encode()) def dir(self,command):
'''查看当前目录下的文件'''
if len(command.split()) == 1:
self.conn.sendall("".encode())
response = self.conn.recv(1024)
send_data = os.popen("dir %s"%self.current_path)
self.conn.sendall(send_data.read().encode())
else:self.conn.sendall("".encode()) def pwd(self,command):
'''查看当前用户路径'''
if len(command.split()) == 1:
self.conn.sendall("".encode())
response = self.conn.recv(1024)
send_data = self.current_path
self.conn.sendall(send_data.encode())
else:self.conn.sendall("".encode()) def mkdir(self,command):
'''创建目录'''
if len(command.split()) > 1:
dir_name = command.split()[1] #目录名
dir_path = self.current_path + r"\%s"%dir_name #目录路径
if not os.path.isdir(dir_path): #目录不存在
self.conn.sendall("".encode())
response = self.conn.recv(1024)
os.popen("mkdir %s"%dir_path)
else:self.conn.sendall("".encode())
else:self.conn.sendall("".encode()) def cd(self,command):
'''切换目录'''
if len(command.split()) > 1:
dir_name = command.split()[1] #目录名
dir_path = self.current_path + r"\%s" %dir_name #目录路径
user_home_path = settings.HOME_PATH + r"\%s"%self.user_db["username"] #宿主目录
if dir_name == ".." and len(self.current_path)>len(user_home_path):
self.conn.sendall("".encode())
response = self.conn.recv(1024)
self.current_path = os.path.dirname(self.current_path) #返回上一级目录
elif os.path.isdir(dir_path) :
self.conn.sendall("".encode())
response = self.conn.recv(1024)
if dir_name != "." and dir_name != "..":
self.current_path += r"\%s"%dir_name #切换目录 else:self.conn.sendall("".encode())
else:self.conn.sendall("".encode()) def __getdirsize(self,home_path):
'''统计目录空间大小'''
size = 0
for root, dirs, files in os.walk(home_path):
size += sum([getsize(join(root, name)) for name in files])
return size
sokect_server.py
效果图:
下载:
续传:
Python开发【项目】:FTP程序的更多相关文章
- 用python开发简单ftp程序
根据alex老师视频开发的简单ftp程序,只能实现简单的get功能 ftp客户端程序: #!/usr/bin/env python #_*_ coding:utf-8 _*_ import socke ...
- Python开发项目:大型模拟战争游戏(外星人入侵)
外星人入侵 游戏概述: 现在准备用python开始搞一个大型游戏,模拟未来战争,地球人狙击外星人大战(其实就是小蜜蜂游戏2333),玩家控制一个飞船,用子弹歼灭屏幕上空的外星飞船:项目用到了Pygam ...
- elipse+pydev+python开发arcgis脚本程序
环境配置参考:http://www.cnblogs.com/halfacre/archive/2012/07/22/2603848.html 添加arcpy类库.arctoolbox.arcgis-b ...
- Python 开发 项目《外星人入侵》
2019-02-05 本篇心路历程: 本篇是打算记录自己的第一个python项目,也是众人皆知的<外星人入侵项目>,本项目大概500多行.趁着寒假,大概耗时3天吧,把完整代码敲了出来,当然 ...
- 〖Android〗从Android Studio转为Eclipse开发项目运行程序闪退的解决方法
很久没有撸Android App开发了- 最近把一个月前通过反编译.二次修改的Android SSHD项目进行简单修改一下: 突然发现迁移项目时,报了一个错误,同时还出现了闪退情况: - ::): t ...
- python开发项目:学生选课系统
程序要求:1.创建北京.上海两所学校(分析:通过学校类实例化两个学校实例) 2.创建Linux.python.go三个课程,Linux\go在北京开,Linux在上海开(创建Linux.python. ...
- 使用Python开发鸿蒙设备程序(0-初体验)
到目前为止,鸿蒙设备开发的"官方指定语言"还是C语言! 这看起来是一件正常的事,毕竟鸿蒙设备开发还是属于嵌入式开发的范畴,而在嵌入式开发中C语言又是当之无愧的首选,所以,大家也都接 ...
- C++ Builder平台使用Indy9开发自动FTP程序01
Indy9与CB自带的Indy8不同处还是挺多的.首先就是图标变漂亮了,其次很多Method都重写了.它主要是依据Delph里的函数,力求与之相通.不同点在本系列的后续章节中会一一介绍. 在写ftp代 ...
- python简单的ftp程序
服务器端 '''1.读取文件名2.检测文件是否存在3.打开文件4.检测文件大小5.发送文件大小给客户端6.等客户端确认7.开始边读边发数据8.发送md5'''import socket,os,time ...
- 2020年python开发微信小程序,公众号,手机购物商城APP
2020年最新的技术全栈,手机短信注册登陆等运用, 精准定位用户 支付宝支付 以及前后端从0到大神的全部精解 2020年最新的技术全栈,手机短信注册登陆等运用, 精准定位用户 支付宝支付 以及前后端从 ...
随机推荐
- 04,Python网络爬虫之requests模块(1)
引入 Requests 唯一的一个非转基因的 Python HTTP 库,人类可以安全享用. 警告:非专业使用其他 HTTP 库会导致危险的副作用,包括:安全缺陷症.冗余代码症.重新发明轮子症.啃文档 ...
- 4.bootstrap的form表单的form-group和form-control的区别与联系
1. form-group一般用于div form-control一般用于置于div中的标签元素,为了让控件在各种表单风格中样式不出错,需要添加类名“form-control”,如: <form ...
- __bridge 使用注意
前奏 在平常开发中,我们可能遇到 CoreFoundation(CF) 框架的对象和 OC 对象之间的类型转换,这时候我们需要 __bridge 来帮忙 注意 : 如果是使用 CF __bridge ...
- android的5个进程等级
一.Foreground Process(前台进程,优先级别最高) 1.正处于 Activity Resume() 状态 2.正处于与 bound 服务交互的状态 3.正处于服务在前台运行的状态(st ...
- 【Neural Network】林轩田机器学习技法
首先从单层神经网络开始介绍 最简单的单层神经网络可以看成是多个Perception的线性组合,这种简单的组合可以达到一些复杂的boundary. 比如,最简单的逻辑运算AND OR NOT都可以由多 ...
- 给vmstat加上时间戳
vmstat -n 5 | awk '{print strftime("[%Y-%m-%d %H:%M:%S]"),$0}' 或者 vmstat -n 5 | awk '{ pri ...
- 孤荷凌寒自学python第五十九天尝试使用python来读访问远端MongoDb数据服务
孤荷凌寒自学python第五十九天尝试使用python来读访问远端MongoDb数据服务 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第五天.今天的感觉是,mongoDB数 ...
- python import模块熟悉方法
python import导入的可用模块很多,新增速度较快,无法一次性全部掌握. 掌握熟悉一种模块的方法是非常有价值的技能. 探究模块可以从python解释器入手,具体流程记录如下: 以os模块为例: ...
- 容器基础(二): 使用Namespace进行边界隔离
Linux Namespace 容器技术可以认为是一种沙盒(sandbox), 为了实现沙盒/容器/应用间的隔离,就需要一种技术来对容器界定边界,从而让容器不至于互相干扰.当前使用的技术就是Names ...
- VS2017+EF+Mysql生成实体数据模型(解决闪退的坑)
原文:VS2017+EF+Mysql生成实体数据模型(解决闪退的坑) 最近要使用VS2017+EF+Mysql,在生成实体数据模型踏过一些坑,在此做个总结. 1.先下载并安装 mysql-connec ...