(轉) Twisted :第十六部分 Twisted 進程守護


簡介

目前咱們所寫的服務器僅僅運行在終端窗口,結果經過 print 語句輸出到屏幕.這對於開發來講已經足夠,但對於產品級的部署還遠遠不夠. 健壯的產品級服務器應該:python

  1. 運行一個 daemon 進程,這個進程不與任何終端或用戶會話相關.由於沒有人願意當某用戶登出時服務自動關閉.react

  2. 將調試和錯誤信息發送到一系列滾轉日誌文件, 或者 syslog 服務.git

  3. 放棄太高的權限,好比,在運行前切換到較低權限.github

  4. 保存它的 pid 文件以便管理員方便地向 daemon 發送信號.shell

咱們能夠利用Twisted提供的 twistd 腳本得到全部以上功能. 可是首先須要稍稍修改咱們的代碼.數據庫


IService

IService 接口定義了一個能夠啓動或中止的服務. 這個服務究竟作了些什麼? 答案是任何你喜歡的事情——這個接口只須要自提供的一些通用屬性和方法,無須用戶定義特定的函數.編程

這邊有兩個須要的屬性: 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 可能會:

  • 儲存狀態,或

  • 關閉打開的數據庫鏈接,或

  • 中止監聽某端口,或

  • 什麼也不作.

當咱們寫自定義服務時, 要恰當地實現這些方法.對於一些通用的行爲,好比監聽某端口,Twisted提供了現成的服務可使用.

注意 stopService 能夠選擇地返回 deferred,要求當服務徹底關閉時被激發.這容許咱們的服務在結束以後與整個程序終止以前完成清理工做.若是你須要服務當即關閉,能夠僅僅返回 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

IServiceCollection 接口定義了一個對象,它可包含若干個 IService 對象.一個服務集合僅僅是一個普通的類容器,具備如下方法:

Application

一個Twisted Application 不是經過一個單獨的接口定義的.相反, Application 對象須要實現 IService 和IServiceCollection 接口以及一些咱們不曾涉及的接口.

Application 是一個表明你整個Twisted應用的最頂層的服務. 在你 daemon 中的全部其餘服務將是這個 Application 對象的兒子(甚至孫子,等等.).

其實須要你本身實現 Application 的機會很小,Twisted已經提供了一個當下經常使用的實現.


Twisted Logging

Twisted在其模塊 twistd.python.log 中包含了其自身的日誌架構.因爲寫日誌的基本 API 很是簡單, 咱們僅僅介紹一個小例子: basic-twisted/log.py,若是你感興趣更多細節能夠瀏覽Twisted模塊.

咱們也不詳細介紹安裝日誌處理程序的 API,由於 twistd 腳本會幫咱們作.


FastPoetry 2.0

好吧,讓咱們看看代碼.咱們已經將快詩服務器升級爲使用 twistd. 源碼在 twisted-server-3/fastpoetry.py. 首先咱們有了 詩歌協議:

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 函數去記錄每一個新鏈接.

這裏是 工廠類:

class PoetryFactory(ServerFactory):

      protocol = PoetryProtocol

      def __init__(self, service):
          self.service = service

正如你看到的,詩再也不儲存在工廠中,而是儲存在一個被工廠引用的服務對象上。注意這邊協議是如何經過工廠從服務得到詩歌.最後,看一下 服務類:

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提供了一個基類供自定義實現,同時具備方便的默認行爲.

咱們使用 twisted.application.service.Service 類實現 PoetryService.

這個基類提供了全部必要方法的默認實現,因此咱們只須要實現個性化的行爲.在上面的例子中,咱們只重載了 startService 方法來加載詩歌文件.注:咱們仍然調用了相應的基類方法(它爲咱們設置 running 屬性).

另外值得一提的是: PoetryService 對象不知道關於 PoetryProtocol 的任何細節.這裏服務的任務僅僅是加載詩歌以及爲其餘須要詩歌的對象提供接口.也就是說, PoetryService 只關心提供詩歌的更高層的細節,而不是關心諸如經過 TCP 鏈接發送詩歌這樣的更底層的細節.因此一樣的服務能夠被另外的協議使用,如 UDP 或 XML-RPC.雖然對於簡單的服務好處不大,但你能夠想象其在更實際服務實現中的優點.

若是這是一個典型的Twisted程序,到目前咱們看到的代碼都不應出如今這個文件裏.它們應該在一些模塊當中(也許是fastpoetry 和 fastpoetry.service).可是,遵循咱們的慣例會使這些例子自包含,也就是在一個腳本中包含了全部東西.


