美文网首页
大师兄的Python学习笔记(十六): FTP与ftplib

大师兄的Python学习笔记(十六): FTP与ftplib

作者: superkmi | 来源:发表于2020-04-26 17:00 被阅读0次

    大师兄的Python学习笔记(十五): Socket编程
    大师兄的Python学习笔记(十七): Mail编程

    一、关于FTP

    1. 关于FTP协议
    • FTP — 文件传输协议(File-Transfer Protocol)是最常用的互联网协议之一。
    • 定义了基于通过套接字(Socket)交换命令字符串和文件内容的高级对话模型。
    • 位于OSI七层模型的应用层。
    2. FTP的端口

    FTP服务器和客户端会建立两个TCP会话:

    • 一个用于在客户端和服务端之间传输控制命令(端口21)。
    • 一个用于传输字节(通常是端口20)。
    3. FTP的账户分类

    登陆FTP服务器必须拥有一个账户:

    • Real账户: 注册账户。
    • Guest账户: 临时授权的某一类用户。
    • Anonymous账户: 匿名账户,任何人。
    4. 主动模式和被动模式

    1) 主动模式 (PORT)

    • FTP的默认模式。
    • 在主动模式下,客户端会开启N和N+1两个端口,N为客户端的命令端口,N+1为客户端的数据端口。
    • 对服务器的管理有利,因为FTP服务器只需要开启21端口的“准入”和20端口的“准出”即可。
    • 对客户端的管理不利,因为FTP服务器20端口连接客户端的数据端口时,有可能被客户端的防火墙拦截掉。

    2 被动模式 (PASV)

    • 命令连接和数据连接都由客户端发起,这样就解决了从服务器到客户端的数据端口的连接被防火墙过滤的问题。
    • 被动模式下,当开启一个 FTP 连接时,客户端打开两个任意的本地端口 (N > 1024 和 N+1) 。
    5. FTP的工作流程

    第1步:客户端与FTP服务器建立Socket连接



    第2步:客户端登录FTP服务器



    第3步: 客户端让服务器进入被动模式

    第4a步:客户端通过被动模式下载文件



    第4b步:客户端通过被动模式向服务器上传文件

    第4c步:客户端通过主动模式向服务器上传文件

    第5步:客户端退出服务器
    image.png
    6. FTP文件名

    分三段表示FTP服务器上的文件,比如ftp://127.0.0.1:10005/workfile/myfile.txt拆开就是

    • HOST: ftp://127.0.0.1:10005
    • DIR:workfile/
    • File:myfile.txt

    二、建立FTP服务器

    • 为了方便演示,我使用pyftdplib包创建一个本地FTP服务器。
    • 如果条件允许,可以自己租用一台网络服务器并开通FTP服务。
    >>>from pyftpdlib.servers import FTPServer
    >>>from pyftpdlib.handlers import FTPHandler
    >>>from pyftpdlib.authorizers import DummyAuthorizer
    >>>import os
    
    >>>file_path = os.path.join("d:\\","FTP_demo")
    
    >>>class Demo_FTP_Server():
    >>>    def __init__(self):
    >>>        # 配置参数
    >>>        handler = FTPHandler
    >>>        ip = '127.0.0.1'
    >>>        port = 10005 # 由于我的21端口已经被占用,所以调用了一个临时端口代替
    >>>        address =(ip,port)
    >>>        self.ftpServer = FTPServer(address,handler)
    >>>        self.ftpServer.max_cons = 5 # 最大接入数
    >>>        self.ftpServer.max_cons_per_ip = 10 # 最大接入ip数
    >>>        print("FTP服务器启动:{}".format(self.ftpServer))
    
    >>>        self.ftpServer.handler.banner = '欢迎接入FTP demo服务器.'
    >>>        self.ftpServer.handler.passive_port = range(2000,2050)
    
    >>>        self.userInfo = {
    >>>            'User_1':{
    >>>            'user_name':'Admin',
    >>>            'password':'Admin',
    >>>            'home_path':file_path,
    >>>            'permission':'elradfmwM',
    >>>            'msg_login':'Admin login successful.',
    >>>            'msg_quit':'Admin log off.'
    >>>        },
    >>>            'User_2': {
    >>>                'user_name': 'Customer',
    >>>                'password': 'Customer',
    >>>                'home_path': file_path,
    >>>                'permission': 'elr',
    >>>                'msg_login': 'Customer login successful.',
    >>>                'msg_quit': 'Customer log off.'
    >>>            },
    >>>        }
    
    >>>    def addUser(self):
    >>>        # 添加注册用户
    >>>        authorizer = DummyAuthorizer()
    
    >>>        for user in self.userInfo.values():
    >>>            authorizer.add_user(
    >>>                user['user_name'],
    >>>                user['password'],
    >>>                user['home_path'],
    >>>                perm=user['permission'],
    >>>                msg_login=user['msg_login'],
    >>>                msg_quit=user['msg_quit'],
    >>>            )
    >>>        self.ftpServer.handler.authorizer = authorizer
    
    >>>        # 添加匿名用户
    >>>        authorizer.add_anonymous(homedir=file_path,perm='elr',msg_login='anonymous >>>login successful.',msg_quit='anonymous log off.')
    
    >>>    def run(self):
    >>>        print('FTP demo服务器启动.')
    >>>        self.ftpServer.serve_forever()
    
    >>>    def stop(self):
    >>>        self.ftpServer.close_all()
    
    >>>if __name__ == '__main__':
    >>>    demo_server = Demo_FTP_Server()
    >>>    demo_server.addUser()
    >>>    demo_server.run()
    FTP服务器启动:<pyftpdlib.servers.FTPServer listening 127.0.0.1:10005 at 0x1b2bef0afc8>
    FTP demo服务器启动.
    [I 2020-04-21 16:55:12] concurrency model: async
    [I 2020-04-21 16:55:12] masquerade (NAT) address: None
    [I 2020-04-21 16:55:12] passive ports: None
    [I 2020-04-21 16:55:12] >>> starting FTP server on 127.0.0.1:10005, pid=9684 <<<
    [I 2020-04-21 16:55:15] 127.0.0.1:62140-[] FTP session opened (connect)
    [I 2020-04-21 16:55:15] 127.0.0.1:62140-[anonymous] USER 'anonymous' logged in.
    [I 2020-04-21 16:55:15] 127.0.0.1:62140-[anonymous] CWD d:\FTP_demo 250
    [I 2020-04-21 16:55:15] 127.0.0.1:62140-[anonymous] FTP session closed (disconnect).
    
    • 如果一切正常,现在就可以使用浏览器以账号或者匿名方式访问本地FTP服务器了。


    三、关于ftplib模块

    • ftplib是Python的默认模块,可以用来实现简单的FTP客户端功能。
    1. FTP对象和方法**

    1)ftplib.FTP(host, user, passwd, acct, timeout)

    • 返回FTP类的新实例。
    • host不为空时,调用connect(host)
    • user、passwd、acct不为空时,调用login(user,passwd,acct)

    2)FTP.set_debuglevel(level)

    • 调试级别,控制打印的调试输出量。
    • level由低到高分为0,1,2。

    3)FTP.connect(host, port, timeout)

    • 连接到服务器主机和端口。

    3)FTP.getwelcome()

    • 获得服务器发送的欢迎消息。

    4)FTP.login(user, passwd, acct)

    • 账号登录。
    • 如果为空则默认以匿名用户(annoymous)登录。

    5)FTP.abort()

    • 尝试中止文件传输。

    6)FTP.sendcmd(command)

    • 发送指令到服务器,并返回响应字符串。

    7)FTP.voidcmd(command)

    • 发送指令到服务器,如果接收到成功响应码(200-299)则不返回任何内容。

    8)FTP.retrbinary(command, callback, maxblocksize, rest)

    • 以二进制传输模式检索文件。
    • command是一个RETR指令:'RETR filename'。

    9)FTP.retrlines(command, callback)

    • 以ASCII传输模式检索文件或目录列表。
    • command`是一个RETR指令或:
    指令 说明
    LIST 检索文件列表和关于这些文件的信息。
    NLST 检索文件名列表。
    MLSD 检索机器可读的文件列表和关于这些文件的信息。

    10)FTP.set_pasv(val)

    • 是否启动被动模式,默认为True。

    11)FTP.storbinary(command, fp, blocksize, callback, rest)

    • 以二进制传输模式存储文件。
    • command是一个STOR命令:"STOR filename"。
    • fp是一个打开的文件对象。

    12)FTP.storlines(command, fp, callback)

    • 以ASCII传输模式存储文件。
    • command是一个STOR命令。

    13)FTP.transfercmd(cmd, rest)

    • 通过数据连接启动传输,返回连接的套接字。
    • 如果传输是有效的,通过cmd发送一个EPRT或PORT命令接受连接。
    • 如果服务器是被动的,则通过cmd发送EPSV或PASV命令,连接到它并启动传输命令。

    14)FTP.ntransfercmdcmd, rest)

    • 与FTP.transfercmd()类似,但返回数据连接的元组和数据的预期大小。

    15)FTP.nlst(argument[, ...])

    • 返回服务器文件名列表,默认为当前目录。

    16)FTP.dir(argument[, ...])

    • 生成该LIST命令返回的目录列表,将其打印到标准输出。

    17)FTP.rename(fromname, toname)

    • 将服务器上的文件名fromname改为toname

    18)FTP.delete(filename)

    • 将服务器上的文件filename删除。

    19)FTP.cwd(pathname)

    • 设置服务器上的当前目录。

    20)FTP.mkd(pathname)

    • 在服务器上创建目录。

    21)FTP.pwd()

    • 返回服务器上当前的路径名。

    22)FTP.rmd(dirname)

    • 删除服务器上的目录(如果为空)

    23)FTP.size(filename)

    • 返回文件filename大小。

    24)FTP.quit()

    • 发送QUIT命令到服务器并关闭连接。
    • 这是关闭服务器的礼貌方式。

    24)FTP.close()

    • 单方面关闭连接。
    2. FTP_TLS对象和方法**
    • FTP_TLS支持加密传输。
    • FTP_TLS类继承自FTP类,并有一些额外的属性和方法。

    1)FTP_TLS.ssl_version

    • SSL版本(默认是ssl.PROTOCOL_SSLv23)。

    2)FTP_TLS.auth()

    • 根据ssl_version,使用TLS或SSL设置安全控制连接。

    3)FTP_TLS.prot_p()

    • 建立加密数据连接。

    4)FTP_TLS.prot_c()

    • 建立明文数据连接。
    3. 实例方法

    1)从FTP下载文件

    >>>import os
    >>>from ftplib import FTP
    
    >>>filename = 'test.txt' # 文件名
    >>>dirname = '.' # 文件夹
    >>>host = '127.0.0.1'
    >>>port=10005 # ftp站点地址
    >>>username = 'Admin'
    >>>password = 'Admin' # 用户名密码
    
    >>>print('连接服务器...')
    >>># 第一步:建立连接
    >>>connection = FTP()
    >>>connection.connect(host=host,port=port)
    >>># 第二步:登录服务器
    >>>connection.login(user=username,passwd=password)
    >>># 第三步:到达指定目录下
    >>>connection.cwd(dirname)
    
    >>>print('下载中...')
    >>># 创建用于储存的本地文件
    >>>file_dl = os.path.join('d:\\','FTP_demo','download',filename) # 下载到文件
    >>>with open(file_dl,'wb') as f:
    >>>    # 第四部:下载文件
    >>>    connection.retrbinary('RETR %s'%filename,f.write,1024)
    >>>    print('下载{}完成.'.format(filename))
    >>>    connection.quit()
    >>>    print('断开服务器。')
    连接服务器...
    下载中...
    下载test.txt完成.
    断开服务器。
    

    2)从本地上传文件到FTP服务器

    >>>import ftplib,os
    
    >>>def uploadfile(file,filename,host,port,dir,username,password):
    >>>    with open(file,'rb') as f:
    >>>        connection = ftplib.FTP()
    >>>        print("连接服务器...")
    >>>        connection.connect(host=host,port=port)
    >>>        connection.login(user=username,passwd=password)
    >>>        connection.cwd(dir)
    >>>        print("开始上传: %s..."%file)
    >>>        connection.storbinary('STOR %s'%filename,f,1024)
    >>>        print("上传完成。")
    >>>        connection.quit()
    >>>        print("断开服务器。")
    
    >>>if __name__ == '__main__':
    >>>    filename = 'test.txt'
    >>>    file = os.path.join('d:\\','FTP_demo','download',filename)
    >>>    host = "127.0.0.1"
    >>>    port = 10005
    >>>    dir = '.'
    >>>    username = "Admin"
    >>>    password = "Admin"   
    >>>    uploadfile(file=file,filename=filename,host=host,port=port,dir=dir,username=username,password=password)
    连接服务器...
    开始上传: d:\FTP_demo\download\test.txt...
    上传完成。
    断开服务器。
    

    3)从FTP下载文件夹

    • 下载文件夹中的所有文件。
    • 用mimetypes包通过文件名猜测文件类型。
    >>>import os,ftplib
    >>>from mimetypes import guess_type
    
    >>>def configue():
    >>>    # 配置信息
    >>>    class config:
    >>>        pass
    >>>    config.host = '127.0.0.1'
    >>>    config.port = 10005  # ftp站点地址
    >>>    config.username = 'Admin'
    >>>    config.password = 'Admin'  # 用户名密码
    >>>    return config
    
    >>>def connect_ftp_server(config):
    >>>    # 创建连接
    >>>    print('正在连接FTP服务器...')
    >>>    connection = ftplib.FTP()
    >>>    connection.connect(host=config.host,port=config.port)
    >>>    connection.login(user=config.username,passwd=config.password)
    >>>    return connection
    
    >>>def sort_type(file):
    >>>    # 判断文件是不是文本类型,或二进制
    >>>    mimetype,encoding = guess_type(file,strict=True)
    >>>    mimetype = mimetype or '?/?'
    >>>    maintype = mimetype.split('/')[0]
    >>>    result = maintype == 'text' and encoding == None
    >>>    return result
    
    >>>def download_file(connection):
    >>>    dir = connection.nlst() # 获得文件列表
    >>>    for filename in dir:
    >>>        if filename in ('.','..'):continue # 跳过根目录
    >>>        print('开始下载:%s...'%filename)
    >>>        localpath = os.path.join("d:\\", "FTP_demo","download",filename)
    >>>        try:
    >>>            if sort_type(filename):
    >>>               # 如果是文本
    >>>                localfile = open(localpath,'w',encoding = connection.encoding)
    >>>               connection.retrlines('RETR %s'%filename,lambda line:localfile.write(line +'\n'))
    >>>            else:
    >>>                # 二进制
    >>>                localfile = open(localpath,'wb')
    >>>                connection.retrbinary('RETR %s'%filename,localfile.write)
    >>>           localfile.close()
    >>>       except Exception as e:
    >>>            pass
    
    >>>    connection.quit()
    >>>    print('断开服务器...')
    >>>if __name__ == '__main__':
    >>>    config = configue()
    >>>    connection = connect_ftp_server(config)
    >>>    download_file(config,connection)
    正在连接FTP服务器...
    开始下载:test1.txt...
    开始下载:test2.txt...
    开始下载:test3.txt...
    断开服务器...
    

    4)上传文件夹到FTP服务器

    >>>import os,ftplib
    >>>from mimetypes import guess_type
    
    >>>def configue():
    >>>    # 配置信息
    >>>    class config:
    >>>        pass
    >>>    config.host = '127.0.0.1'
    >>>    config.port = 10005  # ftp站点地址
    >>>    config.username = 'Admin'
    >>>    config.password = 'Admin'  # 用户名密码
    >>>    return config
    
    >>>def connect_ftp_server(config):
    >>>    # 创建连接
    >>>    print('正在连接FTP服务器...')
    >>>    connection = ftplib.FTP()
    >>>    connection.connect(host=config.host,port=config.port)
    >>>    connection.login(user=config.username,passwd=config.password)
    >>>    return connection
    
    >>>def sort_type(file):
    >>>    # 判断文件是不是文本类型,或二进制
    >>>    mimetype,encoding = guess_type(file,strict=True)
    >>>    mimetype = mimetype or '?/?'
    >>>    maintype = mimetype.split('/')[0]
    >>>    result = maintype == 'text' and encoding == None
    >>>    return result
    
    >>>def upload_file(connection):
    >>>    localpath = os.path.join("d:\\", "FTP_demo", "download")
    >>>    with os.scandir(localpath) as it: # 遍历本地文件夹列表
    >>>        for entry in it:
    >>>            filename = entry.name
    >>>            file = os.path.join(localpath,filename)
    >>>            print(f'开始上传文件{file}')
    >>>            with open(entry.path,'rb') as f:
    >>>                # 上传文件
    >>>                if sort_type(filename):
    >>>                    # 如果是文本
    >>>                    connection.storlines(f'STOR {filename}',f)
    >>>                else:
    >>>                    # 如果是二进制
    >>>                    connection.storbinary(f'STOR {filename}',f)
    >>>    print('断开服务器')
    >>>    connection.quit()
    >>>    print(f'上传完成:{localpath}')
    
    >>>if __name__ == '__main__':
    >>>    config = configue()
    >>>    connection = connect_ftp_server(config)
    >>>    upload_file(config,connection)
    正在连接FTP服务器...
    开始上传文件d:\FTP_demo\download\test1.txt
    开始上传文件d:\FTP_demo\download\test2.txt
    开始上传文件d:\FTP_demo\download\test3.txt
    断开服务器
    上传完成:d:\FTP_demo\download
    

    5)上传目录树和删除目录树

    • 一个案例先上传整个目录树到服务器,再删除上传的目录树。
    • 需要注意的是切换服务器的当前目录。
    >>> import ftplib,os
    >>> from mimetypes import guess_type
    
    >>> class uploadAll:
    >>>     def __init__(self,host,port,username,password):
    >>>         self.fcount=0
    >>>         self.dcount=0
    >>>         self.host = host
    >>>         self.port = port  # ftp站点地址
    >>>         self.username = username
    >>>         self.password = password  # 用户名密码
    >>>         self.connection = None
    
    >>>     def sort_type(self,file):
    >>>         # 判断文件是不是文本类型,或二进制
    >>>         mimetype, encoding = guess_type(file, strict=True)
    >>>         mimetype = mimetype or '?/?'
    >>>         maintype = mimetype.split('/')[0]
    >>>         result = maintype == 'text' and encoding == None
    >>>         return result
    
    >>>     def connect_ftp_server(self):
    >>>         # 创建连接
    >>>         print('正在连接FTP服务器...')
    >>>         connection = ftplib.FTP()
    >>>         connection.connect(host=self.host, port=self.port)
    >>>         connection.login(user=self.username, passwd=self.password)
    >>>         self.connection =  connection
    
    >>>     def upload_file(self,entry):
    >>>         # 上传文件
    >>>         filename = entry.name
    >>>         print(f'开始上传文件{entry.path}...')
    >>>         with open(entry.path, 'rb') as f:
    >>>             # 上传文件
    >>>             if self.sort_type(filename):
    >>>                 # 如果是文本
    >>>                 self.connection.storlines(f'STOR {filename}', f)
    >>>             else:
    >>>                 # 如果是二进制
    >>>                 self.connection.storbinary(f'STOR {filename}', f)
                       self.fcount += 1
    
    >>>     def upload_trees(self,dir=os.path.join("d:\\", "FTP_demo", "download")):
    >>>         with os.scandir(dir) as it:  # 遍历本地文件夹列表
    >>>             for entry in it:
    >>>                 if entry.is_file():
    >>>                     # 如果是文件
    >>>                     try:
    >>>                         self.upload_file(entry)
    >>>                     except Exception as e:
    >>>                         print(f"上传文件{entry.path}时发生错误:\n{e}.")
    >>>                 else:
    >>>                     # 如果是文件夹
    >>>                     try:
    >>>                         print(f'创建文件夹{entry.name}.')
    >>>                         self.connection.mkd(entry.name)
    >>>                     except Exception as e:
    >>>                         print(f"创建文件夹{entry.name}时发生错误:\n{e}.")
    >>>                     print(f'开始上传文件夹{entry.path}...')
    >>>                     self.connection.cwd(entry.name) # 服务器端切换目录
    >>>                     self.upload_trees(entry.path)
    >>>                     self.connection.cwd('..')  # 服务器端返回目录
    >>>         print(f'上传完成,共上传了{self.fcount}个文件.')
    
    >>>     def delete_all(self):
    >>>         lines = []
    >>>         self.connection.dir(lines.append) # 列出远程目录
    >>>         for line in lines:
    >>>             parsed = line.split()
    >>>             permiss = parsed[0] # 获得文件属性
    >>>             filename = parsed[-1]
    >>>             if filename in ('..','.','download'):
    >>>                 continue # 跳过内容
    >>>             elif permiss[0] != 'd':
    >>>                 # 如果是文件
    >>>                 print(f'删除文件{filename}...')
    >>>                 self.connection.delete(filename)
    >>>                 self.dcount += 1
    >>>             else:
    >>>                 # 如果是文件夹
    >>>                 print(f'删除文件夹{filename}...')
    >>>                 self.connection.cwd(filename) # 进入文件夹
    >>>                 self.delete_all() # 删除文件夹中的文件
    >>>                 self.connection.cwd('..')
    >>>                 self.connection.rmd(filename) # 删除文件夹
    >>>         print(f'删除完成,共删除了{self.dcount}个文件.')
    
    >>>     def run(self):
    >>>         self.connect_ftp_server()
    >>>         self.upload_trees()
    >>>         print(f"{'*'*20}")
    >>>         self.delete_all()
    >>>         print(f"{'*'*20}")
    >>>         print('断开服务器')
    >>>         self.connection.quit()
    
    >>> if __name__ == '__main__':
    >>>     host = '127.0.0.1'
    >>>     port = 10005  # ftp站点地址
    >>>     username = 'Admin'
    >>>     password = 'Admin'  # 用户名密码
    >>>     ua = uploadAll(host,port,username,password)
    >>>     ua.run()
    正在连接FTP服务器...
    创建文件夹folder1.
    开始上传文件夹d:\FTP_demo\download\folder1...
    开始上传文件d:\FTP_demo\download\folder1\test1.txt...
    开始上传文件d:\FTP_demo\download\folder1\test2.txt...
    开始上传文件d:\FTP_demo\download\folder1\test3.txt...
    上传完成,共上传了3个文件.
    创建文件夹folder2.
    开始上传文件夹d:\FTP_demo\download\folder2...
    开始上传文件d:\FTP_demo\download\folder2\test1.txt...
    开始上传文件d:\FTP_demo\download\folder2\test2.txt...
    开始上传文件d:\FTP_demo\download\folder2\test3.txt...
    上传完成,共上传了6个文件.
    创建文件夹folder3.
    开始上传文件夹d:\FTP_demo\download\folder3...
    开始上传文件d:\FTP_demo\download\folder3\test1.txt...
    开始上传文件d:\FTP_demo\download\folder3\test2.txt...
    开始上传文件d:\FTP_demo\download\folder3\test3.txt...
    上传完成,共上传了9个文件.
    开始上传文件d:\FTP_demo\download\test1.txt...
    开始上传文件d:\FTP_demo\download\test2.txt...
    开始上传文件d:\FTP_demo\download\test3.txt...
    上传完成,共上传了12个文件.
    ********************
    删除文件夹folder1...
    删除文件test1.txt...
    删除文件test2.txt...
    删除文件test3.txt...
    删除完成,共删除了3个文件.
    删除文件夹folder2...
    删除文件test1.txt...
    删除文件test2.txt...
    删除文件test3.txt...
    删除完成,共删除了6个文件.
    删除文件夹folder3...
    删除文件test1.txt...
    删除文件test2.txt...
    删除文件test3.txt...
    删除完成,共删除了9个文件.
    删除文件test1.txt...
    删除文件test2.txt...
    删除文件test3.txt...
    删除完成,共删除了12个文件.
    ********************
    断开服务器
    

    参考资料



    本文作者:大师兄(superkmi)

    相关文章

      网友评论

          本文标题:大师兄的Python学习笔记(十六): FTP与ftplib

          本文链接:https://www.haomeiwen.com/subject/ptoqvhtx.html