python实现FTP程序

python实现FTP程序

 

程序源码

上传功能

查看文件

cd功能

创建目录

 

程序源码

 目录结构

 

服务端

主程序

import optparse
import socketserver
import server
import configs



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() #options的值为添加对应的值,args为输入的无关值

        #进行命令分发
        self.verify_args(options,args)
    #命令分发
    def verify_args(self,options,args):
        print(options,args)
        cmd=args[0]
        print(cmd)
        if hasattr(self,str(cmd)):
            func=getattr(self,str(cmd))
            func()
    #启动服务端
    def start(self):

        #启动服务端
        s=socketserver.ThreadingTCPServer((configs.IP,int(configs.PORT)),server.ServerHandler)
        #永久启动服务端
        s.serve_forever()

    #帮助信息
    def help(self):
        pass
if __name__ =='__main__':
    ArgvHandler()
main.py

核心代码

import socketserver
import json
import configparser
import configs
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 1:
            try:
                data=self.request.recv(1024).strip().decode('utf8')
            except Exception as e:
                continue
            #将数据转成一个json字典的形式
            if data :
                data=json.loads(data)
                print(data)

                #传过来的数据格式
                # {'action':'auth',
                # 'username':'admin',
                # 'password':'123456'}

                #外层判断命令是否为空
                #内层循环判断是否有这个命令
                if data.get('action'):
                    if hasattr(self,data.get('action')):
                        func=getattr(self,data.get('action'))
                        func(**data)
                    else:
                        print()
                else:
                     print('')



#认证
    def auth(self,**data):
        username=data['username']
        passwd=data['password']
        print(username,passwd)
        print(4)
        username=self.authenticate(username,passwd)

        if username:
            self.send_reponse(254)
        else:
            self.send_reponse(253)


    def authenticate(self,user,passwd):
        print(5)
        cfg=configparser.ConfigParser()
        # cfg.read(configs.auth_path)
        cfg.read('auth.cfg')
        if user in cfg.sections():


            if cfg[user]['passwd']==passwd:
                #
                BASE_DIR = os.path.dirname(os.path.abspath(__file__))
                self.user=user
                #定义用户的本地目录,在home目录下
                self.mainPath=os.path.join(BASE_DIR,'home',self.user)
                print("登陆成功")
                return user

    def send_reponse(self,state_code):
        print(6)
        response={"status_code":state_code,"status_mes":STATUS_CODE[state_code]}
        self.request.sendall(json.dumps(response).encode("utf-8"))


#上传
    def put(self,**data):
        print("data:",data)
        file_name=data.get('file_name')
        file_size=data.get('file_size')
        target_path=data.get('target_path')
        #这个路径对应的上传的完整路径:self.mainPath,target_path+file_name路径
        #self.mainPath:home+username
        #self.target:文件类型
        #file_name:文件名

        #完整的文件名
        has_receive=0
        abs_path=os.path.join(self.mainPath,target_path,file_name)
        print(abs_path)


        #上传文件:
        # 1.已经存在这个文件:如果文件完整,则返回文件存在,如果文件不完整,则返回选择续传还是不续传
        # 2.没有这个文件:直接上传文件
        #对应文件打开模式:不存在:wb  续传:ab 存在完整:不打开

        # 文件存在
        if os.path.exists(abs_path):

            file_has_size=os.stat(abs_path).st_size
            print(file_size,file_has_size)
            # 文件存在,且完整
            if file_size==file_has_size:
                print(1)
                self.request.sendall('801'.encode("utf8"))
                return
            #断点续传
            elif file_has_size<file_size:
                self.request.sendall('800'.encode("utf8"))
                choice=self.request.recv(1024).decode('utf8')
                #续传
                print(2)
                if choice=="Y":
                    self.request.sendall(str(file_has_size).encode("utf8"))
                    has_receive+=file_has_size
                    #这里注意光标的位置,以追加方式打开,光标在文件里的位置在最后
                    f=open(abs_path,"ab")
                #不续传,重传
                else:
                    f = open(abs_path, "wb")

        else:
            #文件不存在
            print(3)
            self.request.sendall('802'.encode("utf8"))
            f=open(abs_path,"wb")
        #has_receive:已经接收到数据大小
        while has_receive<file_size:
            try:
                data=self.request.recv(1024)
                f.write(data)
                has_receive+=len(data)
            except Exception as e:
                break
        f.close()
#cd与ls都要依靠用户的self.mainPath去做处理
#而self.mainPath一直都是用户当前所在的目录
#查看文件
    def ls(self,**data):
        file_list=os.listdir(self.mainPath)
        if len(file_list)==0:
            file_str="<empty dir>"
        else:
            file_str="\n".join(file_list)
        self.request.sendall(file_str.encode("utf8"))
#cd命令
    def cd(self,**data):
        #cd image
        dirname=data.get("dirname")
        if dirname=="..":
            # 返回目录名
            self.mainPath=os.path.dirname(self.mainPath)
        else:

            self.mainPath=os.path.join(self.mainPath,dirname)
        self.request.sendall(self.mainPath.encode('utf8'))
