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.ActiveDIPpyftpdlib.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()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容