ftp服务器/客户端程序

1 程序要求
在这里插入图片描述
2 程序目录及文件结构
说明:
A 程序运行前,需事先建立好用户json文件及测试用用户家目录
B 先运行服务器端程序,后运行客户端程序
C 由于时间不充裕,最后一条要求未能完成,还有就是尽量输入正确的文件名或路径,否则程序会跳出(除输入多个“\”会产生问题外,还没有测试其它特殊符号)
在这里插入图片描述
3 服务器代码

#!/usr/bin/env python
# --author lisheng--

import socketserver
import json
import os
import sys
import hashlib

base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
os.sys.path.append(base_path)

class ftpserver(socketserver.BaseRequestHandler):
    def handle(self):
        """
        用户加密认证及所有命令的解耦方法,每次连接时会刷新与客户端交互用的use_info字典
        :return: none
        """
        self.user_info = {}     #服务器端的临时字典
        print("主机连入:", self.client_address)
        while True:
            try:
                x = self.ser_auth()
                if x:
                    self.user_info = x  # md5验证
                    self.user_info["path"] = r"%s\%s" % (os.path.dirname(os.path.dirname(os.path.abspath(__file__))),x["name"])
                    self.user_info["now_path"] = r"%s\%s" % (os.path.dirname(os.path.dirname(os.path.abspath(__file__))),x["name"])
                    while True:
                        try:
                            file_dict = json.loads(self.request.recv(1024).decode())  # 收交互用的临时字典
                            xcom = getattr(self, file_dict["active"].split()[0])
                            xcom(file_dict)
                        except Exception as e:
                            print("err", e)
                            break
            except Exception as e:
                print("err", e)
                break

    def jisuan(self,dir):
        """
        计算目录空间大小的方法
        :param dir: 要计算大小的目录名
        :return: 返回计算结果
        """
        mulu_size = 0
        for root, dirs, list in os.walk(dir):
            mulu_size += sum([os.stat(os.path.join(root, name)).st_size for name in list])
        print(dir, "size:", mulu_size)
        return mulu_size

    def put(self, file_dict):
        """
        客户端向服务器端上传文件的方法
        :param file_dict: 解耦方法处获得的用户字典
        :return:
        """
        if os.path.isfile("%s\%s" %(self.user_info["now_path"],file_dict["filename"])):
            file_dict["status_code"] = "201"    #文件已存在
            self.request.send(json.dumps(file_dict).encode("utf-8"))  # 发送字典
        else:
            now_size = self.jisuan(self.user_info["path"]) + file_dict["size"]
            print("user space:",self.user_info["space"],"now size:",now_size)
            if now_size < self.user_info["space"]:
                file_dict["status_code"] = "200"    #可以开始上传
                self.request.send(json.dumps(file_dict).encode("utf-8"))  # 发送字典
                f = open("%s\%s" %(self.user_info["now_path"],file_dict["filename"]), mode="ab")
                recv_file_len = 0
                # print("字典里文件大小的数据类型",type(file_dict["size"]))
                while recv_file_len < file_dict["size"]:
                    if file_dict["size"] - recv_file_len > 1024:
                        recv_file = self.request.recv(1024)
                        f.write(recv_file)  # 收文件
                        sys.stdout.write("*")
                        f.flush()
                        recv_file_len += len(recv_file)
                    else:
                        recv_file = self.request.recv(file_dict["size"] - recv_file_len)
                        f.write(recv_file)
                        sys.stdout.write("*")
                        f.flush()
                        recv_file_len += len(recv_file)  # 收文件
                print("\n收到文件大小,传来的字典中的文件大小:", recv_file_len, file_dict["size"])
                f.flush()
                f.close()
                print("接收文件完成")
            else:
                file_dict["status_code"] = "202"    #空间不足
                self.request.send(json.dumps(file_dict).encode("utf-8"))  # 发送字典

    def get(self, file_dict):
        """
        客户端下载方法
        :param file_dict: 解耦方法处获得的用户字典
        :return:
        """
        file_name = "%s\%s" %(self.user_info["now_path"],file_dict["filename"])
        print(file_name)
        if os.path.isfile(file_name):
            file_dict["status_code"] = "300"
            file_dict["size"] = os.stat(file_name).st_size
            self.request.send(json.dumps(file_dict).encode("utf-8"))  # 发送字典
            ack = self.request.recv(1024)
            print("ack:", ack)
            f = open(file_name, "rb")
            print("开始发送文件")
            for i in f:
                self.request.send(i)  # 发送文件
                sys.stdout.write("*")
            f.close()
            print("\n文件传输完成")
        else:
            file_dict["status_code"] = "301"
            self.request.send(json.dumps(file_dict).encode("utf-8"))  # 发送字典

    def dir(self, file_dict):
        """
        客户端查看当前家目录内文件列表的方法
        :param file_dict: 解耦方法处获得的用户字典
        :return:
        """
        print("命令:", file_dict["active"])
        print(self.user_info["now_path"])
        com_result = os.popen("%s %s" %(file_dict["active"],self.user_info["now_path"])).read()
        if com_result:
            file_dict["status_code"] = "400"
            file_dict["res_len"] = len(com_result.encode("utf-8"))
            self.request.send(json.dumps(file_dict).encode("utf-8"))  # 发送状态码及命令字典
            self.request.recv(1024)
            self.request.send(com_result.encode("utf-8"))
            print("from dir:", com_result)
        else:
            file_dict["status_code"] = "401"
            self.request.send(json.dumps(file_dict).encode("utf-8"))  # 发送状态码及命令字典

    def chdir(self, file_dict):
        """
        查看当前客户端的家目录信息
        :param file_dict: 解耦方法处获得的用户字典
        :return:
        """
        print("命令:", file_dict["active"])
        now_path = "\%s\%s" %(self.user_info["name"],os.path.relpath(self.user_info["now_path"],self.user_info["path"]))
        self.request.send(now_path.encode("utf-8"))
        print("now_path:",now_path)

    def cd(self, file_dict):
        """
        修改家目录路径,仅能在家目录内修改路径
        :param file_dict: 解耦方法处获得的用户字典
        :return:
        """
        path = file_dict["active"].split( )[1]
        print("命令:",file_dict["active"])
        if path == "..":
            if self.user_info["path"] in os.path.dirname(self.user_info["now_path"]):
                self.user_info["now_path"] = os.path.dirname(self.user_info["now_path"])
                file_dict["status_code"] = "500"  # 成功修改路径
                print("path",self.user_info["path"],"now_path",self.user_info["now_path"])
            elif len(self.user_info["path"]) > len(os.path.dirname(self.user_info["now_path"])):
                file_dict["status_code"] = "502"  # 您没有权限
        else:
            if os.path.exists("%s%s" %(self.user_info["now_path"],path)):
                self.user_info["now_path"] += path
                file_dict["status_code"] = "500"  # 成功修改路径
                print("path",self.user_info["path"],"now_path",self.user_info["now_path"])
            else:
                file_dict["status_code"] = "501"  # 路径不存在
        self.request.send(json.dumps(file_dict).encode("utf-8"))  # 发送状态码及命令字典

    def ser_auth(self):
        """
        客户端登陆前的加密认证
        :return: 返回用户信息字典
        """
        cli_auth = json.loads(self.request.recv(1024).decode())
        print(cli_auth)
        if os.path.isfile("%s\conf\%s.json" %(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),cli_auth[0])):
            # self.request.send(b"101")   #有用户,判断是否正确
            f = open("%s\conf\%s.json" % (os.path.dirname(os.path.dirname(os.path.abspath(__file__))), cli_auth[0]),"r")
            user = json.loads(f.read())
            print(user)
            f.close()
            u_p = hashlib.md5()
            u_p.update(b"%s%s" % (user["name"].encode("utf-8"), user["password"].encode("utf-8")))
            md5_res = u_p.hexdigest()
            print(md5_res)
            if cli_auth[1] == md5_res:
                self.request.send(b"100")       #验证通过
                return user
            else:
                self.request.send(b"102")       #密码错误
        else:
            self.request.send(b"101")   #无此用户

