用Python编写一个ftb

程序文件结构

具体代码实现

服务端

执行文件bin/ftb_server

import os,sys
PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))     #os.path.dirname()取文件的上一级目录路径
sys.path.append(PATH)         #导入的路径不在同一文件夹下,需要自己添加环境变量


from core import main


if __name__ == "__main__":
    main.ArgvHandler()

配置文件conf/setting

import os,sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


IP = "127.0.0.1"
PORT = 8080


ACCOUNT_PATH = os.path.join(BASE_DIR,"conf","accounts.cfg.txt")

主程序文件core/main

import os,sys

import optparse         #optparse模块用来给脚本传递命令参数,并用预定设置好的选项来解析传过来的参数
import socketserver
from conf import setting
from core import server

class ArgvHandler:
    def __init__(self):
        self.op = optparse.OptionParser()      #实例化
        # self.op.add_option("-s","--server",dest="server")
        # self.op.add_option("-P","--port",dest="port")       #用于增加设定输入的内容对应的关系
        options,args = self.op.parse_args()
        # print(options)       #将上面增加的设定做键值对应后封装成一个对象
        # print(options.server)  #调用这个对象中的属性
        # print(args)          #当输入的的值超出add_option的设置,把这些值放在一个列表中

        self.verify_args(options,args)

    def verify_args(self,options,args):
        cmd = args[0]

        if hasattr(self,cmd):
            func = getattr(self,cmd)
            func()

    def start(self):
        print("the server is working..")
        s = socketserver.ThreadingTCPServer((setting.IP,setting.PORT),server.ServerHandler)
        s.serve_forever()

服务端程序core/server

import socketserver
import json
import configparser
from conf import setting
import os


STATUS_CODE = {
    250:"Invalid cmd format,e.g:{'action':'get','filename':'test.py','size':344}",
    251:"Invalid cmd",
    252:"Invalid auth data",
    253:"Wrong username or password",
    254:"Passed authentication",
    255:"Filename doesn't provided",
    256:"File doesn't exist on server",
    257:"ready to send file",
    258:"md5 verification",
    800:"the file exist,but not enough,is continue",
    801:"the file exist",
    802:"ready to receive datas",
    900:"md5 valdate success",
}    #设置一个共用的状态码字典,服务端只需要回传一个状态码,客户端接收之后就知道对应的结果

