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 ...
- elipse+pydev+python开发arcgis脚本程序
环境配置参考:http://www.cnblogs.com/halfacre/archive/2012/07/22/2603848.html 添加arcpy类库.arctoolbox.arcgis-b ...
- 使用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年最新的技术全栈,手机短信注册登陆等运用, 精准定位用户 支付宝支付 以及前后端从 ...
- python如何编写win程序
python可以编写win程序.win程序的格式是exe,下面我们就来看一下使用python编写exe程序的方法. 编写好python程序后py2exe模块即可将其打包为exe程序. 实际操作过程: ...
- 用python开发android应用 【转载】
用python开发android应用 [转载] 转载自:http://www.miui.com/thread-995114-1-1.html Python是动态语言,比较简洁.Android不直接支持 ...
- 用Python开发实用程序 – 计算器
一段时间前,自己制作了一个库 “sui-math”.这其实是math的翻版.做完后,python既然可以轻易的完成任何的数学计算,何不用python开发一个小程序专门用以计算呢? 现在我们越来越依赖于 ...
随机推荐
- 关于c语言模拟c++的多态
关于c++多态,个人认为就是父类调用子类的方法,c++多态的实现主要通过虚函数实现,如果类中含有虚函数,就会出现虚函数表,具体c++多态可以参考<深度探索c++对象模型> c语言模拟多态主 ...
- NOIp 2014 #5 解方程 Label:数论?
题目描述 已知多项式方程: a0+a1x+a2x^2+..+anx^n=0 求这个方程在[1, m ] 内的整数解(n 和m 均为正整数) 输入输出格式 输入格式: 输入文件名为equation .i ...
- 【BZOJ】1006: [HNOI2008]神奇的国度
http://www.lydsy.com/JudgeOnline/problem.php?id=1006 题意:在一个弦图中找最少染色数.(n<=10000, m<=1000000) #i ...
- 硬盘分区工具gparted使用
一.介绍 GParted是一款linux下的功能非常强大的分区工具,和windows下的‘分区魔术师’类似,操作和显示上也很相似.GParted可以方便的创建.删除分区,也可以调整分区的大小和移动分区 ...
- IOS中NSUserDefaults的用法(轻量级本地数据存储)
NSUserDefaults适合存储轻量级的本地数据,比如要保存一个登陆界面的数据,用户名.密码之类的,个人觉得使用NSUserDefaults是首选.下次再登陆的时候就可以直接从NSUserDefa ...
- Bouncy Castle内存溢出
现象: 堆内存溢出,java.lang.OutOfMemoryError: Java heap space 用jmap查看,显示 num #instances #bytes ...
- MongoDB数据库的简介及安装
一.MongoDB数据库简介 简介 MongoDB是一个高性能,开源,无模式的,基于分布式文件存储的文档型数据库,由C++语言编写,其名称来源取自“humongous”,是一种开源的文档数据库──No ...
- jQuery获取cookie
之前一直以为获取cookie的方法封装在了jQuery包中...没想到还得单独下jquery.cookie.js插件,不太好找,备份一份: /*! * jQuery Cookie Plugin v1. ...
- 关于网上常见的几种MD5加密的区别
(1) using System.Security.Cryptography; byte[] result = Encoding.Default.GetBytes(yps); //tbPass为输入密 ...
- JAVA6开发WebService (二)——JAX-WS例子
转载自http://wuhongyu.iteye.com/blog/807836 上一篇写了个最简单的小例子,只是为了说明JAVA6开发Web Service很方便,这一篇稍微深入一点,写个稍微有点代 ...