python多人登录上传下载其他功能后续加入

要求做一个简单的ftp系统,功能要求如下:

1、多用户同时登陆

2、用户登录,加密认证

3、上传、下载文件,保证文件一致性

4、传输过程中实现进度条

5、不同用户目录不同,且只能访问用户自己目录

6、对用户进行磁盘调配、不同用户调配可以不同

7、用户登录server后可在其权限目录下子目录切换

8、查看当前用户目录下文件,新建文件夹

9、删除文件和空文件夹

10、充分使用面向对象知识

11、支持断点续传


其中5/6、7/8/9暂时缺失,用户登录没有做验证,后面做验证

系统结构如下:

image.png

bin-》index为入口文件

代码如下:

from core.handle import run
if __name__ == '__main__':
    run()

config存放配置文件,基本上暂时用到很少,sys内容如下:

IP_PORT = ('10.10.10.103', 9998)
UPLOAD_PATH = '../uploads'

core中存放核心文件,代码量较少,都放到了handle中,代码如下:

import socketserver
import pickle
import os
from config import sys
import struct
import hashlib
'''
功能列表
'''
class Action:
    def __init__(self, conn):
        # 客户端连接句柄
        self.conn = conn
        self.username = None
        self.pwd = None
    def login(self, username, pwd):
        self.username = username
        self.pwd = pwd

    # md5加密文件
    def file_md5(self, file_path):
        md5 = hashlib.md5(b'tony')
        with open(file_path, 'rb') as f:
            for line in f:
                md5.update(line)
        return md5.hexdigest()

    def upload(self, file_info):
        # 文件上传目录
        file_dir = os.path.join(sys.UPLOAD_PATH, self.username)
        # 文件名称
        file_name = os.path.basename(file_info['file_path'])
        # 已上传文件路径
        file_md5_path = os.path.join(file_dir, file_info['file_md5'])
        print(file_info, file_dir, '执行了服务器上传')
        # 用户目录不存在则创建用户目录
        if not os.path.exists(file_dir):
            os.makedirs(file_dir)
        # 获取已上传的文件大小,如果没有上传则已上传大小为0
        exists_size = os.path.getsize(file_md5_path) if os.path.exists(file_md5_path) else 0
        # 发送已上传文件大小
        self.conn.send(struct.pack('i', exists_size))
        with open(file_md5_path, 'ab') as f:
            while exists_size < file_info['file_size']:
                data = self.conn.recv(1024)
                exists_size += len(data)
                f.write(data)
        os.rename(file_md5_path, os.path.join(file_dir, file_name))
    # 下载文件
    def download(self, cmd_info):

        # 文件下载目录
        file_dir = os.path.join(sys.UPLOAD_PATH, self.username)
        # 获取下载文件路径
        file = os.path.join(file_dir, cmd_info['file_path'])
        print(cmd_info, file_dir, file, os.path.exists(file))
        # 判断要下载的文件是否存在
        if not os.path.exists(file):
            self.conn.send(pickle.dumps({'code':1004, 'msg':'您要下载的文件不存在'}))
        else:
            file_size = os.path.getsize(file)
            print(file_size)
            # 发送给客户端下载文件信息,用来获取文件已下载大小
            self.conn.send(pickle.dumps({'file_size':file_size, 'file_md5':self.file_md5(file)}))
            # 接收客户端发来的文件已下载大小
            exists_size = struct.unpack('i', self.conn.recv(4))[0]
            # 发送文件
            with open(file, 'rb') as f:
                # 移动文件指针至已下载过的位置
                f.seek(exists_size)
                while exists_size < file_size:
                    # 每次读取1024大小
                    data = f.read(1024)
                    # 累加到已下载大小
                    exists_size += len(data)
                    # 发送当次读取数据
                    self.conn.send(data)