class ServerHandler(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            data = self.request.recv(1024).strip()
            data = json.loads(data.decode("utf-8"))
            """
            {"action":"auth",
            "usename":"ss",
            "pwd":123
            }
            """
            if data.get("action"):        #对接收的内容进行验证
                if hasattr(self,data.get("action")):
                    func = getattr(self,data.get("action"))   #验证通过执行action键对应的函数
                    func(**data)
                else:
                    print("Invalid cmd")
            else:
                print("not")

    def send_response(self,state_code):
        response = {"status_code":state_code}       #将状态码回传给客户端
        self.request.sendall(json.dumps(response).encode("utf-8"))

    def auth(self,**data):
        username = data["username"]
        password = data["password"]

        user = self.authenticate(username,password)
        if user:       #将客户端传输的数据与数据库中的内容匹配判断,根据结果回传状态码
            self.send_response(254)
        else:
            self.send_response(253)


    def authenticate(self,user,pwd):
        cfg = configparser.ConfigParser()    #调用数据库
        cfg.read(setting.ACCOUNT_PATH)       #读取数据库中的内容

        if user in cfg.sections():
            if cfg[user]["password"] == pwd:
                self.user = user    #将得到的用户名赋值给self.user,这样其他的函数也可以用这个用户名
                self.mainPath = os.path.join(setting.BASE_DIR,"home",self.user)   #将用户的路径拼接出来
                return user

    #具体操作命令
    def put(self,**data):
        print(data)
        file_name = data.get("file_name")
        file_size = data.get("file_size")
        target_path = data.get("target_path")

        abs_path = os.path.join(self.mainPath,target_path,file_name)  #文件上传到哪个路径下,拼接出绝对路径

        has_received = 0
        if os.path.exists(abs_path):
            file_has_size = os.stat(abs_path).st_size
            if file_has_size < file_size:   #文件已存在,但大小小于传过来的文件大小,断点续传
                self.request.sendall("800".encode("utf-8"))
                choice = self.request.recv(1024).decode("utf-8")
                if choice == "Y":   #选择续传
                    self.request.sendall(str(file_has_size).encode(("utf-8")))   #回传已有多少字节
                    has_received += file_has_size    #将已有字节长度赋值给初始值
                    f = open(abs_path,"ab")          #再在已有基础上写入
                else:
                    f = open(abs_path,"wb")      #不续传,重新写入
            else:
                self.request.sendall("801".encode("utf-8"))
                return       #文件存在,不做操作,返回最初等待接收状态

        else:
            self.request.sendall("802".encode("utf-8"))
            f = open(abs_path,"wb")

        while has_received < file_size:   #判断接收到数据是否小于传输过来的文件大小,小于则继续传输
            try:
                data = self.request.recv(1024)
            except Exception:
                break
            f.write(data)
            has_received += len(data)
        f.close()

    def ls(self,**data):
        file_list = os.listdir(self.mainPath)          #列出当前目录下的所有文件
        file_str = "\n".join(file_list)
        if not len(file_list):
            file_str = "<empty dir>"
        self.request.sendall(file_str.encode("utf-8"))

    def cd(self,**data):
        dirname = data.get("dirname")
        if dirname == "..":
            self.mainPath = os.path.dirname(self.mainPath)
        self.mainPath = os.path.join(self.mainPath,dirname)
        self.request.sendall(self.mainPath.encode("utf-8"))

    def makdir(self,**data):
        dirname = data.get("dirname")
        path = os.path.join(self.mainPath,dirname)
        if not os.path.exists(path):
            if "/" in dirname:
                os.makedirs(path)
            else:
                os.mkdir(path)
            self.request.sendall("create success".encode("utf-8"))
        else:
            self.request.sendall("dirname exist".encode("utf-8"))

客户端:

主程序ftb_client

import socket
import optparse
import configparser
import json
import os
import sys

STATUS_CODE={
    250:"Invalid cmd format,e.g:{'action':'get','filename':'test.py','size':344}",
    251:"Invalid cmd",
    252:"Invalid auth data",
    253:"Wrong username or password",
    254:"Passed authentication",
    255:"Filename doesn't provided",
    256:"File doesn't exist on server",
    257:"ready to send file",
    258:"md5 verification",
    800:"the file exist,but not enough,is continue",
    801:"the file exist",
    802:"ready to receive datas",
    900:"md5 valdate success",
}

class ClientHandler():
    def __init__(self):
        self.op = optparse.OptionParser()      #实例化

        self.op.add_option("-s","--server",dest="server")      #设定输入的参数按照什么样的格式解析
        self.op.add_option("-P","--port",dest="port")
        self.op.add_option("-u","--username",dest="username")
        self.op.add_option("-p","--password",dest="password")

        self.options,self.args = self.op.parse_args()
        #得到的第一个值是一个对象,是将输入的参数按照设定格式解析的键值对,第二个值是列表,包含未被设定格式的参数

        self.verify_args(self.options,self.args)
        self.make_connection()
        self.mainPath = os.path.dirname(os.path.abspath(__file__))
        self.last = 0

    def verify_args(self,options,args):
        server = options.server
        port = options.port
        username = options.username
        password = options.password

        if int(port)<65535:
            return True
        else:
            exit("the port is in 0-65535")

    def make_connection(self):     #客户端套接字流程
        self.sock = socket.socket()
        self.sock.connect((self.options.server,int(self.options.port)))

    def interactive(self):
        if self.authenticate():        #判断是否验证成功
            print("begin to interactive...")
            while True:
                cmd_info = input("[%s]" %self.current_dir).strip()    #用户访问成功之后输入需要执行的操作

                cmd_list = cmd_info.split()   #将用户输入的内容分隔
                if hasattr(self,cmd_list[0]):
                    func = getattr(self,cmd_list[0])
                    func(*cmd_list)

    #具体命令操作
    def put(self,*cmd_list):
        action,local_path,target_path = cmd_list
        local_path = os.path.join(self.mainPath,local_path)    #将上传的文件的本地路径与自定义的mainPath目录拼接

        file_name = os.path.basename(local_path)       #basename取local_path路径下的文件名
        file_size = os.stat(local_path).st_size        #文件大小
        data = {
            "action":"put",
            "file_name":file_name,
            "file_size":file_size,
            "target_path":target_path
        }

        self.sock.send(json.dumps(data).encode("utf-8"))

        is_exist = self.sock.recv(1024).decode("utf-8")   #接收服务端返回的消息

        has_sent = 0
        if is_exist == "800":   #文件不完整
            choice = input("the file exist,but not enough,is continue?[Y/N]").strip()
            if choice.upper() == "Y":
                self.sock.sendall("Y".encode("utf-8"))
                continue_position = self.sock.recv(1024).decode("utf-8")
                has_sent += int(continue_position)
            else:
                self.sock.sendall("N".encode("utf-8"))
        elif is_exist == "801":   #文件已存在
            return
        else:
            pass

        f = open(local_path,"rb")
        f.seek(has_sent)    #将光标移动到多少个字节处
        while has_sent < file_size:   #文件传输
            data = f.read(1024)
            self.sock.send(data)
            has_sent += len(data)
            self.show_progress(has_sent,file_size)
        f.close()

    def show_progress(self,has,total):
        rate = float(has)/float(total)
        rate_num = int(rate * 100)
        if self.last != rate_num:
            sys.stdout.write("%s%% %s\r" %(rate_num,"#"*rate_num))
        self.last = rate_num

    def ls(self,*cmd_list):
        data = {
            "action":"ls"
        }
        self.sock.sendall(json.dumps(data).encode("utf-8"))
        data = self.sock.recv(1024).decode("utf-8")
        print(data)

    def cd(self,*cmd_list):
        data = {
            "action":"cd",
            "dirname":cmd_list[1]
        }
        self.sock.sendall(json.dumps(data).encode("utf-8"))
        data = self.sock.recv(1024).decode("utf-8")
        self.current_dir = os.path.basename(data)

    def mkdir(self,*cmd_list):
        data = {
            "action":"mkdir",
            "dirname":cmd_list[1]
        }
        self.sock.send(json.dumps(data).encode("utf-8"))
        self.sock.recv(1024).decode("utf-8")


    def authenticate(self):    #判断是否输入,再进行传输
        if self.options.username is None or self.options.password is None:
            username = input("username:")
            password = input("password:")
            return self.get_auth_result(username,password)
        return self.get_auth_result(self.options.username,self.options.password)

    def response(self):           #接收服务端回应的消息
        data = self.sock.recv(1024).decode("utf-8")
        data = json.loads(data)   #接收的状态码是字典格式{"status_code":state_code}
        return data

    def get_auth_result(self,user,pwd):        #将数据传输到服务端
        data = {
            "action":"auth",
            "username":user,
            "password":pwd
        }
        self.sock.send(json.dumps(data).encode("utf-8"))
        response = self.response()
        print(response["status_code"])
        if response["status_code"] == 254:      #根据回传的状态码判断是否验证正确
            self.user = user
            self.current_dir = user
            print(STATUS_CODE[254])
            return True     #验证成功返回
        else:
            print(STATUS_CODE[response["status_code"]])

ch = ClientHandler()

ch.interactive()

 

posted @ 2019-06-04 22:23  saber゛  Views(468)  Comments(0Edit  收藏  举报