python通过pyftpdlib实现FTP服务端和客户端
1. 使用pyftpdlib模块搭建FTP服务器
在我上一篇文章里面,详细的介绍了在linux操作系统下面搭建vsftpd服务,那么我们如何通过python实现一个FTP服务器呢?本文会有一个详细的介绍,搭建FTP服务器需要用到python的pyftpdlib,这是一个第三方模块需要使用pip进行安装,具体安装步骤如下:
pip3 install -i https://mirrors.aliyun.com/pypi/simple pyftpdlib
[root@zhanghao ~]# python3
Python 3.6.8 (default, Nov 16 2020, 16:55:22)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyftpdlib
>>> pyftpdlib.__doc__
'\npyftpdlib: RFC-959 asynchronous FTP server.\n\npyftpdlib implements a fully functioning asynchronous FTP server as\ndefined in RFC-959. A hierarchy of classes outlined below implement\nthe backend functionality for the FTPd:\n\n [pyftpdlib.ftpservers.FTPServer]\n accepts connections and dispatches them to a handler\n\n [pyftpdlib.handlers.FTPHandler]\n a class representing the server-protocol-interpreter\n (server-PI, see RFC-959). Each time a new connection occurs\n FTPServer will create a new FTPHandler instance to handle the\n current PI session.\n\n [pyftpdlib.handlers.ActiveDTP]\n [pyftpdlib.handlers.PassiveDTP]\n base classes for active/passive-DTP backends.\n\n [pyftpdlib.handlers.DTPHandler]\n this class handles processing of data transfer operations (server-DTP,\n see RFC-959).\n\n [pyftpdlib.authorizers.DummyAuthorizer]\n an "authorizer" is a class handling FTPd authentications and\n permissions. It is used inside FTPHandler class to verify user\n passwords, to get user\'s home directory and to get permissions\n when a filesystem read/write occurs. "DummyAuthorizer" is the\n base authorizer class providing a platform independent interface\n for managing virtual users.\n\n [pyftpdlib.filesystems.AbstractedFS]\n class used to interact with the file system, providing a high level,\n cross-platform interface compatible with both Windows and UNIX style\n filesystems.\n\nUsage example:\n\n>>> from pyftpdlib.authorizers import DummyAuthorizer\n>>> from pyftpdlib.handlers import FTPHandler\n>>> from pyftpdlib.servers import FTPServer\n>>>\n>>> authorizer = DummyAuthorizer()\n>>> authorizer.add_user("user", "12345", "/home/giampaolo", perm="elradfmwMT")\n>>> authorizer.add_anonymous("/home/nobody")\n>>>\n>>> handler = FTPHandler\n>>> handler.authorizer = authorizer\n>>>\n>>> server = FTPServer(("127.0.0.1", 21), handler)\n>>> server.serve_forever()\n[I 13-02-19 10:55:42] >>> starting FTP server on 127.0.0.1:21 <<<\n[I 13-02-19 10:55:42] poller: <class \'pyftpdlib.ioloop.Epoll\'>\n[I 13-02-19 10:55:42] masquerade (NAT) address: None\n[I 13-02-19 10:55:42] passive ports: None\n[I 13-02-19 10:55:42] use sendfile(2): True\n[I 13-02-19 10:55:45] 127.0.0.1:34178-[] FTP session opened (connect)\n[I 13-02-19 10:55:48] 127.0.0.1:34178-[user] USER \'user\' logged in.\n[I 13-02-19 10:56:27] 127.0.0.1:34179-[user] RETR /home/giampaolo/.vimrc\n completed=1 bytes=1700 seconds=0.001\n[I 13-02-19 10:56:39] 127.0.0.1:34179-[user] FTP session closed (disconnect).\n'
pyftpdlib实现了一个功能完整的异步FTP服务,在rfc-959中定义的。
pyftpdlib.ftpservers.FTPServer
: 接收客户端连接,然后分发给对应的程序
pyftpdlib.handlers.FTPHandler
: 一个表示服务器协议解释器的类,每次出现一个新的连接FTPServer将创建一个新的FTPHandler实例来处理当前PI会话。
pyftpdlib.handlers.ActiveDIP
和pyftpdlib.handlers.PassiveDIP
: 主动模式和被动模式的基础类
pyftpdlib.handlers.DTPHandler
: 这个类处理的是数据传输进程
pyftpdlib.authorizers.DummyAuthorizer
: 处理FTPd的认证和权限的处理类,用于处理用户密码,获取用户的家目录和权限,DummyAuthorizer 是基础的认证模块的类,提供平台无关接口的基础授权程序类
用于管理虚拟用户。
pyftpdlib.filesystems.AbstractedFS
: 类用于与文件系统交互,提供了一个高级的,跨平台接口兼容Windows和UNIX风格文件系统。
1.1 实现脚本
1.1.1 加载模块
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
from pyftpdlib.log import LogFormatter
1.1.2 添加一个FTP的handler
def ftpServer(username, password, directory):
'''
启动一个ftp的server进程
:param username: 需要添加用户的用户名
:param password: 用户的密码
:param directory: 用户的家目录,也就是ftp的数据目录
:return:
'''
# 添加虚拟用户,验证用户需要用户名、密码、家目录、权限
author = DummyAuthorizer()
author.add_user(username, password, directory, perm="elradfmw")
# 初始化ftp句柄
handler = FTPHandler
handler.authorizer = author
# 添加被动端口范围
handler.passive_ports = range(2000, 2400)
# 启动server,本地执行,监听21号端口
server = FTPServer(('0.0.0.0', 32), handler)
server.serve_forever()
username = "zhanghao"
password = "aixocm"
directory = "/data/zhanghao"
ftpServer(username, password, directory)
运行报错如下:对于脚本需要添加异常处理,捕获到目录不存在,需要创建目录
[root@zhanghao python-learning]# python3 ftptest.py
Traceback (most recent call last):
File "ftptest.py", line 37, in <module>
ftpServer(username, password, directory)
File "ftptest.py", line 21, in ftpServer
author.add_user(username, password, directory, perm="elradfmw")
File "/usr/local/lib/python3.6/site-packages/pyftpdlib/authorizers.py", line 107, in add_user
raise ValueError('no such directory: %r' % homedir)
ValueError: no such directory: '/data/zhanghao'
运行报错如下:如果端口被占用,需要捕获报错,然后将输出:"21号端口被占用"
[root@zhanghao python-learning]# python3 ftptest.py
Traceback (most recent call last):
File "ftptest.py", line 37, in <module>
ftpServer(username, password, directory)
File "ftptest.py", line 31, in ftpServer
server = FTPServer(('0.0.0.0', 21), handler)
File "/usr/local/lib/python3.6/site-packages/pyftpdlib/servers.py", line 118, in __init__
self.bind_af_unspecified(address_or_socket)
File "/usr/local/lib/python3.6/site-packages/pyftpdlib/ioloop.py", line 1018, in bind_af_unspecified
raise socket.error(err)
OSError: [Errno 98] Address already in use
完善脚本之后
#!/usr/bin/env python3
#coding:utf-8
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
from pyftpdlib.log import LogFormatter
import os
def ftpServer(username, password, directory):
'''
启动一个ftp的server进程
:param username: 需要添加用户的用户名
:param password: 用户的密码
:param directory: 用户的家目录,也就是ftp的数据目录
:return:
'''
# 如果目录不存在,就创建目录
if not os.path.exists(directory):
os.mkdir(directory)
try:
# 添加虚拟用户,验证用户需要用户名、密码、家目录、权限
author = DummyAuthorizer()
author.add_user(username, password, directory, perm="elradfmw")
except OSError as e:
if "Address already in use" in e:
print("21号端口被占用")
# 初始化ftp句柄
handler = FTPHandler
handler.authorizer = author
# 添加被动端口范围
handler.passive_ports = range(2000, 2400)
# 启动server,本地执行,监听21号端口
server = FTPServer(('0.0.0.0', 21), handler)
server.serve_forever()
username = "zhanghao"
password = "aixocm"
directory = "/data/zhanghao"
ftpServer(username, password, directory)
正常输出
[root@zhanghao python-learning]# python3 ftptest.py
[I 2021-01-09 05:40:02] concurrency model: async
[I 2021-01-09 05:40:02] masquerade (NAT) address: None
[I 2021-01-09 05:40:02] passive ports: 2000->2399
[I 2021-01-09 05:40:02] >>> starting FTP server on 0.0.0.0:32, pid=4772 <<<
客户端连接
[root@zhserver zhanghao]# ftp 192.168.0.101
Connected to 192.168.0.101 (192.168.0.101).
220 pyftpdlib 1.5.6 ready.
Name (192.168.0.101:root): zhanghao
331 Username ok, send password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
1.1.3 FTP服务器资源限制
from pyftpdlib.handlers import ThrottledDTPHandler
transport_handler = ThrottledDTPHandler
transport_handler.read_limit = 200 * 1024
transport_handler.write_limit = 100 * 1024
handler.dtp_handler = transport_handler
1.1.4 添加输出log
脚本中添加log,输出到屏幕和日志文件,日志文件名称为ftp.log
from pyftpdlib.log import LogFormatter
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
stream = logging.StreamHandler()
log_file = logging.FileHandler(filename="ftp.log", encoding="utf-8")
stream.setFormatter(LogFormatter())
log_file.setFormatter(LogFormatter())
logger.addHandler(stream)
logger.addHandler(log_file)
1.1.5 用户权限
参数 | 含义 |
---|---|
e | 读权限,改变文件目录 |
l | 读权限,可以列出所有的文件 |
r | 读权限,可以从服务器下载文件 |
a | 写权限,可以上传文件 |
d | 写权限,删除文件 |
f | 写权限,文件重命名 |
m | 写权限,创建文件 |
w | 写权限 |
M | 文件传输的模式 |
2. ftplib客户端实现
from ftplib import FTP
def ftpClient(host_ip, username, password):
'''
ftp客户端程序
:param host_ip:ftp server的IP地址
:param username: 连接server端的用户名
:param password: 用户密码
:return:
'''
ftp_client = FTP(host=host_ip, user=username, passwd=password)
ftp_client.encoding = 'gbk'
# 执行客户端的操作
ftp_client.cwd()