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开发一个小程序专门用以计算呢? 现在我们越来越依赖于 ...
随机推荐
- 如何查看项目svn路径
1.选择项目根目录---->鼠标右键---->属性---->版本控制(Subversion) 如图:
- [转]基于gulp和webpack的前端工程化
本文样例代码 :https://github.com/demohi/learning-gulp 本文主要简单介绍一下基于gulp和webpack的前端工程化. 技术栈 React.js reFlux ...
- Here's what C++ is
Yes this article describes what c++ exactlyis http://www.skywind.me/blog/archives/1398#comment-3671 ...
- Android js相互调用
一.webview相当于android中的浏览器,基于webkit开发,可以浏览网页文件,支持css javas cript 以及html webview.getSettings().setJavaS ...
- ACM 背包问题
背包问题 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 现在有很多物品(它们是可以分割的),我们知道它们每个物品的单位重量的价值v和重量w(1<=v,w< ...
- ACM: 限时训练题解- Travelling Salesman-最小生成树
Travelling Salesman After leaving Yemen, Bahosain now works as a salesman in Jordan. He spends mos ...
- require.js 入门学习 (share)
以下内容转自阮一峰老师的网络日志:http://www.ruanyifeng.com/blog/2012/11/require_js.html 更多学习资源: require.js官网:http:// ...
- [深入浅出Windows 10]应用实战:Bing在线壁纸
本章介绍一个使用Bing搜索引擎背景图接口实现的一个应用——Bing在线壁纸,讲解如何使用网络的接口来实现一个壁纸下载,壁纸列表展示和网络请求封装的内容.通过该例子我们可以学习到如何使用网络编程的知识 ...
- eclipse安装color theme插件
为Eclipse添加Color.Theme的插件 这样可以方便一键更换主题,再也不用一个一个设置BackgroundColor了,同时也方便回退到default默认主题配置. 方法一: 打开Eclip ...
- Maven dependency spring-web vs spring-webmvc
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmv ...