作业:开发一个支持多用户在线的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. IBatis Map报错10.1

    检查 providers.config 把没用的给关闭掉即可

  2. Codeforces 906B. Seating of Students(构造+DFS)

    行和列>4的可以直接构造,只要交叉着放就好了,比如1 3 5 2 4和2 4 1 3 5,每一行和下一行用不同的方法就能保证没有邻居. 其他的可以用爆搜,每次暴力和后面的一个编号交换并判断可行性 ...

  3. 【CF601C】Kleofáš and the n-thlon

    Portal -->CF601C Description 大概是说\(m\)个人参加\(n\)场比赛,每场一人有一个排名,每场没有两个人排名相同,一个人最后的得分是\(n\)场比赛的排名相加,现 ...

  4. Selective Search for Object Recognition

    http://blog.csdn.net/charwing/article/details/27180421 Selective Search for Object Recognition 是J.R. ...

  5. 04-树5. File Transfer--并查集

    对于一个集合常见的操作有:判断一个元素是否属于一个集合:合并两个集合等等.而并查集是处理一些不相交集合(Disjoint Sets)的合并及查询问题的有利工具. 并查集是利用树结构实现的.一个集合用一 ...

  6. 详解 Cookie 和 Session 关系和区别

    在技术面试中,经常被问到“说说Cookie和Session的区别”,大家都知道,Session是存储在服务器端的,Cookie是存储在客户端的,然而如果让你更详细地说明,你能说出几点?今天个推君就和大 ...

  7. python使用pwd和grp操作unix用户及用户组

    1.pwd模块 pwd模块提供了一个unix密码数据库即/etc/passwd的操作接口,这个数据库包含本地机器用户帐户信息 常用操作如下: pwd.getpwuid(uid):返回对应uid的示例信 ...

  8. OpenCV---ROI(region of interest)和泛洪填充

    一:ROI 感兴趣区(Region of Interest,ROIs) 是图像的一部分,它通过在图像上选择或使用诸如设定阈值(thresholding) 或者从其他文件(如矢量> 转换获得等方法 ...

  9. [LeetCode] 31. Next Permutation ☆☆☆

    Implement next permutation, which rearranges numbers into the lexicographically next greater permuta ...

  10. [LeetCode] 23. Merge k Sorted Lists ☆☆

    Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. 解 ...