if __name__ == "__main__":
    host, port = "0.0.0.0", 9999
    ser = socketserver.ThreadingTCPServer((host, port), ftpserver)
    ser.serve_forever()




4 客户端代码

#!/usr/bin/env python
#Author:lisheng

import socket
import os
import json
import sys
import hashlib

class client(object):
    def __init__(self):
        self.con = socket.socket()
        self.file_dict = {}

    def connet(self,ip,port):
        """
        连接ftp服务器
        :param ip: ftp服务器地址
        :param port: ftp服务器端口
        :return: none
        """
        self.con.connect((ip,port))

    def help(self):
        """
        帮助信息
        :return: none
        """
        print("""
        dir
        chdir
        get
        cd
        put
        """)

    def auth_cli(self):
        """
        登陆前的加密认证
        :return: 认证通过后返回用户名
        """
        i = 1
        while i < 4:
            au = hashlib.md5()
            name = input("name:")
            if name == "q":break
            password = input("password:")
            au.update(name.encode("utf-8"))
            au.update(password.encode("utf-8"))
            au_md5 = au.hexdigest()
            cli = []
            cli.append(name)
            cli.append(au_md5)
            self.con.send(json.dumps(cli).encode("utf-8"))  # 发送用户名,用作目录名
            code = self.con.recv(1024).decode()  # 防粘包
            if code == "100":
                print("验证通过")
                i = 4
                return name
            elif code == "101":
                print("无此用户")
            elif code == "102":
                print("密码错误")
            print(i)
            i += 1

    def action(self):
        """
        所有命令的解耦方法,每次连接时会刷新与服务器交互用的file_dict字典
        :return: none
        """
        while True:
            x = self.auth_cli()
            if x:
                self.file_dict["name"] = x
                while True:
                    self.file_dict = {
                        "filename": None,
                        "size": None,
                        "override": None,
                        "active": None,
                        "status_code": None,
                        "res_len": None,
                        "name": None
                    }
                    commond = r"%s" % input("%s # " % (self.file_dict["name"])).strip()
                    if len(commond) == 0:continue
                    elif commond == "q":break
                    elif hasattr(self,commond.split( )[0]):
                        self.file_dict["active"] = commond
                        xcom = getattr(self,commond.split( )[0])
                        xcom()
                    else:
                        print("请检查命令是否正确")
                        self.help()
            else:
                print("bye")
                break

    def put(self):
        """
        上传文件方法。上传超过用户空间配额时,会阻止上传
        :return: none
        """
        if os.path.isfile(self.file_dict["active"].split()[1]):
            self.file_dict["filename"] = self.file_dict["active"].split()[1]
            self.file_dict["size"] = os.stat(self.file_dict["active"].split()[1]).st_size
            self.file_dict["active"] = "put"
            self.con.send(json.dumps(self.file_dict).encode("utf-8"))  # 发送文件字典
            ser_dict = json.loads(self.con.recv(1024).decode())      #接收字典
            if ser_dict["status_code"] == "200":
                f = open(self.file_dict["filename"],"rb")
                print("开始发送文件")
                for i in f:
                    self.con.send(i)        #发送文件
                    sys.stdout.write("*")
                f.close()
                print("\n文件传输完成")
            elif ser_dict["status_code"] == "202":
                print("服务器上空间不足")
            elif ser_dict["status_code"] == "201":
                print("服务器上文件重名")
        else:
            print("请检查文件名是否正确")

    def get(self):
        """
        下载文件
        :return: none
        """
        if os.path.isfile(self.file_dict["active"].split()[1]):
            print("该文件已存在")
        else:
            self.file_dict["filename"] = self.file_dict["active"].split()[1]
            self.file_dict["active"] = "get"
            self.con.send(json.dumps(self.file_dict).encode("utf-8"))  # 发送文件字典
            ser_dict = json.loads(self.con.recv(1024).decode())  # 接收字典
            if ser_dict["status_code"] == "300":
                self.con.send(b"ack")
                recv_file_len = 0
                f = open(ser_dict["filename"],"ab")
                # print("字典里文件大小的数据类型",type(file_dict["size"]))
                while recv_file_len < ser_dict["size"]:
                    if ser_dict["size"] - recv_file_len > 1024:
                        recv_file = self.con.recv(1024)
                        f.write(recv_file)  # 收文件
                        sys.stdout.write("*")
                        f.flush()
                        recv_file_len += len(recv_file)
                    else:
                        recv_file = self.con.recv(ser_dict["size"] - recv_file_len)
                        f.write(recv_file)  # 收文件
                        sys.stdout.write("*")
                        f.flush()
                        recv_file_len += len(recv_file)  # 收文件
                print("\n收到文件大小,传来的字典中的文件大小:", recv_file_len, ser_dict["size"])
                f.flush()
                f.close()
                print("接收文件完成")
            else:
                print(ser_dict["status_code"])
                print("服务器上无此文件")

    def dir(self):
        """
        查看用户当前在服务器上的家目录,只能看到家目录的相对路径,无法看到服务器上的其它路径
        :return: none
        """
        self.con.send(json.dumps(self.file_dict).encode("utf-8"))  # 发送文件字典
        ser_dict = json.loads(self.con.recv(1024).decode())   #接收状态码及命令字典
        self.con.send(b"ack")
        if ser_dict["status_code"] == "400":
            rec_com = "".encode("utf-8")
            while len(rec_com) < ser_dict["res_len"]:
                if ser_dict["res_len"] - len(rec_com) > 1024:
                    rec_com += self.con.recv(1024)
                else:
                    rec_com += self.con.recv(ser_dict["res_len"] - len(rec_com))
            print(rec_com.decode(),"服务器返回的命令大小(编码后):",ser_dict["res_len"],"收到的命令大小(编码后):",len(rec_com),"收到的命令大小(解码后):",len(rec_com.decode()))
        else:
            print("命令错误或没有结果")

    def chdir(self):
        """
        修改用户路径,只能在家目录里切换目录
        :return:none
        """
        self.con.send(json.dumps(self.file_dict).encode("utf-8"))  # 发送文件字典
        serv_res = self.con.recv(1024).decode() #接收当前目录信息
        print(serv_res)

    def cd(self):
        """
        修改用户在服务器上的相对家目录
        :return:none
        """
        if " " in self.file_dict["active"].strip():
            self.con.send(json.dumps(self.file_dict).encode("utf-8"))  # 发送文件字典
            ser_dict = json.loads(self.con.recv(1024).decode())  # 接收状态码及命令字典
            print("更改路径后的状态码:",ser_dict["status_code"])
            if ser_dict["status_code"] == "501":
                print("路径不存在")
            elif ser_dict["status_code"] == "502":
                print("您没有权限")
            elif ser_dict["status_code"] == "500":
                print("路径修改成功")
            else:
                print("有问题")