Twisted tac files

這個腳本的其他部分包含一般做爲完整內容的 Twisted tac 文件. tac 文件是一個 Twisted Application Configuration 文件,它告訴 twistd 怎樣去構建一個應用.做爲一個配置文件,它負責選擇設置(如端口,詩歌文件位置,等)來以一種特定的方式運行這個應用.換句話說, tac 表明咱們服務的一個特定部署(在這個端口服務這首詩),而不是啓動任何詩歌服務的通常腳本.

若是咱們在同一個域運行多個詩歌服務,咱們將爲每個服務準備一個 tac 文件(所以你能夠明白爲何 tac 文件一般不包含任何通常目的的代碼).在咱們的例子中, tac 文件被配置爲使 poetry/ecstasy.txt 運行在迴環接口的10000號端口:

# configuration parameters
port = 10000
iface = 'localhost'
poetry_file = 'poetry/ecstasy.txt'

注意 twistd 並不知道這些特定變量,咱們僅僅將這些配置值統一的放在這裏.事實上, twistd 只關心整個文件中的一個變量,咱們即將看到.下面咱們開始創建咱們的應用:

# this will hold the services that combine to form the poetry server
top_service = service.MultiService()

咱們的詩歌服務器將包含兩個服務, 上文定義的 PoetryService,和一個Twisted的內置服務,它將創建服務咱們詩歌的監聽套接字.因爲這兩個服務明顯的相關,咱們用 MultiService 將它們組織在一塊兒,一個實現 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)

這是很是簡單的內容.咱們僅建立了 PoetryService,而後用 setServiceParent 方法將其添加到服務集合.下面咱們添加 TCP監聽器:

# 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爲建立鏈接到任意工廠的 TCP 監聽套接字提供了 TCPServer 服務(這裏是 PoetryFactory),咱們沒有直接調用reactor.listenTCP 由於 tac 文件的工做是使咱們的應用準備好開始,而不是實際啓動它. 這裏 TCPServer 將在被 twistd啓動後建立套接字.

你可能注意到咱們沒有爲任何服務起名字.爲服務起名不是必需的,而僅是一個可選項,若是你但願在運行時查找服務.由於咱們不須要這個功能,因此這裏沒有爲服務命名.

既然咱們已經將兩個服務綁定到服務集合.現只需建立咱們的應用,而且將它添加到集合:

# 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顯示了咱們剛剛創建的應用的結構:

_static//p16_application.png

圖34: fastpoetry 應用的結構圖


Running the Server

讓咱們的新服務器運轉起來.做爲 tac 文件,咱們須要用 twistd 啓動它.固然,它僅僅是一個普通的Python文件.因此咱們首先用python 命令啓動,再看看會發生什麼:

python twisted-server-3/fastpoetry.py

若是你這樣作,會發現什麼也沒有發生!正如前文所述, tac 文件的工做是使咱們的應用準備好運行,而不是實際運行它.做爲tac 文件這個特殊目的的提醒,人們將它的擴展名規定爲 .tac 而不是 .py.可是 twistd 腳本實際並不區分擴展名.

讓咱們用 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

須要注意的幾點:

  1. 你能夠看到Twisted日誌系統的輸出, 包括 PoetryFactory 調用 log.msg.可是咱們在 tac 文件中沒有安裝 logger, 因此twistd 會幫咱們安裝.

  2. 你能夠看到咱們的兩個主要服務 PoetryService 和 TCPServer 啓動了.

  3. shell提示符不會返回. 這代表咱們的服務器沒有以守護進程方式運行. 默認地, twistd 會以守護進程方式運行服務器(這正是 twistd 存在的緣由), 可是若是你包含"--nodaemon" 選項,那麼 twistd 將以一個常規shell進程的方式運行你的服務器,同時會將日誌輸出導向到標準輸出. 這對於調試 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 方法調用 log.msg.當你向服務器發送更多請求時, 你將看到更多的日誌條目.

如今能夠用 Ctrl-C 來終止這個服務器. 你能夠看到以下輸出:

^C2010-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並無簡單地崩潰, 而是優雅地關閉並將日誌信息告訴你.

好啦, 如今再次啓動服務器:

twistd --nodaemon --python twisted-server-3/fastpoetry.py

如今打開另外一個shell並切換到 twisted-intro 目錄. 其中有一個叫 twistd.pid 的文件. 它是被 twistd 建立的, 包含咱們這個運行服務器進程號. 試一下下面的方法來關閉服務器:

kill `cat twistd.pid`

注意當服務器關閉後, twistd.pid 文件消失了, 它被 twistd 清理了.


A Real Daemon

如今讓咱們以守護進程的方式啓動服務器, 這是 twistd 的默認方式:

twistd --python twisted-server-3/fastpoetry.py

此次咱們當即看到shell提示符返回. 當你列出目錄中的文件時,會發現除了 twistd.pid 文件,又出現了 twistd.log 文件,它記錄了以前顯示在shell窗口的日誌信息.

當啓動一個守護進程時, twistd 安裝一個日誌管理器將條目寫入一個文件而不是標準輸出. 默認的日誌文件是 twistd.log, 它出如今你運行 twistd 的目錄中,可是你能夠經過"--logfile"來改變它的位置. twistd 安裝的的日誌管理器將滾動輸出日誌信息, 確保其不超過 1M.

你能夠經過列出操做系統上的全部進程來查看正在運行的服務器. 你不妨經過取另外一首詩來測試這個服務器. 你能夠看到記錄每一個詩歌請求的新條目出如今日誌文件中.

因爲這個服務器再也不與shell相連(或者除了 init 的任何其餘進程), 你不能經過 Ctrl-C 關閉它. 做爲一個真的守護進程, 即便你登出它也繼續運行.可是你能夠經過 twistd.pid 文件終止這個進程:

kill `cat twistd.pid`

隨後, 關閉消息出如今日誌文件中, twistd.pid 文件被移除, 服務器中止.

檢查一下其餘的 twistd 啓動選項是個不錯的主意. 例如,你能夠告訴 twistd 在啓動進程守護前切換到另外一個用戶或組帳戶(是一種當你的服務器不須要安全防範措施取消權限的典型方法). 咱們就不進一步探討那些額外的選項了,你能夠經過 twistd 的 --help 本身研究它們.


Twisted 插件系統

如今咱們已經經過 twistd 啓動真正的守護進程服務器. 這很是完美,並且事實上咱們的配置文件是純Python源碼文件,這一點爲咱們設置帶來巨大便利. 可是咱們有時用不到這樣的便利性.對於詩歌服務器,咱們一般只關心一小部分選項:

  1. 須要服務的詩歌

  2. 服務端口

  3. 監聽接口

爲了幾個簡單的變量創建一個 tac 文件顯得有點小題大作. 若是咱們可以經過 twistd 選項指定這些值將很是方便. Twisted的插件系統容許咱們能夠這樣作.

Twisted插件經過定義 Application 提供了一種方法, 能夠實現個性化的命令行選項, 進而 twistd 動態的發現和運行. Twisted自己具備一套插件,你能夠經過運行不帶參數的 twistd 命令來查看它們. 如今就試一試, 在 twisted-intro 目錄外. 在幫助部分後面,你能夠看到以下輸出:

...
ftp                An FTP server.
telnet             A simple, telnet-based remote debugging service.
socks              A SOCKSv4 proxy service.
...

每一行顯示了一個Twisted內置的插件, 你能夠用 twistd 運行它們.

每一個插件一樣有它們本身的選項,你能夠經過 --help 來發現它們. 讓咱們看看 ftp 插件有什麼選項:

twistd ftp --help

注意咱們須要將 --help 放在 ftp 後面而不是 twistd 後面, 由於咱們想獲得 ftp 的可選項.

咱們能夠像運行詩歌服務器同樣運行 ftp 服務器. 但因爲它是一個插件,咱們能夠僅僅經過它的名字運行:

twistd --nodaemon ftp --port 10001

以上命令以非守護進程的方式在端口 10001 上運行 ftp 插件. 注意 twistd 的 nodaemon 選項出如今插件名字的前面,插件特定選項 port 出如今插件名字的後面. 正如咱們的詩歌服務器同樣,你能夠用 Ctrl-C 中止它.

OK, 讓咱們把詩歌服務器轉化爲Twisted的插件. 首先咱們須要介紹一些新概念.


IPlugin

任何Twisted插件都須要實現 twisted.plugin.IPlugin 接口. 若是你瀏覽這個接口的聲明, 你會發現它沒有指定任何方法. 實現IPlugin 接口僅僅至關於一個插件在說:"你好,我是插件!"以便 twistd 找到它. 固然,出於實用考慮,它須要實現一些其餘接口,咱們很快會介紹.

可是你怎樣知道一個對象實現了一個空接口? zope.interface 包含了一個叫作 implements 的函數,它能夠用來聲明一個特定類實現了一個特定的接口. 咱們將在插件版的詩歌服務器中看到這種使用.


