python做一个简单的ftp服务器核心代码

作业要求

1. 多用户同时登陆 v
2. 用户登陆,加密认证 
3. 上传/下载文件,保证文件一致性
4. 传输过程中现实进度条
5. 不同用户家目录不同,且只能访问自己的家目录
6. 对用户进行磁盘配额、不同用户配额可不同
7. 用户登陆server后,可在家目录权限下切换子目录
8. 查看当前目录下文件,新建文件夹
9. 删除文件和空文件夹
10. 充分使用面向对象知识
11. 支持断点续传

客户端及服务端的目录设计

服务端

ftp_server
    bin
    db // 用户信息
    home // 家目录
        用户1
        用户2

客户端

ftp_client
    bin
    conf # 文件下载的文件路径等相关配置

服务端执行代码

利用socketserver模块来实现多线程的服务器

import socketserver
import hashlib
import struct
import json
import os

BASE_PASH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
HOME_PASH = os.path.join(BASE_PASH,"home")


class MyHandler(socketserver.BaseRequestHandler):


    home_path = None
    then_path = None
    show_path = None
    code_info={200:"ok",
               400:"command not found",
               401: "name or pwd error",
               402: "parameter error",
               }

    def handle(self):
        try:
            while 1:
                msg = self._parser()
                cmd = msg.get("cmd")
                if hasattr(self, cmd):
                    getattr(self, cmd)(msg)
                else:
                    self._send_msg(code=400)
        except ConnectionResetError:
            self.request.close()

    def _parser(self):
            struct_info = self.request.recv(4)
            msg_bytes_len = struct.unpack("i", struct_info)[0]
            msg = str(self.request.recv(msg_bytes_len), encoding="utf8")
            return json.loads(msg)

    def _send_msg(self,**kwargs):
        msg_bytes = bytes(json.dumps(kwargs), encoding="utf8")
        self.request.send(struct.pack("i", len(msg_bytes)))
        self.request.send(msg_bytes)

    def login(self, msg):
        name = msg.get("name")
        pwd = msg.get("pwd")
        if name=="alex" and pwd=="4399":
            self.home_path = os.path.join(HOME_PASH, name)
            self.then_path = self.home_path
            self.show_path = self.then_path.replace(self.home_path,"\\")
            self._send_msg(code=200, name=name, show_path = self.show_path)
        else:
            self._send_msg(code=401)

    def ls(self, msg):
        dir_lis = os.listdir(self.then_path)
        if dir_lis:
            dir_str = "    ".join(dir_lis)
            self._send_msg(code=200, dir_str=dir_str)
        else:
            self._send_msg(code=200, dir_str="")

    def cd(self, msg):
        path = msg.get("args")[0]
        if path == "/":
            pwd = self.home_path
        elif path.startswith("/"):
            print(self.home_path)
            pwd = os.path.join(self.home_path, path[1:])
        else:
            pwd = os.path.join(self.then_path, path)
            
        if os.path.isdir(pwd):
            self.then_path = pwd
            self.show_path = self.then_path.replace(self.home_path,"")
            self._send_msg(code=200, show_path=self.show_path)
        else:
            self._send_msg(code=402)

    def pull(self, msg):
        file_name = msg.get("file_name")
        file_path = os.path.join(self.then_path, file_name)
        if os.path.isfile(file_path):
            print("is file")
            load_size = msg.get("load_size")
            all_size = os.path.getsize(file_path)
            md5 = self.get_md5(file_path)
            self._send_msg(code=200, file_name=file_name, all_size=all_size,
                           md5=md5)
            print(load_size, all_size)
            with open(file_path, "rb") as f:
                if load_size:
                    f.seek(load_size)
                for i in f:
                    self.request.send(i)
            
        else:
            self._send_msg(code=402)

    def get_md5(self, file_path):
        abs = hashlib.md5()
        with open(file_path, "rb") as f:
            for i in f:
                abs.update(i)
        return abs.hexdigest()

if __name__ == "__main__":
    sock = socketserver.ThreadingTCPServer(("127.0.0.1",8000),MyHandler)
    sock.serve_forever()

 

客户端代码

import hashlib
import socket
import struct
import json
import os

