教你写一个ftp协议(文件传输协议)

一、FTP协议简介

FTP 是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为“文传协议”。用于Internet上的控制文件的双向传输。同时,它也是一个应用程序(Application)。基于不同的操作系统有不同的FTP应用程序,而所有这些应用程序都遵守同一种协议以传输文件。在FTP的使用当中,用户经常遇到两个概念:”下载”(Download)和”上传”(Upload)。”下载”文件就是从远程主机拷贝文件至自己的计算机上;”上传”文件就是将文件从自己的计算机中拷贝至远程主机上。用Internet语言来说,用户可通过客户机程序向(从)远程主机上传(下载)文件。

二、FTP Server的用户分类及权限归属

在考虑FTP服务器安全性工作的时候,第一步要考虑的就是谁可以访问FTP服务器。以下有三种客户可以访问FTP Server:
一类是Real帐户。这类用户是指在FTP服务上拥有帐号。当这类用户登录FTP服务器的时候,其默认的主目录就是其帐号命名的目录。但是,其还可以变更到其他目录中去。如系统的主目录等等。
第二类帐户是Guest用户。在FTP服务器中,我们往往会给不同的部门或者某个特定的用户设置一个帐户。但是,这个账户有个特点,就是其只能够访问自己的主目录。服务器通过这种方式来保障FTP服务上其他文件的安全性。拥有这类用户的帐户,只能够访问其主目录下的目录,而不得访问主目录以外的文件。
第三类帐户是Anonymous(匿名)用户,这也是我们通常所说的匿名访问。这类用户是指在FTP服务器中没有指定帐户,但是其仍然可以进行匿名访问某些公开的资源。

三、写一个简单的ftp协议

下面我们写一个简单的匿名用户可以访问的ftp协议,前两种账户的功能我会在后续博客中逐一完善。在写一个ftp协议之前你需要了解以下几点:

  1. ftp作为一个服务器端和客户端相互传输文件的协议,我们在写的时候就要分别写服务器端程序server_ftp.py和客户端程序client_ftp.py。
  2. 我们要实现多个客户端可以同时访问服务器端,所以要通过多线程的方式来使得多个客户端访问,在这里我们服务器端通过socketserver来写。

  3. 最后通过将一个txt文件通过客户端上传到服务器端来验证所写代码的准确性。在下面的代码中我会详细备注每条语句完成的功能。


客户端代码:

import socket  #导入socket模块,用来实现socket通信
import os      #导入os模块,主要用来调用系统命令,获得路径
import json    #导入json模块,将字符串形式的json数据转化为字典,也可以将Python中的字典数据转化为字符串形式的json数据

class FtpClient(object ):  
    def __init__(self):
        self.client=socket.socket() #声明客户端利用socket通信

    def help(self):     #写一个打印一些指令的帮助信息函数,
        msg='''
        ls
        pwd
        cd../..
        get filename
        put filename
        '''

    def connect(self,ip,port):   #定义一个连接服务器函数,调用client.connect()方法,连接服务器端
        self.client.connect((ip,port))


    def interactive(self):  #定义一个与服务器交互的函数
        while True:
            cmd=input('>>').strip() #用户在客户端输入指令,strip()去掉用户输入指令的空格和换行符
            if len(cmd)==0:continue
            cmd_str=cmd.split()[0]  #拆分指令的第一个字符赋给cmd_str,永远都是指令help()
            #反射 
            if hasattr(self,'cmd_%s'%cmd_str ): #hasattr() 函数用于判断对象是否包含对应的属性
                func=getattr(self,'cmd_%s'%cmd_str)  # 函数用于返回一个对象属性值
                func(cmd)  #取到help中的指令 ls,pwd,get ,put。然后传入后面的 filename,这样后面的函数名字就叫做cmd_put  cmd_get...
            else:
                self.help()


    def cmd_put(self,*args): #写一个通过客户端上传文件的函数,*args为了接收更多数据
        #上传一个文件
        cmd_split= args[0].split() #将传入的第一个参数赋值给cmd_split,变为列表
        if len(cmd_split)>1:  #这里大于1 因为最后我们输入put filename.txt进行验证,由于存在put所以大于1
            filename=cmd_split[1]
            if os.path.isfile(filename): #判断要上传的文件是否存在
                filesize=os.stat(filename).st_size  #获取文件大小

                #发送文件大小,文件名,进行的操作(这里我们默认put上传数据)给服务器,所以写成json字典形式,需要扩展直接在这加
                msg_dic={
                    'action':'put',
                    'filename':filename,  
                    'size':filesize
                }
                self.client.send(json.dumps(msg_dic).encode('utf-8'))#发给服务器端,json.dumps()字典转换为json格式
                server_response=self.client.recv(1024) #等待服务器响应
                f=open(filename ,'rb') #打开文件,发送给服务器
                for i in f:
                    self.client.send(i)
                else:
                    print('文件传输完毕')
                    f.close()
            else:
                print(filename ,'is not exist')

    def cmd_get(self):  #定义一个从服务器下载文件函数,这里和上面大同小异,先不写
        pass

ftp=FtpClient() #实例化
ftp.connect('localhost',9999)  #连接服务器端口
ftp.interactive()  #调用和服务器交互函数

服务器端代码

import socketserver  #利用socketserver来写
import json ,os
#自己写一个请求处理类,继承BaseRequestHandler
class MyTCPHandler(socketserver.BaseRequestHandler):

    def put(self,*args):
        #接收客户端文件
        cmd_dic=args[0] 
        filename=cmd_dic['filename'] #获取文件名
        filesize=cmd_dic['size']     #获取文件大小
        if os.path.isfile(filename ):  #如果已经存在上传文件,新建一个文件
            f=open(filename+'.new','wb')
        else:
            #如果不存在 给客户端响应上传
            f=open(filename ,'wb')
        self.request.send(b'200 ok')  #响应客户端
        received_size=0
        while received_size < filesize :   #循环接收文件
            data=self.request.recv(1024)
            f.write(data)
            received_size +=len(data)
        else:
            print('file[%s] has overload..'%filename )  #文件传输完成


    #跟客户端的交互在handle中
    def handle(self):
        while True :
            try:
                self.data=self.request.recv(1024).strip()
                #format格式化 打印客户端ip地址
                print('{}wrote:'.format(self.client_address[0]))  
                print(self.data)

                cmd_dic=json.loads(self.data.decode())#json字符串转为字典
                action=cmd_dic['action']   #获取进行的操作 这里默认是put
                #反射
                if hasattr(self,action ):  #判断put操作是否存在
                    func=getattr(self ,action )
                    func(cmd_dic)

            except ConnectionResetError as e:
                print(e)
                break

if __name__ =='__main__':
    HOST,POST='localhost',9999
    # 实例化TCPServer
    server=socketserver.ThreadingTCPServer((HOST,POST),MyTCPHandler )  #ThreadingTCPServer:多线程,多个客户端可以请求
    #这样此服务器就可以让多个客户端连接 
    #处理多个请求
    server.serve_forever()

运行结果

我们把需要测试的文件lianxiren.txt放在客户端目录下,先运行服务器,在运行客户端,并且输入put lianxiren.txt 可以发现文件传输完成,在服务器的文件夹中会发现传输过去的txt文件。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42898819/article/details/81502058