Python实现FTP服务器和客户端
基礎(chǔ)知識(shí)
FTP只通過(guò)TCP連接,FTP不同于其他服務(wù)的是它使用了兩個(gè)端口,?一個(gè)數(shù)據(jù)端口和一個(gè)命令端口(或稱為控制端口)。
通常21端口是命令端口,20端口是數(shù)據(jù)端口。當(dāng)混入主動(dòng)/被動(dòng)模式的概念時(shí),數(shù)據(jù)端口就有可能不是20了
FTP主動(dòng)模式
在主動(dòng)模式下,FTP客戶端隨機(jī)開啟一個(gè)大于1024的端口N向服務(wù)器的21號(hào)端口發(fā)起連接,
然后開放N+1號(hào)端口進(jìn)行監(jiān)聽,并向服務(wù)器發(fā)出PORT?N+1命令。
服務(wù)器接收到命令后,會(huì)用其本地的FTP數(shù)據(jù)端口(通常是20)來(lái)連接客戶端指定的端口N+1,進(jìn)行數(shù)據(jù)傳輸。
- FTP服務(wù)器命令(21)端口接受客戶端任意端口(客戶端初始連接)
- FTP服務(wù)器命令(21)端口到客戶端端口(>1023)(服務(wù)器響應(yīng)客戶端命令)
- FTP服務(wù)器數(shù)據(jù)(20)端口到客戶端端口(>1023)(服務(wù)器初始化數(shù)據(jù)連接到客戶端數(shù)據(jù)端口)
- FTP服務(wù)器數(shù)據(jù)(20)端口接受客戶端端口(>1023)(客戶端發(fā)送ACK包到服務(wù)器的數(shù)據(jù)端口)
主動(dòng)模式的優(yōu)點(diǎn):
服務(wù)端配置簡(jiǎn)單,利于服務(wù)器安全管理,服務(wù)器只需要開放21端口
主動(dòng)模式的缺點(diǎn):
如果客戶端開啟了防火墻,或客戶端處于內(nèi)網(wǎng)(NAT網(wǎng)關(guān)之后), 那么服務(wù)器對(duì)客戶端端口發(fā)起的連接可能會(huì)失敗
FTP被動(dòng)模式
在被動(dòng)模式下,FTP庫(kù)戶端隨機(jī)開啟一個(gè)大于1024的端口N向服務(wù)器的21號(hào)端口發(fā)起連接,同時(shí)會(huì)開啟N+1號(hào)端口。
然后向服務(wù)器發(fā)送PASV命令,通知服務(wù)器自己處于被動(dòng)模式。
服務(wù)器收到命令后,會(huì)開放一個(gè)大于1024的端口P進(jìn)行監(jiān)聽,然后用PORT?P命令通知客戶端,自己的數(shù)據(jù)端口是P。
客戶端收到命令后,會(huì)通過(guò)N+1號(hào)端口連接服務(wù)器的端口P,然后在兩個(gè)端口之間進(jìn)行數(shù)據(jù)傳輸
- FTP服務(wù)器命令(21)端口接受客戶端任意端口(客戶端初始連接)
- FTP服務(wù)器命令(21)端口到客戶端端口(>1023)(服務(wù)器響應(yīng)客戶端命令)
- FTP服務(wù)器數(shù)據(jù)端口(>1023)接受客戶端端口(>1023)(客戶端初始化數(shù)據(jù)連接到服務(wù)器指定的任意端口)
- FTP服務(wù)器數(shù)據(jù)端口(>1023)到客戶端端口(>1023)(服務(wù)器發(fā)送ACK響應(yīng)和數(shù)據(jù)到客戶端的數(shù)據(jù)端口)
被動(dòng)模式缺點(diǎn):
服務(wù)器配置管理稍顯復(fù)雜,不利于安全,服務(wù)器需要開放隨機(jī)高位端口以便客戶端可以連接,因此大多數(shù)FTP服務(wù)軟件都可以手動(dòng)配置被動(dòng)端口的范圍
被動(dòng)模式的優(yōu)點(diǎn):
對(duì)客戶端網(wǎng)絡(luò)環(huán)境沒有要求
?
使用python來(lái)實(shí)現(xiàn)FTP服務(wù)
安裝模塊 pyftpdlib
pip3 install pyftpdlibhttp://pyftpdlib.readthedocs.io/en/latest/tutorial.html教程
源碼
https://github.com/giampaolo/pyftpdlib
使用:
from pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.handlers import FTPHandler from pyftpdlib.servers import FTPServer# 新建一個(gè)用戶組 authorizer = DummyAuthorizer() # 將用戶名,密碼,指定目錄,權(quán)限 添加到里面 authorizer.add_user("fan", "root", "E:/", perm="elr") # adfmw # 這個(gè)是添加匿名用戶,任何人都可以訪問(wèn),如果去掉的話,需要輸入用戶名和密碼,可以自己嘗試 authorizer.add_anonymous("E:/")handler = FTPHandler handler.authorizer = authorizer # 開啟服務(wù)器 server = FTPServer(("127.0.0.1", 21), handler) server.serve_forever()然后將程序運(yùn)行起來(lái),接下來(lái)看一下效果,在瀏覽器上ftp://localhost/
用戶權(quán)限
讀取權(quán)限:
"e"?=更改目錄(CWD,CDUP命令)
"l"?=列表文件(LIST,NLST,STAT,MLSD,MLST,SIZE命令)
"r"?=從服務(wù)器檢索文件(RETR命令)
寫入權(quán)限:
"a"?=將數(shù)據(jù)追加到現(xiàn)有文件(APPE命令)
"d"?=刪除文件或目錄(DELE,RMD命令)
"f"?=重命名文件或目錄(RNFR,RNTO命令)
"m"?=創(chuàng)建目錄(MKD命令)
"w"?=將文件存儲(chǔ)到服務(wù)器(STOR,STOU命令)
"M"=更改文件模式/權(quán)限(SITE CHMOD命令)
"T"=更改文件修改時(shí)間(SITE MFMT命令)
開啟被動(dòng)端口模式
#添加被動(dòng)端口范圍 handler.passive_ports = range(8300, 8500) from pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.handlers import FTPHandler from pyftpdlib.servers import FTPServer# 新建一個(gè)用戶組 authorizer = DummyAuthorizer() # 將用戶名,密碼,指定目錄,權(quán)限 添加到里面 authorizer.add_user("test", "1234", "E:/", perm="elradfmw") # adfmw # 這個(gè)是添加匿名用戶,任何人都可以訪問(wèn),如果去掉的話,需要輸入用戶名和密碼,可以自己嘗試 authorizer.add_anonymous("E:/")handler = FTPHandler handler.authorizer = authorizer#添加被動(dòng)端口范圍 handler.passive_ports = range(8300, 8500)# 開啟服務(wù)器 server = FTPServer(("127.0.0.1", 2121), handler) server.serve_forever()-----------輸出-----------------[I 2018-08-06 15:37:16] >>> starting FTP server on 127.0.0.1:2121, pid=8564 <<< [I 2018-08-06 15:37:16] concurrency model: async [I 2018-08-06 15:37:16] masquerade (NAT) address: None [I 2018-08-06 15:37:16] passive ports: 8300->8499 [I 2018-08-06 15:37:32] 127.0.0.1:54898-[] FTP session opened (connect)讀取用戶列表,建立FTP
#-----------user.ini------[alex] password=123 perm=elradfmwM home=D:/[egon] password=123456 perm=elradfmwM home=D:/#------------ftpdemofrom pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.handlers import FTPHandler, ThrottledDTPHandler from pyftpdlib.servers import FTPServer import configparser import loggingIP = '127.0.0.1'PORT = '2121'# 上傳速度 100kb/s MAX_UPLOAD = 100 * 1024# 下載速度 100kb/s MAX_DOWNLOAD = 100 * 1024# 最大連接數(shù) MAX_CONS = 100# 最多IP數(shù) MAX_PER_IP = 10# 被動(dòng)端口范圍,注意被動(dòng)端口數(shù)量要比最大IP數(shù)多,否則可能出現(xiàn)無(wú)法連接的情況 PASSIVE_PORTS = (8300, 8500)# 是否開啟匿名訪問(wèn) on|off ENABLE_ANONYMOUS = 'off'# 匿名用戶目錄 ANONYMOUS_PATH = 'E:/DEVTOOL/'# 日志文件 LOGING_NAME = 'pyftp.log'# 歡迎信息 WELCOME_MSG = 'Welcome to my ftp'# 新建一個(gè)用戶組 authorizer = DummyAuthorizer()# 讀取用戶配置 config = configparser.ConfigParser() config.read('user.ini') user_list = config.sections() for user in user_list:passwd = config[user]["password"]perm = config[user]["perm"]home_dir = config[user]["home"]# 將用戶名,密碼,指定目錄,權(quán)限 添加到里面authorizer.add_user(user, passwd, homedir=home_dir, perm=perm)# 添加匿名用戶 只需要路徑 if ENABLE_ANONYMOUS == 'on':authorizer.add_anonymous(ANONYMOUS_PATH)# 下載上傳速度設(shè)置 dtp_handler = ThrottledDTPHandler dtp_handler.read_limit = MAX_DOWNLOAD dtp_handler.write_limit = MAX_UPLOAD# 初始化ftp句柄 handler = FTPHandler handler.authorizer = authorizer# 添加被動(dòng)端口范圍 handler.passive_ports = range(PASSIVE_PORTS[0], PASSIVE_PORTS[1])# 歡迎信息 handler.banner = WELCOME_MSG# 監(jiān)聽ip 和 端口 server = FTPServer((IP, PORT), handler)# 最大連接數(shù) server.max_cons = MAX_CONS server.max_cons_per_ip = MAX_PER_IP# 開始服務(wù) print('FTP開始服務(wù) ', (IP, PORT)) server.serve_forever()?
?
基于twisted
#!/usr/bin/env python # -*- coding: utf-8 -*- """a very simple ftp server by twisted """ __author__ = "Bobning(nb5550606@gmail.com)" import sys, os sys.path.append('../../..') os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' from twisted.protocols import ftp from twisted.cred import portal, checkers, credentials, error as credError from twisted.internet import reactor, defer from zope.interface import implements from twisted.python import filepath from django.contrib.auth import authenticate from django.contrib.auth.models import User class UserChecker: implements(checkers.ICredentialsChecker) credentialInterfaces = (credentials.IUsernamePassword,) def __init__(self): "passwords: a dict-like object mapping usernames to passwords" def requestAvatarId(self, credentials): user = authenticate(username=credentials.username, password=credentials.password) if user is not None: if user.is_active: return defer.succeed(credentials.username) else: return defer.fail(credError.UnauthorizedLogin("access denied")) else: return defer.fail(credError.UnauthorizedLogin("No such user")) class MyFTPRealm: implements(portal.IRealm) def __init__(self, anonymousRoot): self.anonymousRoot = filepath.FilePath(anonymousRoot) def requestAvatar(self, avatarId, mind, *interfaces): for iface in interfaces: if iface is ftp.IFTPShell: if avatarId is checkers.ANONYMOUS: avatar = ftp.FTPAnonymousShell(self.anonymousRoot) else: try: user = User.objects.get(username=avatarId) ftpdir = user.ftp.all()[0].ftpdir except: raise "沒有該用戶" avatar = ftp.FTPShell(filepath.FilePath(ftpdir.encode("utf-8"))) return ftp.IFTPShell, avatar, getattr(avatar, "logout", lambda:None) raise NotImplementedError("only IFTPShell interface is supported by this realm") class MyFtpServer(ftp.FTP): def __init__(self, *args, **kw): super(ftp.FTP, self).__init__(*args, **kw) def dataReceived(self, data): self.transport.write(data) if __name__ == "__main__": p = portal.Portal(MyFTPRealm("")) p.registerChecker(UserChecker()) factory = ftp.FTPFactory(MyFtpServer()) factory.portal = p reactor.listenTCP(2121, factory) reactor.run()?
客戶端實(shí)現(xiàn)
import ftplib import sys#獲取服務(wù)器的ip地址(如192.168.1.107),使用sys.argv可以從命令行參數(shù)里面獲取 if len(sys.argv) < 2:tmp = input("please input server address:")sys.argv.append(tmp) server_address = sys.argv[1] #創(chuàng)建FTP實(shí)例,并顯示歡迎界面 ftp = ftplib.FTP(server_address) print(ftp.getwelcome()) #登錄,輸入服務(wù)器里添加過(guò)的用戶名和口令 ftp.login('user', 'pass')#文件上傳 def upload(fname):fd = open(fname, 'rb')new_name = input("input new name:")#以二進(jìn)制的形式上傳ftp.storbinary("STOR %s" % new_name, fd)fd.close()print("upload finished")#文件下載 def download(fname):#構(gòu)建文件的存儲(chǔ)路徑,這里用的是D盤,可以自行設(shè)置new_path = "D:\\FTPdownload\\" + fnamefd = open(new_path, 'wb')#以二進(jìn)制形式下載,注意第二個(gè)參數(shù)是fd.write,上傳時(shí)是fdftp.retrbinary("RETR %s" % fname, fd.write)fd.close()print("download finished")def main():#選擇操作,上傳、下載、退出op = input("what do you want?(u/d/q)")if op == "u":#輸入文件完整路徑,必要時(shí)可以用絕對(duì)路徑fname = input("input the file of path:")upload(fname)elif op == "d":fname = input("input the file name:")download(fname)else:print("quit now!")ftp.quit()if __name__ == '__main__':main()附帶一些ftplib庫(kù)的相關(guān)操作:
ftp.cwd(pathname) # 設(shè)置FTP當(dāng)前操作的路徑 ftp.dir() # 顯示目錄下所有目錄的信息 ftp.nlst() # 獲取目錄下的文件 ftp.mkd(pathname) # 新建遠(yuǎn)程目錄 ftp.rmd(dirname) # 刪除遠(yuǎn)程目錄 ftp.pwd() # 返回當(dāng)前所在位置 ftp.delete(filename) # 刪除遠(yuǎn)程文件 ftp.rename(old_name, new_name) #將fromname改為toname ftp.storbinary('STOR filename.txt',file_handel,[bufsize]) # 上傳目標(biāo)文件,最后一個(gè)參數(shù)可以不填 ftp。retrbinary('RETR filename.txt',file_handel,[bufsize]) # 下載FTP文件?
總結(jié)
以上是生活随笔為你收集整理的Python实现FTP服务器和客户端的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python global文件的全局变量
- 下一篇: Python面向对象编程之Zope.in