#创建目录
    def mkdir(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("dirname create success".encode("utf8"))
        else:
            self.request.sendall("dirname exist".encode("utf8"))
server.py

日志文件

logger.py

 配置文件

config.py
import os
IP="127.0.0.1"
PORT="4444"

# BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# auth_path=os.path.join(BASE_DIR,"conf","auth.cfg")

 数据文件

 auth.cfg
[DEFAULT]

[yuan]
passwd=123098

[root]
passwd=wenli
保存用户名与密码

 

 

客户端

import optparse
import socket
import json
import re
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()
        #上传文件的绝对路径
        #os.path.abspath(__file__):获得当前文件的绝对路径
        # os.path.dirname(os.path.abspath(__file__)):获得上一级目录
        self.mainPath=os.path.dirname(os.path.abspath(__file__))
        self.last = 0

        #下面两个函数是实例化类的时候就执行
        #对参数进行处理
        self.verify_args(self.options)
        #连接服务端,进行用户操作
        self.make_connection()

    # 对参数进行处理
    def verify_args(self,options):
        server=options.server
        port=options.port
        username=options.username
        password=options.password
        #对参数进行简单判断
        if re.match(r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",str(server)):
            return True
        else:
            exit("IP vaild")
        if int(port) and int(port) < 65535:
            return True
        else:
            exit("the port is in 0-65535")

        if username is None:
            return True
        else:
            exit("用户名为空")

        if password is None:
            return True
        else:
            exit("密码为空")



    #连接服务端
    def make_connection(self):
        #创建socket
        self.sock=socket.socket()
        self.sock.connect((self.options.server,int(self.options.port)))

    # 用户操作
    def interactive(self):
        #先进行身份认证
        if self.authenticate():
            print('''请选择你要进行的操作:
            1.上传文件示例:put filename file 
            2.查看文件:ls
            3.进入目录:cd dirname
            4.创建目录:mkdir dirname
            5.退出:quit
            ''')
            while 1:
                cmd_info=input("[%s]:"%self.user).strip()#put test.py file

                cmd_list=cmd_info.split()#默认以空格分隔
                if cmd_list[0]=="quit":break
                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
        print( action,local_path,target_path)
        # os.path.join(self.mainPath, local_path):self.mainPath+local_path得到本地完全的图片的路径
        #这里首先对格式进行判断


        #获取文件完整路径
        local_path=os.path.join(self.mainPath,local_path)
        #获取文件的名字
        file_name=os.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#类型
        }
        has_sent=0
        self.sock.send(json.dumps(data).encode('utf-8'))
        is_exist=int(self.sock.recv(1024).decode("utf-8"))
        if is_exist==800:
            print(STATUS_CODE[is_exist])
            choice=input('请输入你的选择是否选择续传:[Y/N]')
            if choice.upper()=="Y":
                self.sock.sendall("Y".encode("utf8"))
                continue_position=self.sock.recv(1024).decode("utf8")
                has_sent+=int(continue_position)
            else:
                self.sock.sendall("N".encode("utf8"))
        elif is_exist==801:
            print(STATUS_CODE[is_exist])
            return
        else:
            print(STATUS_CODE[is_exist])

        f=open(local_path,"rb")
        #将光标的位置调整到has_sent
        f.seek(has_sent)
        while has_sent<file_size:
            data=f.read(1024)
            self.sock.sendall(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)
       sys.stdout.write("%s%% %s\r"%(rate_num,"#"* rate_num))

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

    def cd(self,*cmd_list):
        data={
            'action': 'cd',
            'dirname':cmd_list[1]
        }
        self.sock.sendall((json.dumps(data).encode("utf8")))
        reponse = self.sock.recv(1024).decode("utf8")
        # self.current_dir=reponse
        print("当前目录:"+str(reponse))

    def mkdir(self,*cmd_list):

        data = {
            'action': 'mkdir',
            'dirname': cmd_list[1]
        }
        self.sock.sendall((json.dumps(data).encode("utf8")))
        reponse = self.sock.recv(1024).decode("utf8")
        print(reponse)



    #身份认证
    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 get_auth_result(self,username,password):
        # 构建数据
        dic={
            'action':'auth',
            'username':username,
            'password':password
        }
        #发送认证信息
        self.sock.send(json.dumps(dic).encode('utf-8'))
        #判断服务端回复信息,进行认证
        response=self.response()
        if response['status_code']==254:
            self.user=username
            self.current_dir=username
            print(STATUS_CODE[254])
        else:
            print(response['status_code']+response['status_mes'])
        return True

    #接收信息
    def response(self):
        data = self.sock.recv(1024).decode('utf-8')
        data = json.loads(data)
        return data



ch=ClientHandler()
# 用户操作,在用户操作里面会进行身份认证

ch.interactive()
FTP_Client.py

 

 

上传功能

 

查看文件

 

cd功能

 

创建目录

 

posted @ 2019-04-13 21:09  -零  阅读(725)  评论(0编辑  收藏  举报