if __name__ == "__main__":
    a = client()
    a.connet("localhost",9999)
    a.action()


5 用户文件格式为.json格式,内容如下
{“name”: “ls”, “password”: “1234”, “space”: 102400000}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自己写的ftp服务端程序代码,支持{"USER", do_user }, {"PASS", do_pass }, {"CWD", do_cwd }, {"XCWD", do_cwd }, {"CDUP", do_cdup }, {"REIN", do_rein },//重新初始化,此命令终止USER,重置所有参数,控制连接仍然打开,用户可以再次使用USER命令 {"QUIT", do_quit }, /*------------传输参数命令------------*/ {"PORT", do_port },//数据端口,主要向服务发送客户数据连接的端口 //格式为PORT h1,h2,h3,h4,p1,p2,其中32位的IP地址用h1,h2,h3,h4表示,16位的TCP端口号用p1,p2表示 {"PASV", do_pasv },//此命令要求服务数据传输进程在指定的数据端口侦听,进入被动接收请求的状态 {"TYPE", do_type },//文件类型,可指定ASCII码、EBCDIC码、Image、本地类型文件等参数 /*------------服务命令----------------*/ {"RETR", do_retr },//下载文件 {"STOR", do_stor },//上传 {"APPE", do_appe },//上传,如文件已存在,数据附加到尾部 {"REST", do_rest },//重新开始 {"RNFR", do_rnfr }, {"RNTO", do_rnto },//重命名文件或目录 {"ABOR", do_abor },//异常终止 {"DELE", do_dele },//删除文件 {"RMD", do_rmd },//删除目录 {"XRMD", do_rmd }, {"MKD", do_mkd },//新建目录 {"XMKD", do_mkd }, {"PWD", do_pwd },//打印当前目录 {"XPWD", do_pwd }, {"LIST", do_list },//列目录详细清单 {"NLST", do_nlst },//列目录短清单 {"SYST", do_syst },//获取系统信息 {"STAT", do_stat },//返回服务状态 {"SIZE", do_size },//获得文件大小 {"HELP", do_help }, {"NOOP", do_noop }, {"SITE", do_site }, }等命令
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值