1. 搭建FTP服务器
FTP(File Transfer Protocol,文件传输协议)运行在TCP协议上,使用两个端口,即数据端口和命令端口,也称控制端口。默认情况下,20是数据端口,21是命令端口。
FTP有两种传输模式:主动模式和被动模式。
(1)主动模式:客户端首先从任意的非特殊端口n(大于1023的端口,也是客户端的命令端口)连接FTP服务器的命令端口(默认是21),向服务发出命令PORT n+1,告诉服务器自己使用n+1端口作为数据端口进行数据传输,然后在n+1端口监听。服务器收到PORT n+1后向客户端返回一个'ACK',然后服务器从它自己的数据端口(20)到客户端先前指定的数据端口(n+1端口)的连接,最后客户端向服务器返回一个'ACK',过程结束。
(2)被动模式:为了解决服务器发起到客户的连接问题,人们开发了被动FTP,或者叫作PASV,当客户端通知服务器处于被动模式时才启用。在被动模式FTP中,命令连接和数据连接都由客户端发起。当开启一个FTP连接时,客户端打开两个任意的非特权本地端口(大于1023)。第一个端口连接服务器的21端口,但与主动方式的FTP不同,客户端不会提交PORT命令并允许服务器来回连接数据端口,而是提交PASV命令。这样做的结果是服务器会开启一个任意的非特权端口,并发送PORT P命令给客户端,然后客户端发起从本地端口N+1到服务器的端口P的连接用来传送数据。
总结:主动方式对FTP服务器的管理有利,但对客户端的管理不利。因为FTP服务器企图与客户端的高位随机端口建立连接,而这个端口很有可能被客户端的防火墙阻塞掉。被动方式对FTP客户端的管理有利,但对服务器端的管理不利。因为客户端要与服务器端建立两个连接,其中一个连到一个高位随机端口,而这个端口很有可能被服务器端的防火墙阻塞掉。
使用Python搭建一个FTP服务器需要pyftpdlib模块,安装非常简单:
pip install pyftpdlib
(1)快速搭建一个简单的FTP服务器。执行:
python -m pyftpdlib -p 21
即可在执行命令所在的目录下建立一个端口为21的供下载文件的FTP服务器,注意Linux系统需要root用户才能使用默认端口21,windows系统中目录文件名可能是乱码,原因是pyftpdlib内部使用utf8,而windows使用gbk,参照下面的步骤可解决windows系统的乱码问题。
首先,找到pyftpdlib源文件所在的目录。
[root@localhost ~]# python3
Python 3.6.5 (default, Jun 29 2019, 14:06:04)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyftpdlib
>>> pyftpdlib.__path__
['/usr/local/python3.6.5/lib/python3.6/site-packages/pyftpdlib']
其次,在目录pyftpdlib源文件所在的目录找到文件filesystems.py和handlers.py,先备份。
[root@localhost ~]# cd /usr/local/python3.6.5/lib/python3.6/site-packages/pyftpdlib
[root@localhost pyftpdlib]# ls
authorizers.py filesystems.py __init__.py log.py prefork.py servers.py
_compat.py handlers.py ioloop.py __main__.py __pycache__ test
再次,打开filesystems.py,找到
yield line.encode('utf8, self.cmd_channel.unicode_errors)
共有两处,修改'utf8'为'gbk',保存退出。
打开handlers.py,找到
return bytes.decode('utf8', self.unicode_errors)
修改utf8为gbk,保存退出。
最后,验证乱码已解决。
(2)搭建一个具有访问权限,可配置相关信息的FTP服务器(ftpserver.py)。
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler,ThrottledDTPHandler
from pyftpdlib.servers import FTPServer
from pyftpdlib.log import LogFormatter
import logging
#记录日志,默认情况下日志仅输出到屏幕(终端),这里既输出到屏幕又输出到文件,方便日志查看
logger = logging.getLogger()
logger.setLevel(logging.INFO)
ch = logging.StreamHandler()
fh = logging.FileHandler(filename='myftpserver.log',encoding='utf-8') #默认的方式是追加到文件
ch.setFormatter(LogFormatter())
fh.setFormatter(LogFormatter())
logger.addHandler(ch) #将日志输出至屏幕
logger.addHandler(fh) #将日志输出至文件
# 实例化虚拟用户,这是FTP验证首要条件
authorizer = DummyAuthorizer()
# 添加用户权限和路径,括号内的参数是(用户名、密码、用户目录、权限),可以为不同的用户添加不同的目录和权限
authorizer.add_user("user", "12345", "/ftp", perm="elradfmw")
# 添加匿名用户,只需要路径
authorizer.add_anonymous("/ftp")
# 初始化ftp句柄
handler = FTPHandler
handler.authorizer = authorizer
# 添加被动端口范围
handler.passive_ports = range(2000,2333)
# 下载上传速度
dtp_handler = ThrottledDTPHandler
dtp_handler.read_limit = 300 * 1024 #300kb/s
dtp_handler.write_limit 300 * 1024 #300kb/s
handler.dtp_handler = dtp_handler
# 监听ip和端口,linux里需要root用户
server = FTPServer(("0.0.0.0", 21), handler)
# 最大连接数
server.max_cons = 150
server.max_cons_per_ip = 15
# 开始服务,自带日志打印信息
server.serve_forever()
执行python ftpserver.py
[root@localhost ~]# python3 ftpserver.py
[I 2020-02-22 17:03:23] concurrency model: async
[I 2020-02-22 17:03:23] masquerade (NAT) address: None
[I 2020-02-22 17:03:23] passive ports: 2000->2332
[I 2020-02-22 17:03:23] >>> starting FTP server on 0.0.0.0:21, pid=7716 <<<
同时该目录下也会生成一个myftpserver.log文件,文件内容与屏幕上的信息一致。
下面我们登录该FTP并列出目录进行测试:
C:\Users\willi>ftp 192.168.9.43
连接到 192.168.9.43。
220 pyftpdlib 1.5.6 ready.
530 Log in with USER and PASS first.
用户(192.168.9.43:(none)): user
331 Username ok, send password.
密码:
230 Login successful.
ftp> dir
200 Active data connection established.
125 Data connection already open. Transfer starting.
226 Transfer complete.
ftp> dir
200 Active data connection established.
125 Data connection already open. Transfer starting.
drwxr-xr-x 2 root root 6 Feb 22 09:19 test
226 Transfer complete.
ftp: 收到 64 字节,用时 0.00秒 32.00千字节/秒。
ftp>
对应服务器的打印信息:
[root@localhost ~]# python3 ftpserver.py
[I 2020-02-22 17:03:23] concurrency model: async
[I 2020-02-22 17:03:23] masquerade (NAT) address: None
[I 2020-02-22 17:03:23] passive ports: 2000->2332
[I 2020-02-22 17:03:23] >>> starting FTP server on 0.0.0.0:21, pid=7716 <<<
[I 2020-02-22 17:18:40] 192.168.9.206:8439-[] FTP session opened (connect)
[I 2020-02-22 17:18:48] 192.168.9.206:8439-[user] USER 'user' logged in.
[I 2020-02-22 17:24:25] 192.168.9.206:8439-[user] Control connection timed out.
[I 2020-02-22 17:24:25] 192.168.9.206:8439-[user] FTP session closed (disconnect).
至此,一个FTP服务器已经搭建完成,可以修改ftpserver.py来满足自己的需求。
用户权限的代码及说明:
2. 编写FTP客户端程序
在实际应用中可能经常访问FTP服务器来上传或下载文件,Python也可以替我们做这些。
# -*- coding: utf-8 -*-
# !/usr/local/bin/python
# Time: 2020/2/22 22:22:22
# Description:
# File Name: ftpclient.py
from ftplib import FTP
#登录FTP
ftp = FTP('localhost', user='user', passwd='12345')
#设置编码方式,由于在windows系统,设置编码为gbk
ftp.encoding = 'gbk'
# 切换目录
ftp.cwd('test')
#列出文件夹的内容
ftp.retrlines('LIST') # ftp.dir()
#下载文件 note.txt
ftp.retrbinary('RETR note.txt', open('note.txt', 'wb').write)
#上传文件 ftpserver.py
ftp.storbinary('STOR ftpserver.py', open('ftpserver.py', 'rb'))
#查看目录下的文件详情
for f in ftp.mlsd(path='/test'):
print(f)
运行结果如下:
[root@localhost ~]# python3 ftpclient.py
-rw-r--r-- 1 root root 0 Feb 22 16:58 note.txt
('note.txt', {'modify': '20200222165853', 'perm': 'radfw', 'size': '0', 'type': 'file', 'unique': 'fd00gf155'})
('ftpserver.py', {'modify': '20200222165941', 'perm': 'radfw', 'size': '1681', 'type': 'file', 'unique': 'fd00gf156'})
注意:操作前需要先进入/ftp目录创建对应的目录和文件。