Python FTP协议编程

113 阅读11分钟

FTP协议

FTP(File Transfer Protocol)文件传输协议,是TCP/IP协议组中的协议之一

FTP为C/S架构,需要同时具备客户端和服务端才可以进行工作。其中FTP服务器用来存储文件。用户可以通过FTP客户端访问位于FTP服务器的资源。FTP传输效率也非常高,因此也常用于传输一些较大文件。

默认情况下FTP协议使用20/21这两个端口,其中20用于传输数据,21用于传输FTP控制的命令信息

Python实现FTP的功能主要是使用ftplib模块来实现的,在我们所使用的3.x版本Python中,属于内置模块,不需要额外安装即可使用

传输模式

FTP主动模式(PORT):客户端从一个任意端口N(N>1024)连接到FTP服务器的21端口之后,如服务器有效响应,则客户端继续监听N+1端口,并通过命令端口21向服务器发送FTP命令“port N+1”到FTP服务器,FTP服务器以数据端口20连接到客户端所提供的N+1端口。

FTP被动模式(PASV):客户端从一个任意端口N(N>1024)连接到FTP服务器的21端口之后,客户端开始监听端口N+1,客户端提交PASV命令,服务器从本机的一个大于1024的端口P想客户端发送响应命令(PORT P)。客户端从本机端口N+1发起连接向服务器的P端口。

  • 主动模式:服务器在确认响应后主动连接客户端

    • 服务器:21端口传输命令,20端口传输数据。
    • 客户端:N(N>1024)端口发送命令,N+1端口传输数据。
  • 被动模式:客户端在确认服务器端口后主动连接服务器。

    • 服务器:21端口传输命令,P(P>1024)端口传输数据。

      客户端:N(N>1024)端口发送命令,N+1端口传输数据。

搭建FTP服务器

目前关于ftp服务器,主流有如下:Vsftpd、ProFTPD、PureFTPd、Wuftpd、Server-U FTP、 FileZilla Server等软件。其中较为常用的linux下ftp服务器软件为Vsftpd

在这里也会首先搭建Vsftpd服务来进行Python有关ftp的操作。

Vsftpd全称为非常安全的ftp服务进程(Very Secure Ftp Daemon),Vsftpd是在众多类unix操作系统下最主流的ftp服务器。Vsftpd是一款完全免费、开放源码的ftp服务器软件,并且他有良好的可伸缩性、可以通过创建虚拟用户来管理ftp服务。并且也具有非常高的安全性,可以基于MySQL数据库提供安全验证保护

  • 在centos7版本下进行Vsftpd的安装
yum install vsftpd
  • 启动服务
systemctl start vsftpd

Vsftpd服务配置

Vsftpd通过Yum安装之后默认配置文件路径在/etc/vsftpd/vsftpd.conf,其中默认开启的配置项内容为

配置项解释
anonymous_enable=YES开启匿名用户访问
local_enable=YES启用本地系统用户访问
write_enable=YES本地系统用户写入权限
local_umask=022本地用户创建文件及目录权限码
dirmessage_enable=YES打印目录显示信息,通常用于用户首次访问目录时
xferlog_enable=YES开启上传/下载日志记录
connect_from_port_20=YES开启20端口数据传输
xferlog_std_format=YES启用xferlog格式写入日志
listen=NOVsftpd服务不以独立服务启动
listen_ipv6=YES启用ipv6监听
pam_service_name=vsftpd登录ftp服务器时,根据/etc/pam.d/vsftpd中内容进行认证
userlist_enable=YES/etc/vsftpd/user_list /etc/vsftpd/ftpusers 禁止以上两个配置文件中用户访问ftp服务
tcp_wrappers=YES开启Vsftpd服务于Tcp Wrapper结合进行服务器控制访问

ftplib模块

ftplib模块是可以使我们方便的通过程序形式访问并获取对应ftp服务器上内容

属于Python标准库中的内容,不需要额外安装;模块内定义了FTP类及一些相关的功能函数

参考地址:docs.python.org/3.6/library…

class ftplib.FTP(host='', user='', passwd='', acct='', timeout=None, source_address=None)
# 返回一个实例化之后的一个新ftp对象
# 当host参数给定时,将自动调用实例中的connect函数连接ftp服务器;
# 当user参数给定时,还会调用实例的login(user,passwd=’’,acct=’’)来连接服务器
'''
host:缺省参数;ftp服务器地址,默认为空字符串。
user:缺省参数;登录用户名
passwd:缺省参数;ftp登录密码,当没有给出时;默认为空字符串。
acct:缺省参数;额外登录信息;默认为空字符串。
timeout:缺省参数;指定连接超时,单位为秒;默认为None。
source_address:缺省参数;绑定来源地址,参数为一个二元组;包含主机地址字符串及整型端口地址(host:str, port:int);默认为None
'''
  • 连接ftp服务器