'''
主程序
'''
class Server(socketserver.BaseRequestHandler):
    def handle(self):
        print(self.client_address, '已经成功连接至服务器……')
        action = Action(self.request)
        if action.username:
            self.request.send(pickle.dumps({'code': 1000, 'msg': '您已登录,请输入您要执行的命令:'}))
        else:
            self.request.send(pickle.dumps({'code': 1001, 'msg': '请输入用户名和密码:'}))
            pickle_user = pickle.loads(self.request.recv(1024))
            action.login(pickle_user['username'], pickle_user['pwd'])
        while 1:
            try:
                cmd_info = pickle.loads(self.request.recv(1024))
                if hasattr(action, cmd_info['cmd']):
                    getattr(action, cmd_info['cmd'])(cmd_info)
            except Exception as e:
                break
        self.request.close()
        print(self.client_address, '已经断开服务器……')
def run():
    server = socketserver.ThreadingTCPServer(sys.IP_PORT, Server)
    server.serve_forever()
    print('系统开始运行……')

log日志目录暂时没写入,uploads为上传文件夹根目录,里面存放各用户上传的数据。

临时客户端代码直接写到了client文件中,代码如下:

import socket
import pickle
import os
import hashlib
import struct
from config import sys
client = socket.socket()
client.connect(sys.IP_PORT)
# md5加密文件
def file_md5(file_path):
    md5 = hashlib.md5(b'tony')
    with open(file_path, 'rb') as f:
        for line in f:
            md5.update(line)
    return md5.hexdigest()

# 上传进度条
def upload_progress(current_size, total_size):
        print('\r总大小:{}【{:50}】已上传:{:.2f}%'.format(total_size, '=' * (current_size * 50 // total_size), current_size/total_size*100), end='')
# 发送文件内容
def send_file(file_path, exists_size = 0):
    # 获取文件大小
    file_size = os.path.getsize(file_path)
    # 读取文件
    with open(file_path, 'rb') as f:
        # 指针移动到已经上传大小的位置
        f.seek(exists_size)
        while exists_size < file_size:
            # 每次读取1024
            data = f.read(1024)
            # 累计读取的大小
            exists_size += len(data)
            # 发送当次循环数据
            client.send(data)
            # 显示进度条
            upload_progress(exists_size, file_size)

def upload(file_path):
    # 封装文件信息
    cmd_info = {'cmd' : 'upload', 'file_path':file_path, 'file_size':os.path.getsize(file_path), 'file_md5':file_md5(file_path)}
    print(cmd_info)
    # 序列化文件信息
    cmd_info_picle = pickle.dumps(cmd_info)
    # 发送文件信息
    client.send(cmd_info_picle)
    # 接收已上传大小
    exists_size = struct.unpack('i', client.recv(1024))[0]
    # 发送文件
    send_file(file_path, exists_size)
def download(file_path):
    # 封装下载信息
    cmd_info = {'cmd': 'download', 'file_path': file_path}
    # 序列化下载信息
    cmd_info_picle = pickle.dumps(cmd_info)
    # 发送下载信息
    client.send(cmd_info_picle)
    # 接收服务器返回响应
    responses = pickle.loads(client.recv(1024))
    print(responses)

    # 判断文件是否存在
    if 'code' in responses and responses['code'] == 1004:
        print(responses['msg'])
    else:
        exists_size = os.path.getsize(responses['file_md5']) if os.path.exists(responses['file_md5']) else 0
        client.send(struct.pack('i', exists_size))
        # 接收文件
        with open(responses['file_md5'], 'ab') as f:
            while exists_size < responses['file_size']:
                # 每次接收的数据
                data = client.recv(1024)
                # 累加到总接收大小
                exists_size += len(data)
                # 写入文件
                f.write(data)
                upload_progress(exists_size, responses['file_size'])
        os.rename(responses['file_md5'], cmd_info['file_path'])
ret = pickle.loads(client.recv(1024))
print(ret)
if ret['code'] == 1001:
    username = input('请输入用户名:')
    pwd     = input('请输入密码:')
    client.send(pickle.dumps({'username':username, 'pwd':pwd}))
while 1:
    print()
    print('----------------------------------')
    handle, file_path = input('请输入您要执行的命令:').split('|')
    if handle == 'upload':
        upload(file_path)
    if handle == 'download':
        download(file_path)

client.close()

后期整理以上代码