python多用户数据库_(转)Python开发程序:支持多用户在线的FTP程序

原文链接:http://www.itnose.net/detail/6642756.html

作业:开发一个支持多用户在线的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 系统交互码

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 == "400":

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 == "201": #命令可执行

filename = command.split()[1]

# 文件名存在,判断是否续传

if os.path.isfile(filename):

revice_size = os.stat(filename).st_size #文件已接收大小

self.client.sendall("403".encode())

response = self.client.recv(1024)

self.client.sendall(str(revice_size).encode()) #发送已接收文件大小

status_code = self.client.recv(1024).decode()

# 文件大小不一致,续传

if status_code == "205":

print("继续上次上传位置进行续传")

self.client.sendall("000".encode())

# 文件大小一致,不续传,不下载

elif status_code == "405":

print("文件已经存在,大小一致")

return

# 文件不存在

else:

self.client.sendall("402".encode())

revice_size = 0

file_size = self.client.recv(1024).decode() #文件大小

file_size = int(file_size)

self.client.sendall("000".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 == "202":

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 == "203":

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 == "201": # 命令可执行

self.client.sendall("000".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 == "201": #命令可执行

self.client.sendall("000".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() #开始连接

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()

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":"123456","lzl":"8888","eric":"6666"}

#磁盘配额 10M

LIMIT_SIZE = 10240000

#ftp服务端口

IP_PORT = ("0.0.0.0",9999)

6、database用户数据库(系统初始化自动生成)

{"username": "alex", "password": "e10adc3949ba59abbe56e057f20f883e", "limitsize": 10240000, "homepath": "C:\\Users\\L\\PycharmProjects\\s14\\homework\\Day8\\FTP\\ftpserver\\home\\alex"}

{"username": "eric", "password": "e9510081ac30ffa83f10b68cde1cac07", "limitsize": 10240000, "homepath": "C:\\Users\\L\\PycharmProjects\\s14\\homework\\Day8\\FTP\\ftpserver\\home\\eric"}

{"username": "alex", "password": "e10adc3949ba59abbe56e057f20f883e", "limitsize": 10240000, "homepath": "C:\\Users\\L\\PycharmProjects\\s14\\homework\\Day8\\FTP\\ftpserver\\home\\alex"}

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()

#!/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 == "400":

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("401".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 "200",result

else:return "400",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("201".encode()) #命令可执行

file_size = os.stat(file_path).st_size # 文件总大小

status_code = self.conn.recv(1024).decode()

# 客户端存在此文件

if status_code == "403":

self.conn.sendall("000".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("205".encode())

file_size -= has_send_size #续传文件大小

response = self.conn.recv(1024) # 等待响应

# 客户端文件完整不可续传、不提供下载

else:

self.conn.sendall("405".encode())

return

# 客户端不存在此文件

elif status_code == "402":

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("402".encode())

else:self.conn.sendall("401".encode())

def put(self,command):

'''上传文件'''

filename = command.split()[1]

file_path = self.current_path + r"\%s" % filename

self.conn.sendall("000".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("202".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("203".encode())

else:self.conn.sendall("404".encode())

def dir(self,command):

'''查看当前目录下的文件'''

if len(command.split()) == 1:

self.conn.sendall("201".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("401".encode())

def pwd(self,command):

'''查看当前用户路径'''

if len(command.split()) == 1:

self.conn.sendall("201".encode())

response = self.conn.recv(1024)

send_data = self.current_path

self.conn.sendall(send_data.encode())

else:self.conn.sendall("401".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("201".encode())

response = self.conn.recv(1024)

os.popen("mkdir %s"%dir_path)

else:self.conn.sendall("403".encode())

else:self.conn.sendall("401".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("201".encode())

response = self.conn.recv(1024)

self.current_path = os.path.dirname(self.current_path) #返回上一级目录

elif os.path.isdir(dir_path) :

self.conn.sendall("201".encode())

response = self.conn.recv(1024)

if dir_name != "." and dir_name != "..":

self.current_path += r"\%s"%dir_name #切换目录

else:self.conn.sendall("402".encode())

else:self.conn.sendall("401".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

效果图:

下载:

续传:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值