from ftplib import FTP #导入模块
with FTP('192.168.1.105') as ftp: #连接ftp服务器
    ftp.login() #匿名登录ftp服务器
    print(ftp.dir()) #打印目录下文件及目录
  • ftp实例支持方法
FTP.set_debuglevel (level)
# 设置实例的调试级别
'''
level:调试级别
	0,不输出任何调试信息,同时0也是默认值。
	1:输出每次请求的单行信息。
	2:最高级别调试信息输出,记录每一行发送及接收时的命令
'''

FTP.connect(host='', port=21, timeout=None, source_address=None)
# 连接给定端口及主机地址的ftp服务器

一般很少去指定端口,默认为21端口

对于每一个实例,只需要调用一次即可,如果在使用FTP类创建实例及提供了ftp服务器地址,则不需要调用该函数

可选参数与class ftplib.FTP相同


FTP.getwelcome()
# ftp连接之后展示欢迎信息.
# 可以在/etc/vsftpd/vsftpd.conf配置文件中设置ftpd_banner值

FTP.login(user='anonymous', passwd='', acct='')
# 使用给定用户名user登录FTP服务器
# passwd和acct参数为可选参数,默认为空字符串

如果没有指定user参数,默认使用匿名用户访问FTP服务器


FTP.abort()
# 中断文件传输

FTP.sendcmd(cmd)
# 向服务器发送一个简单的命令字符串并返回响应内容字符串

FTP.voidcmd(cmd)
# 向服务器发送一个简单命令字符串并处理响应。如果命令执行成功,则返回空(此时状态码为200-299),否则引发异常(error_reply)

FTP.set_pasv(val)
# 设置ftp是否使用被动模式
'''
val:为True时,启用被动模式;反之,禁用。默认情况下是被动模式
'''

FTP.mlsd(path=’’, facts=[])
# 使用MLSD命令(RFC 3659)以标准格式列出目录
'''
path:访问路径;如果省略,则指定为当前路径。
facts:所需信息的字符串列表,如facts=['type','size','perm'];指明需要获取的目录列表信息为类型
'''

函数返回值为一个二元组

元组中第一个数据为文件名,第二个数据为一个字典,字典内容为获取参数的对应内容


FTP.nlst(path)
# 返回FTP服务器文件名列表
'''
path:要列出的目录,默认为当前服务器目录
'''

FTP.dir(path)
# 生成由列表返回的目录列表,并将其打印到标准输出,返回值在成功时为None
'''
path:控制返回的目录,默认为当前服务器默认目录
'''

FTP.rename(fromname, toname)
# 重命名FTP服务器上的文件名fromname为toname

FTP.delete(filename)
# 删除在FTP服务器上的文件,删除成功,返回响应数据。反之,则抛出异常

FTP.cwd()
# 设置当前FTP操作路径
FTP.mkd()
# 在FTP服务器上创建新的目录

FTP.pwd(pathname)
# 返回当前FTP操作的路径

FTP.rmd(filename)
# 删除FTP服务器上名字为dirname的目录

FTP.size(filename)
# 请求服务器上名为filename文件的大小,成功是返回文件大小,反之返回None。
'''
filename:需要获取的文件名
'''

FTP.quit()
# 发送QUIT命令给服务器并关闭FTP连接。是一个比较温柔的方式中断连接。关闭错误时抛出异常

FTP.close()
# 当前FTP连接,需要注意的是,close不要在quit之后使用造成多次关闭连接

连接FTP

由于FTP实例化后包含上下文管理,具备创建和释放的两个因素

所以我们也可以使用上下文管理语句来方便创建ftp实例并进行连接

def ftp_connect(host,user='anonymous',passwd=''):
    '''
      host: 主机地址
      user: ftp登录用户名
      passwd: ftp登录密码
    '''
    ftp = FTP()
    ftp.connect(host)
    ftp.login(user,passwd) # 默认使用匿名用户登录
    ftp.cwd('pub/') # 切换到对应目录下
    return ftp

目录列表

from ftplib import FTP      # 加载ftp模块
from ftp_connect import ftp_connect

def show_file_list(ftp):
    '''
      字典返回FTP服务器文件及目录
      返回值:
        key: 文件/目录名
        value: 文件类型('dir','file','block')
    '''
    cmd  = 'LIST'
    filelist = {}
    def get_file(filename):
        file_sig = filename[0]
        file_name = filename.split(' ')[-1]
        sig = {
            'd': 'dir',
            '-': 'file',
            'b': 'block',
        }
        filelist[file_name] = sig[file_sig]
    try:
      	ftp.retrlines(cmd,get_file)
    except:
      	return None
    else:
      	return filelist

