作业:开发一个支持多用户在线的FTP程序

要求:

  1. 用户加密认证
  2. 允许同时多用户登录
  3. 每个用户有自己的家目录 ,且只能访问自己的家目录
  4. 对用户进行磁盘配额,每个用户的可用空间不同
  5. 允许用户在ftp server上随意切换目录
  6. 允许用户查看当前目录下文件
  7. 允许上传和下载文件,保证文件一致性
  8. 文件传输过程中显示进度条
  9. 附加功能:支持文件的断点续传

之前作业的链接地址: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作业进阶的更多相关文章

  1. Python学习笔记——基础篇【第七周】———FTP作业(面向对象编程进阶 & Socket编程基础)

    FTP作业 本节内容: 面向对象高级语法部分 Socket开发基础 作业:开发一个支持多用户在线的FTP程序 面向对象高级语法部分 参考:http://www.cnblogs.com/wupeiqi/ ...

  2. python之ftp作业【还未完成】

    作业要求 0.实现用户登陆 1.实现上传和下载 3.每个用户都有自己的家目录,且只可以访问自己的家目录 4.对用户进行磁盘配额,每个用户的空间不同,超过配额不允许下载和上传 5.允许用户在指定的家目录 ...

  3. 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 ...

  4. python day33 ,socketserver多线程传输,ftp作业

    一.一个服务端连多个客户端的方法 1.服务端 import socketserver class MyServer(socketserver.BaseRequestHandler): def hand ...

  5. python全栈开发day29-网络编程之socket常见方法,socketserver模块,ftp作业

    一.昨日内容回顾 1.arp协议含义 2.子网,子网掩码 3.两台电脑在网络中怎么通信的? 4.tcp和udp socket编码 5.tcp和udp协议的区别 6.tcp三次握手和四次挥手,syn洪攻 ...

  6. python基础——面向对象进阶

    python基础--面向对象进阶 1.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 ...

  7. python面向对象编程进阶

    python面向对象编程进阶 一.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 1 ...

  8. Python学习笔记进阶篇——总览

    Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(Socket编程进阶&多线程.多进程) Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(异常处理) Pyth ...

  9. Python学习笔记——进阶篇【第八周】———进程、线程、协程篇(Socket编程进阶&多线程、多进程)

    本节内容: 异常处理 Socket语法及相关 SocketServer实现多并发 进程.线程介绍 threading实例 线程锁.GIL.Event.信号量 生产者消费者模型 红绿灯.吃包子实例 mu ...

随机推荐

  1. 【刷题】洛谷 P3676 小清新数据结构题

    题目背景 本题时限2s,内存限制256M 题目描述 在很久很久以前,有一棵n个点的树,每个点有一个点权. 现在有q次操作,每次操作是修改一个点的点权或指定一个点,询问以这个点为根时每棵子树点权和的平方 ...

  2. bzoj3709: [PA2014]Bohater(贪心)

    贪心... 可以回血的按d[i]升序防止死掉 不能回血的按a[i]降序,因为只考虑d我们要扣除的血量是一定的,为了不死显然回血多的放前面更好 #include<iostream> #inc ...

  3. Codeforces Round #305 (Div. 2) D 维护单调栈

    D. Mike and Feet time limit per test 1 second memory limit per test 256 megabytes input standard inp ...

  4. Codeforces Round #407 (Div. 2)A B C 水 暴力 最大子序列和

    A. Anastasia and pebbles time limit per test 1 second memory limit per test 256 megabytes input stan ...

  5. linux中使用随机数

    (1)单纯使用rand重复调用n次,就会得到一个0-RAND_MAX之间的伪随机数,如果需要调整范围,可以得到随机数序列后再进行计算.(2)单纯使用rand来得到伪随机数序列有缺陷,每次执行程序得到的 ...

  6. 前端PHP入门-010-内部函数

    内部函数,是指在函数内部又声明了一个函数. 注意事项: 内部函数名,不能是已存在的函数名 假设在函数a里面定义了一个内部函数,不能调用两次函数a. <?php function foo() { ...

  7. [吴恩达机器学习笔记]12支持向量机2 SVM的正则化参数和决策间距

    12.支持向量机 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考资料 斯坦福大学 2014 机器学习教程中文笔记 by 黄海广 12.2 大间距的直观理解- Large Margin I ...

  8. springboot+spring session+redis+nginx实现session共享和负载均衡

    环境 centos7. jdk1.8.nginx.redis.springboot 1.5.8.RELEASE session共享 添加spring session和redis依赖 <depen ...

  9. nginx 初探 之反向代理

    首先要解释的是什么叫做反向代理? 平时我们浏览网页可以输入网址直接访问,  但如果访问国外的网站,  可能就没那么简单('中国特色'),  这时候我们需要配置一个代理服务器, 然后通过此服务器中转来访 ...

  10. HDU 2588 思维 容斥

    求满足$1<=X<=N ,(X,N)>=M$的个数,其中$N, M (2<=N<=1000000000, 1<=M<=N)$. 首先,假定$(x, n)=m$ ...