作业:开发一个支持多用户在线的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. CF449,急需提高姿势水平

    这可能是我第1场只出1题的div1? A 鬼畜题? 我的dev没有字符统计功能,于是只好扔到notepad++上.数错好几次.题目本身没什么特别的地方. B 当时一直没想出来QAQ 题目中说m> ...

  2. 数字表格(product)

    Portal -->broken qwq Description ​  求\(\prod\limits_{i=1}^n\prod\limits_{j=1}^m f[gcd(i,j)](mod\ ...

  3. Web前端之HTML详解20180327

    一.html概述 html就是超文本标记语言的简写,是最基础的网页语言.html通过标签来定义语言,代码都是由标签所组成. 1.html代码从<html>开始</html>结束 ...

  4. tableau学习

    一.维度 度量 1. 连接到新数据源时,Tableau 会将该数据源中的每个字段分配给“数据”窗格的“维度”区域或“度量”区域,具体情况视字段包含的数据类型而定. 例如: 2. tableau中的字段 ...

  5. 51nod 1684 子集价值

    lyk最近在研究位运算. 它发现除了xor,or,and外还有很多运算. 它新定义了一种运算符“#”. 具体地,可以由4个参数来表示. ai,j表示 i#j. 其中i,j与a的值均∈[0,1]. 当然 ...

  6. JSP2 特性

    JSP2 新特性 1.直接配置 JSP 属性 2.表达式语言 3.简化的自定义标签 API 4.Tag 文件语法 如果要使用 JSP2 语法,web.xml 文件必须使用 Servlet2.4 以上版 ...

  7. poppo大根堆的原理与实现。

    大根堆的定义:1 大根堆是一个大根树 2 大根堆是一个完全二叉树 所以大根堆用数组表示是连续的,不会出现空白字段. 对于大根堆的插入 对于大根堆的插入,可以在排序前确定大根堆的形状,可以确定元素5从位 ...

  8. ural 1297 后缀数组 最长回文子串

    https://vjudge.net/problem/URAL-1297 题意: 给出一个字符串求最长回文子串 代码: //论文题,把字符串反过来复制一遍到后边,中间用一个没出现的字符隔开,然后就是枚 ...

  9. Python urllib urlretrieve函数解析

    Python urllib urlretrieve函数解析 利用urllib.request.urlretrieve函数下载文件 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文献 Ur ...

  10. C++对象与其第一个非静态成员地址相同

    由于对象的内存空间里是按顺序依次存储了它的非静态成员变量,因此对象和它的第一个非静态成员变量的地址是相同的: class A { private: int i; } int main() { A a; ...