IServiceMaker

除了 IPlugin,咱們的插件還實現 IServiceMaker 接口. 一個實現了 IServiceMaker 接口的對象知道如何建立 IService,它將成爲運行程序的核心. IServiceMaker 指定了三個屬性和一個方法:

  1. tapname: 表明插件名字的字符串. "tap"表明"Twisted Application Plugin". 注:老版本的Twisted還使用"tapfiles"文件,不過這個功能如今已經取消了.

  2. description: 插件的描述, twistd 將以它做爲幫助信息輸出.

  3. options: 一個表明這個插件接受的命令行選項的對象.

  4. makeService: 一個建立 IService 對象的方法,需提供一些特定的命令行選項.

咱們將在下一個版本的詩歌服務器中看到怎樣將上述內容組織在一塊兒.

Fast Poetry 3.0

如今咱們已經爲插件版本的"Fast Poetry"作好準備,它位於 twisted/plugins/fastpoetry_plugin.py.

你可能注意到與其餘例子不一樣, 咱們命名了一個不一樣的目錄. 這是由於 twistd 須要插件文件位於 twisted/plugins 目錄中, 同時在你的Python搜索路徑上. 這個目錄沒必要是一個包(也就是, 沒必要包含任何 __init__.py 文件), 並且在路徑上能夠有多個 twisted/plugins 目錄, twistd 都會找到它們. 這個插件的實際文件名是什麼也沒有關係, 可是一個好的方案是根據應用所表明的含義來命名, 就像咱們在這裏作的.

咱們的插件開頭部分一樣包括詩歌協議,工廠,以及像 tac 文件中所實現的服務.如前所述,這些代碼一般應該單獨的存在於一個模塊中,但出於咱們例子自包含的目的,仍是將它們放在插件文件中.

下面將 聲明 這個插件的命令行選項:

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 函數來聲明咱們的類同時實現 IServiceMaker 和 IPlugin 接口.

你應該從以前的 tac 文件辨認出 makeService 中的代碼, 可是此次咱們不須要本身創建一個 Application 對象, 咱們僅僅建立並返回最頂層服務,這樣咱們的程序就能夠運行, twistd 來處理其他的事情. 注意咱們是如何使用 options 參數來提取插件傳遞給 twistd 的特定命令行選項.

定義了上述類, 還有 一步 :

service_maker = PoetryServiceMaker()

twistd 腳本會發現咱們插件的實例並使用它構建最頂層服務. 與 tac 文件不一樣的是, 選擇什麼變量名沒有關係, 關鍵是咱們的對象實現了 IPlugin 和 IServiceMaker 接口.

既然已經建立了插件, 讓咱們運行它. 確保你位於 twisted-intro 目錄中, 或者 twisted-intro 位於Python的搜索目錄中. 下面單獨運行twistd,你會看到"fastpoetry"是列出的插件之一,後面顯示插件文件中定義的描述文字.

你一樣會注意到 twisted/plugins 目錄中出現了一個 dropin.cache 的新文件. 這個文件由 twistd 建立, 用來加速後續掃描插件的.

如今讓咱們獲取一些關於插件的幫助信息:

twistd fastpoetry --help

你能夠看到關於 fastpoetry 插件選項的幫助性文字. 最後,運行這個插件:

twistd fastpoetry --port 10000 --poem poetry/ecstasy.txt

這將以守護進程方式啓動 fastpoetry 服務器. 與前面例子同樣, 你會在當期文件夾看到 twistd.pid 和 twistd.log 文件. 測試完咱們的服務器, 用一下命令關閉:

kill `cat twistd.pid`

這就是如何製做Twisted插件的方法.


總 結

在這個部分, 咱們學習了將Twisted服務器轉換到支持長時間運行的守護進程模式. 咱們還涉及了Twisted日誌系統以及如何使用twistd 以守護進程模式啓動一個Twisted應用程序, 即或者經過 tac 配置文件或者Twisted插件. 在 第十七部分部分, 咱們將轉向異步編程的更基本的主題和另一種結構化Twisted回調函數的方法.


參 考 練 習

  1. 修正 tac 文件以在另一個端口服務另一首詩. 使用另一個 MultiService 對象以保持每首詩的服務是分離的.

  2. 建立一個新的 tac 文件來啓動一個詩歌代理服務器.

  3. 修正插件文件使其可接受第二個可選詩歌文件和服務端口.

  4. 爲詩歌代理服務器建立一個新的插件.

相關文章
相關標籤/搜索