def main():
	host = '192.168.1.100'
	ftp = ftp_connect(host)
	filelist = show_file_list(ftp)
	for name in filelist:
		print('%-12s:%-5s' %(name,filelist[name]))
if __name__ == '__main__':
	main()

通过FTP命令'LIST'以及对应的函数retrlines获取到服务器上目录及文件的列表,其中'LIST'命令返回的FTP服务器上的文件列表内容类似linux下的'ls'命令

通过函数内嵌的get_file()函数对返回字符串进行二次处理,获取到文件名及文件类型。最终函数返回文件名对应文件类型的字典


还可以直接通过ftp.nlst()函数进行更加简单的服务器文件获取工作

from ftplib import FTP      # 加载ftp模块
from ftp_connect import ftp_connect

def main():
    host = '192.168.1.100'
    ftp = ftp_connect(host)
    result = ftp.nlst()
    print(result)

if __name__ == '__main__':
    main()

直接通过使用nlst将返回一个列表数据,列表中的每一个元素为FTP服务器上的文件及目录名

  • 注意:返回结果并不会告知返回文件名的类型

上传文件

FTP.storbinary(cmd, fp, blocksize=8192, callback=None, rest=None)
# 以二进制模式传输上传文件
'''
cmd:STOR命令,如:"STOR filename",指定传输存储文件。
fp:二进制模式打开的本地文件对象。
blocksize:传输数据的最大块大小,默认为8192。
callback:每一次block大小的数据发送之后的回调函数 
rest:与FTP.retrbinary同
'''
FTP.storlines(cmd, fp, callback=None)
# 以ASCII传输模式传输上传文件
'''
cmd:STOR命令,如:"STOR filename"。
fp:二进制模式打开的本地文件对象。
callback:每次发送数据之后的回调函数
'''
  • 上传文件参考
from ftplib import FTP # 加载ftp模块
from ftp_connect import ftp_connect

def upload_file_binary(ftp,filename):
    '''
    filename: 本地需要上传的文件路径
    '''
    cmd = 'STOR ' + filename
    with open(filename, 'rb') as fp:
      ftp.storbinary(cmd,fp)

def upload_file_ascii(ftp,filename):
    '''
    filename: 本地需要上传的文件路径
    '''
    cmd = 'STOR ' + filename
    with open(filename, 'rb') as fp:
      ftp.storlines(cmd,fp)

def main():
    host = '192.168.1.100'
    ftp = ftp_connect(host)
    filename = 'upload.md'
    upload_file_ascii(ftp,filename)
    #upload_file_binary(ftp, filename)

if __name__ == '__main__':
	main()

下载文件

FTP.retrbinary(cmd, callback, blocksize=8192, rest=None)
# 二进制模式传输存储文件;常用在下载FTP服务器上内容
'''
cmd:RETR命令,如:'RETR filename',用来指明下载文件。
callback:每一个接收到的数据块所使用的回调函数,一般为文件对象的写入函数。
blocksize:文件读取传输的最大数据块大小。也是传递给回调函数的最大值。默认为8292。
rest:该参数常用在控制访问文件的字节偏移量。服务器将会重新发送文件字节数据从rest偏移量起
'''
FTP.retrlines(cmd, callback=None)
# ASCII模式传输存储文件;也常用在展示FTP文件目录列表
'''
cmd:参数可以是	RETR或者LIST或NLST;LIST以列表形式检索有关文件及其详细信息。NLST只是以列表展示文件名。
callback:每一行数据所使用的回调函数,默认回调函数将结果打印到标准输出
'''
  • 下载代码参考
from ftplib import FTP # 加载ftp模块
from ftp_connect import ftp_connect

def download_file_binary(ftp,path,filename):
    '''
     二进制模式传输下载文件
     ftp: ftp实例对象
     path: 待下载远程主机文件
     filename: 本地保存文件名
    '''
    cmd = 'RETR ' + path
	with open(filename,'wb') as fp:
    	ftp.retrbinary(cmd,fp.write)

def download_file_acsii(ftp,path,filename):
    '''
    文本模式传输下载文件
    ftp: ftp实例对象
    path: 待下载远程主机文件
    filename: 本地保存文件名
    '''
    cmd = 'RETR ' + path
    with open(filename,'w') as fp:
    	ftp.retrlines(cmd,fp.write)

def main():
    host = '192.168.1.100'
    ftp = ftp_connect(host)
    download_file_acsii(ftp, 'test.md', 'down_file.md')  # ascii模式无法下载具有中文文件
    #download_file_binary(ftp,'test.md','down_file.md')

if __name__ == '__main__':
	main()

下载时,需要我们指明对应服务器上需要下载的文件名在两个下载函数的参数部分我们声明为path然后与'RETR '命令进行拼接,注意这里'RETR '命令后有一个空格;然后分别通过对应ftp实例的 retrlines与retrbinary函数进行下载