[ python ] FTP作业进阶
作业:开发一个支持多用户在线的FTP程序
要求:
- 用户加密认证
- 允许同时多用户登录
- 每个用户有自己的家目录 ,且只能访问自己的家目录
- 对用户进行磁盘配额,每个用户的可用空间不同
- 允许用户在ftp server上随意切换目录
- 允许用户查看当前目录下文件
- 允许上传和下载文件,保证文件一致性
- 文件传输过程中显示进度条
- 附加功能:支持文件的断点续传
之前作业的链接地址:https://www.cnblogs.com/hukey/p/8909046.html 这次的重写是对上次作业的补充,具体实现功能点如下:
README
# 作者介绍:
author: hkey # 博客地址:
https://www.cnblogs.com/hukey/p/10182876.html # 功能实现: 作业:开发一个支持多用户在线的FTP程序 要求: 用户加密认证
允许同时多用户登录
每个用户有自己的家目录 ,且只能访问自己的家目录
对用户进行磁盘配额,每个用户的可用空间不同
允许用户在ftp server上随意切换目录
允许用户查看当前目录下文件
允许上传和下载文件,保证文件一致性
文件传输过程中显示进度条
附加功能:支持文件的断点续传 # 目录结构: FTP
├── ftp_client/ # ftp客户端程序
│ └── ftp_client.py # 客户端主程序
└── ftp_server/ # ftp服务端程序
├── bin/
│ ├── __init__.py
│ └── start.py
├── conf/ # 配置文件目录
│ ├── __init__.py
│ ├── settings.py
│ └── user.list # 记录注册用户名
├── db/ # 用户数据库
├── home/ # 用户家目录
├── logs/ # 记录日志目录
└── modules/ # 程序核心功能目录
├── auth.py # 用户认证(注册和登录)
├── __init__.py
├── log.py # 日志初始化类
└── socket_server.py # socket网络模块 # 功能实现:
. 实现了用户注册和登录验证(新增)。
. 用户注册时,将用户名添加到 conf/user.list里并创建home/[username],为每个用户生成独立的数据库文件 db/[username].db
. 每个用户的磁盘配额为10M, 在conf/settings.py 中声明, 可以修改
. 本程序适用于windows,命令:cd / mkdir / pwd / dir / put / get
. 实现了get下载续传的功能:
服务器存在文件, 客户端不存在,直接下载;
服务器存在文件, 客户端也存在文件,比较大小, 一致则不传,不一致则追加续传;
. 实现日志记录(新增) # 状态码: 登录验证(用户名或密码错误)
注册验证(注册的用户名已存在)
命令不正确
空间不足
续传
get(客户端文件存在) 登录成功
注册成功
命令执行成功
文件一致 系统交互码
README
程序结构
具体代码实现
1. ftp客户端程序
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Author: hkey
import os, sys
import socket class MyClient:
def __init__(self, ip_port):
self.client = socket.socket()
self.ip_port = ip_port def connect(self):
self.client.connect(self.ip_port) def start(self):
self.connect()
while True:
print('注册(register)\n登录(login)')
auth_type = input('>>>').strip()
if not auth_type: continue
if auth_type == 'register' or auth_type == 'login':
user = input('用户名:').strip()
pwd = input('密码:').strip()
auth_info = '%s:%s:%s' % (auth_type, user, pwd)
self.client.sendall(auth_info.encode())
status_code = self.client.recv(1024).decode()
if status_code == '':
print('\033[32;1m登录成功.\033[0m')
self.interactive()
elif status_code == '':
print('\033[32;1m注册成功.\033[0m')
elif status_code == '':
print('\033[31;1m用户名或密码错误.\033[0m')
elif status_code == '':
print('\033[31;1m注册用户名已存在.\033[0m')
else:
print('[%s]Error!' % status_code) else:
print('\033[31;1m输入错误,请重新输入.\033[0m') def interactive(self):
while True:
command = input('>>>').strip()
if not command: continue
command_str = command.split()[0]
if hasattr(self, command_str):
func = getattr(self, command_str)
func(command) def dir(self, command):
self.__universal_method_data(command) def pwd(self, command):
self.__universal_method_data(command) def mkdir(self, command):
self.__universal_method_none(command) def cd(self, command):
self.__universal_method_none(command) def __universal_method_none(self, command):
self.client.sendall(command.encode())
status_code = self.client.recv(1024).decode()
if status_code == '':
self.client.sendall(b'')
else:
print('[%s]Error!' % status_code) def __universal_method_data(self, command):
self.client.sendall(command.encode())
status_code = self.client.recv(1024).decode()
if status_code == '':
self.client.sendall(b'')
result = self.client.recv(4096)
print(result.decode('gbk'))
else:
print('[%s]Error!' % status_code) def put(self, command):
if len(command.split()) > 1:
filename = command.split()[1]
if os.path.isfile(filename):
self.client.sendall(command.encode())
file_size = os.path.getsize(filename)
response = self.client.recv(1024)
self.client.sendall(str(file_size).encode())
status_code = self.client.recv(1024).decode()
if status_code == '':
with open(filename, 'rb') as f:
while True:
data = f.read(1024)
send_size = f.tell()
if not data: break
self.client.sendall(data)
self.__progress(send_size, file_size, '上传中')
else:
print('\033[31;1m[%s]空间不足.\033[0m' % status_code) else:
print('\033[31;1m[%s]文件不存在.\033[0m' % filename) else:
print('\033[31;1m命令格式错误.\033[0m') def __progress(self, trans_size, file_size, mode):
bar_length = 100
percent = float(trans_size) / float(file_size)
hashes = '=' * int(percent * bar_length)
spaces = ' ' * int(bar_length - len(hashes))
sys.stdout.write('\r%s %.2fM/%.2fM %d%% [%s]'
% (mode, trans_size / 1048576, file_size / 1048576, percent * 100, hashes + spaces)) 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):
self.client.sendall(b'')
response = self.client.recv(1024)
has_send_data = os.path.getsize(filename)
self.client.sendall(str(has_send_data).encode())
status_code = self.client.recv(1024).decode()
if status_code == '':
print('续传.')
response = self.client.sendall(b'')
elif status_code == '':
print('文件一致.')
return
else:
self.client.sendall(b'')
has_send_data = 0 file_size = int(self.client.recv(1024).decode())
self.client.sendall(b'')
with open(filename, 'ab') as f:
while has_send_data != file_size:
data = self.client.recv(1024)
has_send_data += len(data)
f.write(data)
self.__progress(has_send_data, file_size, '下载中') else:
print('[%s]Error!' % status_code) if __name__ == '__main__':
ftp_client = MyClient(('localhost', 8080))
ftp_client.start()
ftp_client.py
2. ftp服务端程序
(1)ftp启动程序
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Author: hkey
import os, sys BASE_DIR = os.path.dirname(os.getcwd()) sys.path.insert(0, BASE_DIR) from conf import settings
from modules import socket_server if __name__ == '__main__':
server = socket_server.socketserver.ThreadingTCPServer(settings.IP_PORT, socket_server.MyServer)
server.serve_forever()
start.py
(2)conf配置文件
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Author: hkey
import os BASE_DIR = os.path.dirname(os.getcwd()) HOME_PATH = os.path.join(BASE_DIR, 'home')
LOG_PATH = os.path.join(BASE_DIR, 'logs')
DB_PATH = os.path.join(BASE_DIR, 'db')
USER_LIST_FILE = os.path.join(BASE_DIR, 'conf', 'user.list') LOG_SIZE = 102400
LOG_NUM = 5 LIMIT_SIZE = 10240000000 IP_PORT = ('localhost', 8080)
settings.py
(3)modules 核心模块
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Author: hkey
import os, sys
import pickle
from conf import settings
from modules.log import Logger class Auth:
def __init__(self, user, pwd):
self.user = user
self.pwd = pwd def register(self):
user_list = Auth.file_oper(settings.USER_LIST_FILE, 'r').split('\n')[:-1]
if self.user not in user_list:
Auth.file_oper(settings.USER_LIST_FILE, 'a', self.user + '\n')
user_home_path = os.path.join(settings.HOME_PATH, self.user)
if not os.path.isdir(user_home_path):
os.makedirs(user_home_path)
user_dict = {'user': self.user, 'pwd': self.pwd, 'home_path': user_home_path,
'limit_size': settings.LIMIT_SIZE}
user_pickle = pickle.dumps(user_dict)
user_db_file = os.path.join(settings.DB_PATH, self.user) + '.db'
Auth.file_oper(user_db_file, 'ab', user_pickle)
Logger.info('[%s]注册成功。' % self.user)
return ''
else:
Logger.warning('[%s]注册用户名已存在。' % self.user)
return '' def login(self):
user_list = Auth.file_oper(settings.USER_LIST_FILE, 'r').split('\n')[:-1]
if self.user in user_list:
user_db_file = os.path.join(settings.DB_PATH, self.user) + '.db'
user_pickle = Auth.file_oper(user_db_file, 'rb')
user_dict = pickle.loads(user_pickle)
if self.user == user_dict['user'] and self.pwd == user_dict['pwd']:
Logger.info('[%s]登录成功.' % self.user)
return user_dict
else:
Logger.error('[%s]用户名或密码错误.' % self.user) else:
Logger.warning('[%s]登录用户不存在.' % self.user) @staticmethod
def file_oper(file, mode, *args):
if mode == 'a' or mode == 'ab':
data = args[0]
with open(file, mode) as f:
f.write(data)
elif mode == 'r' or mode == 'rb':
with open(file, mode) as f:
data = f.read()
return data
auth.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Author: hkey
import os, sys
import logging.handlers from conf import settings class Logger:
logger = logging.getLogger()
formatter = logging.Formatter('[%(asctime)s][%(levelname)s] %(message)s', '%Y-%m-%d %H:%M:%S')
logfile = os.path.join(settings.LOG_PATH, sys.argv[0].split('/')[-1].split('.')[0]) + '.log'
fh = logging.handlers.RotatingFileHandler(filename=logfile, maxBytes=settings.LOG_SIZE,
backupCount=settings.LOG_NUM, encoding='utf-8')
ch = logging.StreamHandler() fh.setFormatter(formatter)
ch.setFormatter(formatter) logger.setLevel(level=logging.INFO) logger.addHandler(fh)
logger.addHandler(ch) @classmethod
def info(cls, msg):
cls.logger.info(msg) @classmethod
def warning(cls, msg):
cls.logger.warning(msg) @classmethod
def error(cls, msg):
cls.logger.error(msg)
log.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Author: hkey import os
import socketserver
import subprocess
from os.path import getsize, join
from modules.auth import Auth
from modules.log import Logger class MyServer(socketserver.BaseRequestHandler):
def handle(self):
try:
while True:
auth_info = self.request.recv(1024).decode()
auth_type, user, pwd = auth_info.split(':')
auth_user = Auth(user, pwd)
if auth_type == 'register':
status_code = auth_user.register()
self.request.sendall(status_code.encode())
elif auth_type == 'login':
user_dict = auth_user.login()
if user_dict:
self.request.sendall(b'')
self.user_current_path = user_dict['home_path']
self.user_home_path = user_dict['home_path']
self.user_limit_size = user_dict['limit_size']
while True:
command = self.request.recv(1024).decode()
command_str = command.split()[0]
if hasattr(self, command_str):
func = getattr(self, command_str)
func(command) else:
self.request.sendall(b'')
except ConnectionResetError as e:
print('Error:', e) def dir(self, command):
if len(command.split()) == 1:
Logger.info('[%s] 执行成功.' % command)
self.request.sendall(b'')
response = self.request.recv(1024)
cmd_res = subprocess.Popen('dir %s' % self.user_current_path, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, shell=True)
stdout = cmd_res.stdout.read()
stderr = cmd_res.stderr.read()
result = stdout if stdout else stderr
self.request.sendall(result)
else:
Logger.warning('[%s] 命令格式错误.' % command)
self.request.sendall(b'') def pwd(self, command):
if len(command.split()) == 1:
self.request.sendall(b'')
Logger.info('[%s] 执行成功.' % command)
response = self.request.recv(1024)
self.request.sendall(self.user_current_path.encode())
else:
Logger.warning('[%s] 命令格式错误.' % command)
self.request.sendall(b'') def mkdir(self, command):
if len(command.split()) > 1:
dir_name = command.split()[1]
dir_path = os.path.join(self.user_current_path, dir_name)
if not os.path.isdir(dir_path):
Logger.info('[%s] 执行成功.' % command)
self.request.sendall(b'')
response = self.request.recv(1024)
os.makedirs(dir_path)
else:
Logger.warning('[%s] 命令格式错误.' % command)
self.request.sendall(b'') def cd(self, command):
if len(command.split()) > 1:
dir_name = command.split()[1]
dir_path = os.path.join(self.user_current_path, dir_name)
if dir_name == '..' and len(self.user_current_path) > len(self.user_home_path):
self.request.sendall(b'')
response = self.request.recv(1024)
self.user_current_path = os.path.dirname(self.user_current_path)
elif os.path.isdir(dir_path):
self.request.sendall(b'')
response = self.request.recv(1024)
if dir_name != '.' and dir_name != '..':
self.user_current_path = dir_path
else:
self.request.sendall(b'')
else:
Logger.warning('[%s] 命令格式错误.' % command)
self.request.sendall(b'') def put(self, command):
filename = command.split()[1]
file_path = os.path.join(self.user_current_path, filename)
response = self.request.sendall(b'')
file_size = self.request.recv(1024).decode()
file_size = int(file_size)
used_size = self.__getdirsize(self.user_home_path)
if self.user_limit_size > file_size + used_size:
self.request.sendall(b'')
Logger.info('[%s] 执行成功.' % command)
recv_size = 0
Logger.info('[%s] 文件开始上传.' % file_path)
with open(file_path, 'wb') as f:
while recv_size != file_size:
data = self.request.recv(1024)
recv_size += len(data)
f.write(data)
Logger.info('[%s] 文件上传完成.' % file_path) else:
self.request.sendall(b'') def __getdirsize(self, user_home_path):
size = 0
for root, dirs, files in os.walk(user_home_path):
size += sum([getsize(join(root, name)) for name in files])
return size def get(self, command):
if len(command.split()) > 1:
filename = command.split()[1]
file_path = os.path.join(self.user_current_path, filename)
if os.path.isfile(file_path):
self.request.sendall(b'')
file_size = os.path.getsize(file_path)
status_code = self.request.recv(1024).decode()
if status_code == '':
self.request.sendall(b'')
recv_size = int(self.request.recv(1024).decode())
if file_size > recv_size:
self.request.sendall(b'')
respon = self.request.recv(1024)
elif file_size == recv_size:
self.request.sendall(b'')
print('一致.')
return
else:
recv_size = 0 self.request.sendall(str(file_size).encode())
resonse = self.request.recv(1024)
with open(file_path, 'rb') as f:
f.seek(recv_size)
while True:
data = f.read(1024)
if not data: break
self.request.sendall(data) else:
self.request.sendall(b'')
socket_server.py
(4)其他目录
db/ - 注册成功后生成个人数据库文件
home/ - 注册成功后创建个人家目录
log/ - 日志文件目录
程序运行效果图
(1)注册、登录及命令的执行
client:
server:
(2)上传
(3)下载(续传功能)
[ python ] FTP作业进阶的更多相关文章
- Python学习笔记——基础篇【第七周】———FTP作业(面向对象编程进阶 & Socket编程基础)
FTP作业 本节内容: 面向对象高级语法部分 Socket开发基础 作业:开发一个支持多用户在线的FTP程序 面向对象高级语法部分 参考:http://www.cnblogs.com/wupeiqi/ ...
- python之ftp作业【还未完成】
作业要求 0.实现用户登陆 1.实现上传和下载 3.每个用户都有自己的家目录,且只可以访问自己的家目录 4.对用户进行磁盘配额,每个用户的空间不同,超过配额不允许下载和上传 5.允许用户在指定的家目录 ...
- python day 18: thinking in UML与FTP作业重写
目录 python day 18 1. thinking in UML读书小感 2. FTP作业重写 2.1 软件目录结构 2.2 FTPClient端脚本 2.3 FTPServer端脚本 pyth ...
- python day33 ,socketserver多线程传输,ftp作业
一.一个服务端连多个客户端的方法 1.服务端 import socketserver class MyServer(socketserver.BaseRequestHandler): def hand ...
- python全栈开发day29-网络编程之socket常见方法,socketserver模块,ftp作业
一.昨日内容回顾 1.arp协议含义 2.子网,子网掩码 3.两台电脑在网络中怎么通信的? 4.tcp和udp socket编码 5.tcp和udp协议的区别 6.tcp三次握手和四次挥手,syn洪攻 ...
- python基础——面向对象进阶
python基础--面向对象进阶 1.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 ...
- python面向对象编程进阶
python面向对象编程进阶 一.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 1 ...
- Python学习笔记进阶篇——总览
Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(Socket编程进阶&多线程.多进程) Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(异常处理) Pyth ...
- Python学习笔记——进阶篇【第八周】———进程、线程、协程篇(Socket编程进阶&多线程、多进程)
本节内容: 异常处理 Socket语法及相关 SocketServer实现多并发 进程.线程介绍 threading实例 线程锁.GIL.Event.信号量 生产者消费者模型 红绿灯.吃包子实例 mu ...
随机推荐
- 表单验证2-JS正则
1. JS正则: 以/开头,以/结尾. test作用:到里面去找,只要里面有,就返回true:否则就返回false. 例如:rep=/\d+/; 检验里面是否有数字. 2.rep=/^ $/; ...
- HDU.1166 敌兵布阵 (线段树 单点更新 区间查询)
HDU.1166 敌兵布阵 (线段树 单点更新 区间查询) 题意分析 加深理解,重写一遍 代码总览 #include <bits/stdc++.h> #define nmax 100000 ...
- POJ.2299 Ultra-QuickSort (线段树 单点更新 区间求和 逆序对 离散化)
POJ.2299 Ultra-QuickSort (线段树 单点更新 区间求和 逆序对 离散化) 题意分析 前置技能 线段树求逆序对 离散化 线段树求逆序对已经说过了,具体方法请看这里 离散化 有些数 ...
- [模板]2-SAT 问题&和平委员会
tarjan的运用 this is a problem:link 2-SAT处理的是什么 首先,把「2」和「SAT」拆开.SAT 是 Satisfiability 的缩写,意为可满足性.即一串布尔变量 ...
- [POJ3613] Cow Relays
link 题目大意 给你一个含有边权的无向图,问从$S$到$T$经过$N$条边的最小花费. 试题分析 我们可以很容易推导$dp$方程,$dp(k,i,j)$表示经过$k$条边从$i$到$j$的最小花费 ...
- redis搭建集群并用TreeSoft管理
前言:redis作为一款高效的NOSQL数据库已经深入贯彻和落实到我们的日常开发代码中,作为缓存.时间控制.数据仓库.队列等使用方法层出不穷,简直是开写代码.居家旅行之必备良药.曾经,我们的项目都是单 ...
- 《剑指offer》— JavaScript(10)矩形覆盖
矩形覆盖 题目描述 我们可以用(2*1)的小矩形横着或者竖着去覆盖更大的矩形.请问用n个(2*1)的小矩形无重叠地覆盖一个(2*n)的大矩形,总共有多少种方法? 实现代码 function jumpF ...
- UESTC--1727
原题链接:http://acm.uestc.edu.cn/problem.php?pid=1727 分析:用 l[i] 记录第 i 层楼有多少物品需要往上继续搬运,如果某层楼没有物品,但是更上面还有, ...
- js 多个事件的绑定及移除(包括原生写法和 jquery 写法)
需要打开控制台查看效果: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q ...
- array_unique后,数组本身的值并不会变
<?php $arr = [ ' ]; print_r($arr); print_r(array_unique($arr)); print_r($arr); //array_unique后,数组 ...