目前我們所寫的服務器僅僅運行在終端窗口,結果通過 print
語句輸出到屏幕.這對于開發(fā)來說已經(jīng)足夠,但對于產(chǎn)品級的部署還遠遠不夠. 健壯的產(chǎn)品級服務器應該:
我們可以利用Twisted提供的 twistd
腳本獲得所有以上功能. 但是首先需要稍稍修改我們的代碼.
IService 接口定義了一個可以啟動或停止的命名服務. 這個服務究竟做了些什么? 答案是任何你喜歡的事情——這個接口只需要自提供的一些通用屬性和方法,無須用戶定義特定的函數(shù).
這邊有兩個需要的屬性: name
和 running
.其中 name
屬性是一個字符串,如 "fastpoetry
",或者 None
如果你不想給這個服務起名字. running
屬性是 Boolean 變量,如果服務成功啟動,值為 True
.
下面我們只涉及 IService
的某些方法, 跳過那些很顯而易見的或者在簡單的Twisted程序中用不到的高級方法. startService 和 stopService 是 IService
的兩個關鍵方法:
def startService():
"""
Start the service.
"""
def stopService():
"""
Stop the service.
@rtype: L{Deferred}
@return: a L{Deferred} which is triggered when the service has
finished shutting down. If shutting down is immediate, a
value can be returned (usually, C{None}).
"""
同樣,這些方法做什么取決于服務的需求,比如 startService
可能會:
stopService
可能會:
當我們寫自定義服務時, 要恰當?shù)貙崿F(xiàn)這些方法.對于一些通用的行為,比如監(jiān)聽某端口,Twisted提供了現(xiàn)成的服務可以使用.
注意 stopService
可以選擇地返回 deferred
,要求當服務完全關閉時被激發(fā).這允許我們的服務在結束之后與整個程序終止之前完成清理工作.如果你需要服務立即關閉,可以僅僅返回 None
而不是 deferred
.
服務可以被組織成集合以便一起啟動和停止.下面來看看這里最后一個 IService
方法: setServiceParent,它添加一個服務到集合:
def setServiceParent(parent):
"""
Set the parent of the service.
@type parent: L{IServiceCollection}
@raise RuntimeError: Raised if the service already has a parent
or if the service has a name and the parent already has a child
by that name.
"""
任何服務都可以有雙親,這意味著服務可以被組織為層級結構.這把我們引向了今天討論的另一個接口.
IServiceCollection 接口定義了一個對象,它可包含若干個 IService
對象.一個服務集合僅僅是一個普通的類容器,具有以下方法:
一個Twisted Application
不是通過一個單獨的接口定義的.相反, Application
對象需要實現(xiàn) IService
和 IServiceCollection
接口以及一些我們未曾涉及的接口.
Application
是一個代表你整個Twisted應用的最頂層的服務. 在你 daemon
中的所有其他服務將是這個 Application
對象的兒子(甚至孫子,等等.).
其實需要你自己實現(xiàn) Application
的機會很小,Twisted已經(jīng)提供了一個當下常用的實現(xiàn).
Twisted在其模塊 twistd.python.log 中包含了其自身的日志架構.由于寫日志的基本 API 非常簡單, 我們僅僅介紹一個小例子: basic-twisted/log.py,如果你感興趣更多細節(jié)可以瀏覽Twisted模塊.
我們也不詳細介紹安裝日志處理程序的 API,因為 twistd
腳本會幫我們做.
好吧,讓我們看看代碼.我們已經(jīng)將快詩服務器升級為使用 twistd
. 源碼在 twisted-server-3/fastpoetry.py. 首先我們有了 詩歌協(xié)議:
class PoetryProtocol(Protocol):
def connectionMade(self):
poem = self.factory.service.poem
log.msg('sending %d bytes of poetry to %s'
% (len(poem), self.transport.getPeer()))
self.transport.write(poem)
self.transport.loseConnection()
注意沒有使用 print
語句,而是使用 twisted.python.log.msg
函數(shù)去記錄每個新連接.
這里是 工廠類:
class PoetryFactory(ServerFactory):
protocol = PoetryProtocol
def __init__(self, service):
self.service = service
正如你看到的,詩不再儲存在工廠中,而是儲存在一個被工廠引用的服務對象上。注意這邊協(xié)議是如何通過工廠從服務獲得詩歌.最后,看一下 服務類:
class PoetryService(service.Service):
def __init__(self, poetry_file):
self.poetry_file = poetry_file
def startService(self):
service.Service.startService(self)
self.poem = open(self.poetry_file).read()
log.msg('loaded a poem from: %s' % (self.poetry_file,))
就像許多其他接口類一樣,Twisted提供了一個基類供自定義實現(xiàn),同時具有方便的默認行為.
我們使用 twisted.application.service.Service 類實現(xiàn) PoetryService
.
這個基類提供了所有必要方法的默認實現(xiàn),所以我們只需要實現(xiàn)個性化的行為.在上面的例子中,我們只重載了 startService
方法來加載詩歌文件.注:我們?nèi)匀徽{(diào)用了相應的基類方法(它為我們設置 running
屬性).
另外值得一提的是: PoetryService
對象不知道關于 PoetryProtocol
的任何細節(jié).這里服務的任務僅僅是加載詩歌以及為其他需要詩歌的對象提供接口.也就是說, PoetryService
只關心提供詩歌的更高層的細節(jié),而不是關心諸如通過 TCP 連接發(fā)送詩歌這樣的更底層的細節(jié).所以同樣的服務可以被另外的協(xié)議使用,如 UDP 或 XML-RPC.雖然對于簡單的服務好處不大,但你可以想象其在更實際服務實現(xiàn)中的優(yōu)勢.
如果這是一個典型的Twisted程序,到目前我們看到的代碼都不該出現(xiàn)在這個文件里.它們應該在一些模塊當中(也許是 fastpoetry
和 fastpoetry.service
).但是,遵循我們的慣例會使這些例子自包含,也就是在一個腳本中包含了所有東西.
這個腳本的其余部分包含通常作為完整內(nèi)容的 Twisted tac
文件. tac
文件是一個 Twisted Application Configuration
文件,它告訴 twistd
怎樣去構建一個應用.作為一個配置文件,它負責選擇設置(如端口,詩歌文件位置等)來以一種特定的方式運行這個應用.換句話說, tac
代表我們服務的一個特定部署(在這個端口服務這首詩),而不是啟動任何詩歌服務的一般腳本.
如果我們在同一個域運行多個詩歌服務,我們將為每一個服務準備一個 tac
文件(因此你可以明白為什么 tac
文件通常不包含任何一般目的的代碼).在我們的例子中, tac
文件被配置為使 poetry/ecstasy.txt 運行在回環(huán)接口的10000號端口:
# configuration parameters
port = 10000
iface = 'localhost'
poetry_file = 'poetry/ecstasy.txt'
注意 twistd
并不知道這些特定變量,我們僅僅將這些配置值統(tǒng)一的放在這里.事實上, twistd
只關心整個文件中的一個變量,我們即將看到.下面我們開始建立我們的應用:
# this will hold the services that combine to form the poetry server
top_service = service.MultiService()
我們的詩歌服務器將包含兩個服務, 上文定義的 PoetryService
,和一個Twisted的內(nèi)置服務,它將建立服務我們詩歌的監(jiān)聽套接字.由于這兩個服務明顯的相關,我們用 MultiService
(一個實現(xiàn) IServiceCollection
和 IService
的類) 將它們組織在一起.
作為一個服務集合, MultiService
把我們的詩歌服務組織在一起.同時作為一個服務, MultiService
啟動時將啟動它的子服務,關閉時將關閉它的子服務.讓我們向服務集合 添加 第一個詩歌服務:
# the poetry service holds the poem. it will load the poem when it is
# started
poetry_service = PoetryService(poetry_file)
poetry_service.setServiceParent(top_service)
這是非常簡單的內(nèi)容.我們僅創(chuàng)建了 PoetryService
,然后用 setServiceParent
方法將其添加到服務集合.下面我們添加 TCP 監(jiān)聽器:
# the tcp service connects the factory to a listening socket. it will
# create the listening socket when it is started
factory = PoetryFactory(poetry_service)
tcp_service = internet.TCPServer(port, factory, interface=iface)
tcp_service.setServiceParent(top_service)
Twisted為創(chuàng)建連接到任意工廠的 TCP 監(jiān)聽套接字提供了 TCPServer
服務(這里是 PoetryFactory
),我們沒有直接調(diào)用 reactor.listenTCP
因為 tac
文件的工作是使我們的應用準備好開始,而不是實際啟動它. 這里 TCPServer
將在被 twistd
啟動后創(chuàng)建套接字.
你可能注意到我們沒有為任何服務起名字.為服務起名不是必需的,而僅是一個可選項,如果你希望在運行時查找服務.因為我們不需要這個功能,所以這里沒有為服務命名.
既然我們已經(jīng)將兩個服務綁定到服務集合.現(xiàn)只需創(chuàng)建我們的應用,并且將它添加到集合:
# this variable has to be named 'application'
application = service.Application("fastpoetry")
# this hooks the collection we made to the application
top_service.setServiceParent(application)
在這個腳本中 twistd
所關心的唯一變量就是 application
. twistd
正是通過它找到那個需要啟動的應用(所以這個變量必須被命名為 applicaton
).當應用被啟動時,我們添加到它的所有服務都會被啟動.
圖34顯示了我們剛剛建立的應用的結構:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p16_application.png" alt="" />
讓我們的新服務器運轉起來.作為 tac
文件,我們需要用 twistd
啟動它.當然,它僅僅是一個普通的Python文件.所以我們首先用 python
命令啟動,再看看會發(fā)生什么:
python twisted-server-3/fastpoetry.py
如果你這樣做,會發(fā)現(xiàn)什么也沒有發(fā)生!正如前文所述, tac
文件的工作是使我們的應用準備好運行,而不是實際運行它.作為 tac
文件這個特殊目的的提醒,人們將它的擴展名規(guī)定為 .tac
而不是 .py
.但是 twistd
腳本實際并不區(qū)分擴展名.
讓我們用 twistd
腳本來實際運行這個服務器:
twistd --nodaemon --python twisted-server-3/fastpoetry.py
運行以上命令后會看到如下輸出:
2010-06-23 20:57:14-0700 [-] Log opened.
2010-06-23 20:57:14-0700 [-] twistd 10.0.0 (/usr/bin/python 2.6.5) starting up.
2010-06-23 20:57:14-0700 [-] reactor class: twisted.internet.selectreactor.SelectReactor.
2010-06-23 20:57:14-0700 [-] __builtin__.PoetryFactory starting on 10000
2010-06-23 20:57:14-0700 [-] Starting factory <__builtin__.PoetryFactory instance at 0x14ae8c0>
2010-06-23 20:57:14-0700 [-] loaded a poem from: poetry/ecstasy.txt
需要注意的幾點:
PoetryFactory
調(diào)用 log.msg
.但是我們在 tac
文件中沒有安裝 logger
, 所以 twistd
會幫我們安裝.PoetryService
和 TCPServer
啟動了.twistd
會以守護進程方式運行服務器(這正是 twistd
存在的原因), 但是如果你包含"--nodaemon
" 選項,那么 twistd
將以一個常規(guī)shell進程的方式運行你的服務器,同時會將日志輸出導向到標準輸出. 這對于調(diào)試 tac
文件非常有用.下面測試取詩服務器, 通過我們的詩歌代理或者 netcat
命令:
netcat localhost 10000
這將從服務器抓取詩歌,并且你可以看到一行如下的日志:
2010-06-27 22:17:39-0700 [__builtin__.PoetryFactory] sending 3003 bytes
of poetry to IPv4Address(TCP, '127.0.0.1', 58208)
這個日志來自 PoetryProtocol.connectionMade
方法調(diào)用 log.msg
.當你向服務器發(fā)送更多請求時, 你將看到更多的日志條目.
現(xiàn)在可以用 Ctrl-C
來終止這個服務器. 你可以看到如下輸出:
2010-06-29 21:32:59-0700 [-] Received SIGINT, shutting down.
2010-06-29 21:32:59-0700 [-] (Port 10000 Closed)
2010-06-29 21:32:59-0700 [-] Stopping factory <__builtin__.PoetryFactory instance at 0x28d38c0>
2010-06-29 21:32:59-0700 [-] Main loop terminated.
2010-06-29 21:32:59-0700 [-] Server Shut Down.
正如你看到的, Twisted并沒有簡單地崩潰, 而是優(yōu)雅地關閉并將日志信息告訴你.
好啦, 現(xiàn)在再次啟動服務器:
twistd --nodaemon --python twisted-server-3/fastpoetry.py
現(xiàn)在打開另一個shell并切換到 twisted-intro
目錄. 其中有一個叫 twistd.pid
的文件. 它是被 twistd
創(chuàng)建的, 包含我們這個運行服務器進程號. 試一下下面的方法來關閉服務器:
kill `cat twistd.pid`
注意當服務器關閉后, twistd.pid
文件消失了, 它被 twistd
清理了.
現(xiàn)在讓我們以守護進程的方式啟動服務器, 這是 twistd
的默認方式:
twistd --python twisted-server-3/fastpoetry.py
這次我們立即看到shell提示符返回. 當你列出目錄中的文件時,會發(fā)現(xiàn)除了 twistd.pid
文件,又出現(xiàn)了 twistd.log
文件,它記錄了之前顯示在shell窗口的日志信息.
當啟動一個守護進程時, twistd
安裝一個日志管理器將條目寫入一個文件而不是標準輸出. 默認的日志文件是 twistd.log
, 它出現(xiàn)在你運行 twistd
的目錄中,但是你可以通過"--logfile
"來改變它的位置. twistd
安裝的的日志管理器將滾動輸出日志信息, 確保其不超過 1M
.
你可以通過列出操作系統(tǒng)上的所有進程來查看正在運行的服務器. 你不妨通過取另一首詩來測試這個服務器. 你可以看到記錄每個詩歌請求的新條目出現(xiàn)在日志文件中.
由于這個服務器不再與shell相連(或者除了 init 的任何其他進程), 你不能通過 Ctrl-C
關閉它. 作為一個真的守護進程, 即使你登出它也繼續(xù)運行.但是你可以通過 twistd.pid
文件終止這個進程:
kill `cat twistd.pid`
隨后, 關閉消息出現(xiàn)在日志文件中, twistd.pid
文件被移除, 服務器停止.
檢查一下其他的 twistd
啟動選項是個不錯的主意. 例如,你可以告訴 twistd
在啟動進程守護前切換到另一個用戶或組賬戶(是一種當你的服務器不需要安全防范措施取消權限的典型方法). 我們就不進一步探討那些額外的選項了,你可以通過 twistd
的 --help
自己研究它們.
現(xiàn)在我們已經(jīng)通過 twistd
啟動真正的守護進程服務器. 這非常完美,而且事實上我們的配置文件是純Python源碼文件,這一點為我們設置帶來巨大便利. 但是我們有時用不到這樣的便利性.對于詩歌服務器,我們通常只關心一小部分選項:
為了幾個簡單的變量建立一個 tac
文件顯得有點小題大做. 如果我們能夠通過 twistd
選項指定這些值將非常方便. Twisted的插件系統(tǒng)允許我們可以這樣做.
Twisted插件通過定義 Application
提供了一種方法, 可以實現(xiàn)個性化的命令行選項, 進而 twistd
動態(tài)的發(fā)現(xiàn)和運行. Twisted本身具有一套插件,你可以通過運行不帶參數(shù)的 twistd
命令來查看它們. 現(xiàn)在就試一試, 在 twisted-intro
目錄外. 在幫助部分后面,你可以看到如下輸出:
...
ftp An FTP server.
telnet A simple, telnet-based remote debugging service.
socks A SOCKSv4 proxy service.
...
每一行顯示了一個Twisted內(nèi)置的插件, 你可以用 twistd
運行它們.
每個插件同樣有它們自己的選項,你可以通過 --help
來發(fā)現(xiàn)它們. 讓我們看看 ftp
插件有什么選項:
twistd ftp --help
注意我們需要將 --help
放在 ftp
后面而不是 twistd
后面, 因為我們想得到 ftp
的可選項.
我們可以像運行詩歌服務器一樣運行 ftp
服務器. 但由于它是一個插件,我們可以僅僅通過它的名字運行:
twistd --nodaemon ftp --port 10001
以上命令以非守護進程的方式在端口 10001
上運行 ftp
插件. 注意 twistd
的 nodaemon
選項出現(xiàn)在插件名字的前面,插件特定選項 port
出現(xiàn)在插件名字的后面. 正如我們的詩歌服務器一樣,你可以用 Ctrl-C
停止它.
OK, 讓我們把詩歌服務器轉化為Twisted的插件. 首先我們需要介紹一些新概念.
任何Twisted插件都需要實現(xiàn) twisted.plugin.IPlugin 接口. 如果你瀏覽這個接口的聲明, 你會發(fā)現(xiàn)它沒有指定任何方法. 實現(xiàn) IPlugin
接口僅僅相當于一個插件在說:"你好,我是插件!"以便 twistd
找到它. 當然,出于實用考慮,它需要實現(xiàn)一些其他接口,我們很快會介紹.
但是你怎樣知道一個對象實現(xiàn)了一個空接口? zope.interface
包含了一個叫做 implements
的函數(shù),它可以用來聲明一個特定類實現(xiàn)了一個特定的接口. 我們將在插件版的詩歌服務器中看到這種使用.
除了 IPlugin
,我們的插件還實現(xiàn) IServiceMaker
接口. 一個實現(xiàn)了 IServiceMaker
接口的對象知道如何創(chuàng)建 IService
,它將成為運行程序的核心. IServiceMaker
指定了三個屬性和一個方法:
tapname
: 代表插件名字的字符串. "tap
"代表"Twisted Application Plugin". 注:老版本的Twisted還使用"tapfiles
"文件,不過這個功能現(xiàn)在已經(jīng)取消了.description
: 插件的描述, twistd
將以它作為幫助信息輸出.options
: 一個代表這個插件接受的命令行選項的對象.makeService
: 一個創(chuàng)建 IService
對象的方法,需提供一些特定的命令行選項.我們將在下一個版本的詩歌服務器中看到怎樣將上述內(nèi)容組織在一起.
現(xiàn)在我們已經(jīng)為插件版本的"Fast Poetry
"做好準備,它位于 twisted/plugins/fastpoetry_plugin.py.
你可能注意到與其他例子不同, 我們命名了一個不同的目錄. 這是因為 twistd
需要插件文件位于 twisted/plugins
目錄中, 同時在你的Python搜索路徑上. 這個目錄不必是一個包(也就是, 不必包含任何 __init__.py
文件), 而且在路徑上可以有多個 twisted/plugins
目錄, twistd
都會找到它們. 這個插件的實際文件名是什么也沒有關系, 但是一個好的方案是根據(jù)應用所代表的含義來命名, 就像我們在這里做的.
我們的插件開頭部分同樣包括詩歌協(xié)議,工廠,以及像 tac
文件中所實現(xiàn)的服務.如前所述,這些代碼通常應該單獨的存在于一個模塊中,但出于我們例子自包含的目的,還是將它們放在插件文件中.
下面將 聲明 這個插件的命令行選項:
class Options(usage.Options):
optParameters = [
['port', 'p', 10000, 'The port number to listen on.'],
['poem', None, None, 'The file containing the poem.'],
['iface', None, 'localhost', 'The interface to listen on.'],
]
以上代碼指定可以放在 twistd
命令后面使用的插件特定選項的名字.
這里就不必進一步解釋上述選項的含義了,其含義很顯然. 下面我們來看一下插件的主要部分 服務制造類:
class PoetryServiceMaker(object):
implements(service.IServiceMaker, IPlugin)
tapname = "fastpoetry"
description = "A fast poetry service."
options = Options
def makeService(self, options):
top_service = service.MultiService()
poetry_service = PoetryService(options['poem'])
poetry_service.setServiceParent(top_service)
factory = PoetryFactory(poetry_service)
tcp_service = internet.TCPServer(int(options['port']), factory,
interface=options['iface'])
tcp_service.setServiceParent(top_service)
return top_service
這里你可以看到如何使用 zope.interface.implements
函數(shù)來聲明我們的類同時實現(xiàn) IServiceMaker
和 IPlugin
接口.
你應該從之前的 tac
文件辨認出 makeService
中的代碼, 但是這次我們不需要自己建立一個 Application
對象, 我們僅僅創(chuàng)建并返回最頂層服務,這樣我們的程序就可以運行, twistd
來處理其余的事情. 注意我們是如何使用 options
參數(shù)來提取插件傳遞給 twistd
的特定命令行選項.
定義了上述類, 還有 一步 :
service_maker = PoetryServiceMaker()
twistd
腳本會發(fā)現(xiàn)我們插件的實例并使用它構建最頂層服務. 與 tac
文件不同的是, 選擇什么變量名沒有關系, 關鍵是我們的對象實現(xiàn)了 IPlugin
和 IServiceMaker
接口.
既然已經(jīng)創(chuàng)建了插件, 讓我們運行它. 確保你位于 twisted-intro
目錄中, 或者 twisted-intro
位于Python的搜索目錄中. 下面單獨運行 twistd
,你會看到"fastpoetry
"是列出的插件之一,后面顯示插件文件中定義的描述文字.
你同樣會注意到 twisted/plugins
目錄中出現(xiàn)了一個 dropin.cache
的新文件. 這個文件由 twistd
創(chuàng)建, 用來加速后續(xù)掃描插件的.
現(xiàn)在讓我們獲取一些關于插件的幫助信息:
twistd fastpoetry --help
你可以看到關于 fastpoetry
插件選項的幫助性文字. 最后,運行這個插件:
twistd fastpoetry --port 10000 --poem poetry/ecstasy.txt
這將以守護進程方式啟動 fastpoetry
服務器. 與前面例子一樣, 你會在當期文件夾看到 twistd.pid
和 twistd.log
文件. 測試完我們的服務器, 用一下命令關閉:
kill `cat twistd.pid`
這就是如何制作Twisted插件的方法.
在這個部分, 我們學習了將Twisted服務器轉換到支持長時間運行的守護進程模式. 我們還涉及了Twisted日志系統(tǒng)以及如何使用 twistd
以守護進程模式啟動一個Twisted應用程序, 即或者通過 tac
配置文件或者Twisted插件. 在第十七部分 我們將轉向異步編程的更基本的主題和另外一種結構化Twisted回調(diào)函數(shù)的方法.
tac
文件以在另外一個端口服務另外一首詩. 使用另外一個 MultiService
對象以保持每首詩的服務是分離的.tac
文件來啟動一個詩歌代理服務器.本部分原作參見: dave @ http://krondo.com/blog/?p=2345
本部分翻譯內(nèi)容參見luocheng @ https://github.com/luocheng/twisted-intro-cn/blob/master/p16.rst