LOAD_PATH = os.path.dirname(os.path.abspath(__file__))

class Client:
    
    user = None
    show_path = None
    code_info = {200: "ok",
                 400: "command not found",
                 401: "name or pwd error",
                 402: "parameter error",
                 }

    def __init__(self):
        self.ip = "127.0.0.1"
        self.port = 8000
        
    def connect(self):
        self.request = socket.socket()
        self.request.connect((self.ip,self.port))
    
    def login(self):
        name, pwd = input("请输入账号"),input("请输入密码")
        self.connect()
        self.send_msg(cmd="login", name=name, pwd=pwd)
        
    def send_msg(self,**kwargs):
        msg_bytes = bytes(json.dumps(kwargs), encoding="utf8") 
        self.request.send(struct.pack("i", len(msg_bytes)))
        self.request.send(msg_bytes)
        
    def parser(self):
            struct_info = self.request.recv(4)
            msg_bytes_len = struct.unpack("i", struct_info)[0]
            msg = str(self.request.recv(msg_bytes_len), encoding="utf8")
            return json.loads(msg)
    def forever(self):
        while True:
            self.login()
            msg = self.parser()
            if msg.get("code") == 200:
                self.show_path = msg.get("show_path")
                self.name = msg.get("name")
                self.handle()
            else:
                print(self.code_info.get(msg.get("code")))

    def handle(self):
        while True:
            cmd_line = input("[%s@%s]#"%(self.name, self.show_path)).strip()
            if not cmd_line:continue
            cmd_list = cmd_line.split(" ", 1)
            cmd = cmd_list[0]
            if hasattr(self, cmd):
                getattr(self, cmd)(cmd_list)

    def ls(self, cmd_list):
        self.send_msg(cmd=cmd_list[0])
        msg = self.parser()
        code = msg.get("code")
        if code == 200:
            print(msg.get("dir_str"))

    def cd(self, cmd_list):
        self.send_msg(cmd=cmd_list[0], args=cmd_list[1:])
        msg = self.parser()
        code = msg.get("code")
        if code == 200:
            print(msg.get("show_path"))
            self.show_path = msg.get("show_path")
        else:
            print(self.code_info.get(code))

    def pull(self, cmd_list):
        file_name = cmd_list[-1]
        file_path = os.path.join(LOAD_PATH, file_name)
        if os.path.isfile(file_path):
            load_size = os.path.getsize(file_path)
        else:
            load_size = 0
        self.send_msg(cmd=cmd_list[0], file_name=file_name,
                      load_size=load_size)
        msg = self.parser()
        code = msg.get("code")
        if code == 200:
            md5 = msg.get("md5")
            all_size = msg.get("all_size")
            print(load_size, all_size)
            with open(file_path, "ab") as f:
                while load_size < all_size:
                    ret = self.request.recv(8096)
                    f.write(ret)
                    load_size += len(ret)
                    self.progress(load_size, all_size)
            local_md5 = self.get_md5(file_path)
            print(md5, local_md5)
            if md5 == local_md5:
                print("文件一致性校验通过")

        else:
            print(self.code_info.get(code))

    def push(self, cmd_list):
        pass

    def progress(self, load_size, all_size):
        i = load_size*100 // all_size
        char_num = i // 2  # 打印多少个'*'
        per_str = '\r%s%% : %s\n' % (i, '*' * char_num) if i == 100 else '\r%s%% : %s' % (i, '*' * char_num)
        print(per_str, end='', flush=True)

    def get_md5(self, file_path):
        abs = hashlib.md5()
        with open(file_path, "rb") as f:
            for i in f:
                abs.update(i)
        return abs.hexdigest()
if __name__ == "__main__":
    Client().forever()

  

  

 

断点续传思路

下载文件时,判断本地下载目录中是否存在同名文件(已下载), 若存在读取其大小, 将其大小写入pack的字典中

服务端判断是否已下载, 若存在,则将光标移动至已下载位置(f.seek(光标位置)), 在读取发送

客户端则使用追加的方式写入

posted @ 2018-09-07 11:09  瓜田月夜  阅读(468)  评论(0)    收藏  举报