twisted系列教程十六–twisted守护进程
Introduction
到目前為止我們寫的server 還運行在一個終端里面,通過print 語句向外輸出內容.開發的時候這樣做是很有好處的,但是當你部署一個產品的時候這樣就不好了.一個生產環境中的server應該是:
????作為一個守護進程運行,不和任何的終端和會話相連.你不會希望當你登出的時候,你的server 也會退出
????把debug和錯誤信息輸出到一個日志文件中,或者syslog 服務中
????低權限的,比如,用一個低權限的用戶運行
????把它的pid 記到一個文件中,管理員可以很容易的向守護進程發送信號
?
twisted 已經提供了對上面四條的支持.但首先我們首先要先改變一下我們的代碼.
The Concepts
要想理解twisted 你需要學習一些新的概念.最重要的一個概念是Service,一般來說,一個新的概念往往和一個或多個接口對應.
IService
IService接口定義了一個可以被停止和開啟的service.這個service 做什么呢? 你可以做任何你想做的,這個接口僅僅需要一些很少的參數和方法(它定義的少,你可以實現的就多),而不是定義了特定的函數的一種service.
這個接口需要兩個屬性:name 和 running.name屬性只是一個字符串, 例如”fastpoetry”或者None,running 屬性是一個布爾類型,假如這個service被成功的開啟,這個值為true.
我們只會接觸ISservice 的一些方法.我們會略過那些高級的不常用的方法.IService 的兩個基本方法是startService 和stopService:
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}).
????"""
這些方法會完成什么功能要看service要完成的功能.例如,startService 可能會做如下的工作:
加載配置文件,或者
初始化數據庫,或者
開始監聽一個端口,或者
什么也不做
stopService 方法可能做如下的工作:
持久化一些狀態,或者
關閉數據庫連接,或者
停止監聽一個端口,或者
什么也不做
當我們寫我們自己的service 的時候我們需要適當的實現這些方法.對于一些常見的操作,比如監聽一個端口,twisted 已經提供了一個可以直接使用的service.
注意一下stopService 有可能返回一個deferred,這個deferred 會在這個service 完全關閉的時候觸發.這可以讓我們在這個應用完全關閉前去清理一些數據.假如你的service是突然關閉的,你可以只返回None 來代替.
services 可以被組織在一起,可以同時的被開啟或者同時關閉.我們要看的最后一個IService 的方法是setServiceParent,可以把一個service 加入到一個集合中.
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.
????"""
任何一個service 都會有一個parent,這意味著services 可以按等級來組織,這就引出了下面的這個接口.
IServiceCollection
IServiceCollection接口定義了一個可以包含IService 對象的對象.一個service 的集合就是包含如下方法的集合類:
通過service name 查找 service (getServiceNamed)
迭帶集合中的services(__iter__)
向集合中增加一個service(addService)
從集合中移除一個service(removeService)
注意一下一個IServiceCollection 的實現不會自動是一個IService的實現,但是沒有理由一個類不能實現兩個接口(后面我們將會看到一個實例).
Application
twisted 的Application 不是被分開的接口定義的,但是一個Application 對象需要實現 IService 和 IServiceCollection,還有一些其他的我們不會講到的接口.
Application 是最頂層的代表你整個twisted 應用的service.你所有的其他的service 全是Application 的孩子.
去完全的實現你自己的Application 是很少見的.我們今天會用到twisted 已經提供了的實現.
Twisted Logging
twisted 包含了它自己的logging 實現,在模塊twisted.python.log 中,logging 中的一些api 非常簡單,所以我們只包含了一個很簡單的例子在basic-twisted/log.py.如果你感興趣的話,你可以在twisted 源碼中看個究竟.
FastPoetry 2.0
好了,讓我們看一些代碼.我們已經更新我們的fast poetry server了,原代碼在twisted-server-3/fastpoetry.py,首先我們看一些 poetry protocol:
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()
注意我們使用twisted.python.log.msg 函數替代我們原來的輸出語句,來記錄每一個新的連接.
下面是 factory class:
class PoetryFactory(ServerFactory):
????protocol = PoetryProtocol
????def __init__(self, service):
????????self.service = service
你可以看到,詩已經不再存在factory里面,而是存在一個被factory 引用的service 對象里面.注意這個protocol 是怎樣從service 中通過factory 獲取到詩的.最后是service class:
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 去加載我們需要的詩歌文件.
另一個值得一提的是PoetryService 不知道PoetryProtocol 的任何事情.這個service 的唯一的工作就是加載詩,并把它提供給那些需要它的對象.換句話說就是,PoetryService 只負責高層次的提供詩歌內容,而不是低層次的向一個tcp 連接中發送數據.所以這個service 也可以被其他的protocol 使用.我們的例子比較小所以你看不出有多大的便利,如果是現實中的一個service 的實現,你可以想象會給我們帶來多大的便利.
假如這是一個典型的twisted 程序,上面的程序最終不可能只在一個文件中.而是,它應該在某個模塊中.但是根據我們前面的寫代碼的經驗,我們把所有的需要的代碼文件都放在一個文件中了.
Twisted tac files
一個tac 文件是一個twisted application configuration 文件,它會告訴twisted 怎樣構建一個.作為一個配置文件它會為了讓一個application以某種特定的方式運行起來 來選擇配置.換句話說,一個tac 文件代表了一種我們服務的部署,而不是一段可以開啟我們poetry server 的腳本.
假如我們在一臺主機上運行了多個poetry server,我們可以為每一個server 配置一個配置文件.在我們的例子中,這個tac 文件被配置成一個server 在本地10000端口上并提供poetry/ecstasy.txt 文件里的詩.
# configuration parameters
port = 10000
iface = 'localhost'
poetry_file = 'poetry/ecstasy.txt'
twisted 對配置文件里面的變量是一無所知的,我們只是把放在這里.實際上,twisted 只關心整個配置文件中的一個變量,我們在后面會講到.下面我們開始創建我們的application:
# this will hold the services that combine to
# form the poetry server
top_service = service.MultiService()
我們的poetry server 由兩個services 組成,一個是我們上面定義的PoetryService,另一個是twisted 內置的 service–用來創建我們的詩用來服務的socket. 因為這兩個services互相之間有關系,我們會把他們放在一起通過一個MultiService,一個實現了IService 和 IServiceCollection 接口的類.
作為一個service 的集合,MultiService 會把我們的兩個poetry service 組合起來.作為一個service,MultiService 會在它自己啟動的時候同時啟動兩個子service,當它關閉的時候也同時關閉子service.讓我們向集合中加入第一個poetry service:
# 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 提供了一個TCPServer service 用來創建一個和任意factory 相連的 監聽tcp 連接的socket .我們沒有直接用reactor.listenTCP 的原因是tac 文件的工作是讓我們的application準備好,而不是運行它.TCPServer 在被twisted 啟動的時候TCPServer會創建這個socket.
ok,現在我們的service 都被綁定進集合去了.現在我們可以創建我們的Application,并把我們的集合傳給它.
# this variable has to be named 'application'
application = service.Application("fastpoetry")
# this hooks the collection we made to the application
top_service.setServiceParent(application)
在這個腳本中,twisted真正關心的是application 變量.twisted 通過它來找到application(而且必須命名為application),當這個application啟動的時候,我們向其添加的service也會被啟動.
圖片三十四描述了我們上面剛建立的這個application 的結構:
圖片三十四
Running the Server
一個tac文件,我們需要用twisted 來運行它.當然,它也是一個python 文件.所以先讓我們用python 執行一下它看看會發生什么:
python twisted-server-3/fastpoetry.py
如果你這樣做,你會發現什么也沒發生.就像我們之前說的,tac 文件的工作就是讓一個application 做好準備運行的準備.為了提醒tac 文件的特殊應用,一些把它的后綴名改為.tac.twisted 并不關心后綴名是什么.你可以twisted 來運行這個tac 文件:
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
下面是要注意的一些事情:
????你可以看到Twisted logging 系統的輸出,包括PoetryFactory 中的log.msg. twisted 已經為我們安裝了一個logger
????你也可以看到我們的兩個services,PoetryService 和 TCPServer 開始運行
????你會發現命令行并沒有返回,這個說明我們的server 并不是以守護進程運行的.默認的twisted 會以守護進程的方式運行.你可以用 --nodaemon 選項,這樣log信息會輸出到標準輸出.這對調試很有幫助
現在我們可以測試了,你可以用poetry client 或者netcat:
netcat localhost 10000
這個會從server 上獲取一首詩,你還會看到下面的日志輸出:
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 輸出的.如果你做多次請求server,你會發現其他的日志輸出.
現在你按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 不會簡單的崩潰掉,而是干凈的關閉自己并用log 信息記錄清理過程.
A Real Daemon
現在讓我們的server成為一個真正的守護進程:
twistd --python twisted-server-3/fastpoetry.py
這一次我們立即的返回了得到我們的命令行提示符號.如果你看下你當前的目錄你會發現除了twisted.pid 文件以外還有一個twisted.log 文件.
當我們開啟一個守護進程的時候,twisted 會安裝一個log handler ,這樣你的log 就會被記錄到一個文件中而不是輸出到標準輸出.默認的log 文件是twisted.log,在你運行twisted 的當前目錄.但是你可以通過–logfile 來改變log 的路徑.
(略去兩段)
The Twisted Plugin System
現在我們可以用twisted 來啟動守護進程了.用python文件作為我們的配置文件給了我們配置上的很大的靈活性.但是我們經常用不到這么多的靈活性.對于我們的poetry server 來說,我們只關心三個選項:
1,詩的內容
2,server 需要用的端口
3,需要監聽的ip(localhost)
twisted 的插件提供了一個定義帶有多個命令行選項的Application 的方法,twisted 就可以動態的運行.twisted 自身帶了很多的插件. 如果你不帶任何的參數運行twisted 的話,你會發現它帶的一些插件.你可以現在試一下twisted命令.在幫助信息之后,你會看到如下的內容:
?...
????ftp An FTP server.
????telnet A simple, telnet-based remote debugging service.
????socks A SOCKSv4 proxy service.
????...
每一行輸出一個twisted 內置的插件,你可以用twisted 運行他們中的任何一個.
每個插件還會有它自己的輸出信息,你可以用–help 來查看具體的信息,例如:
twistd ftp --help
你可以用下面的命令運行一個ftp 服務:
twistd --nodaemon ftp --port 10001
ok,讓我們從poetry server 上先轉向twisted 插件.同樣的,讓我們先講一些新的概念.
IPlugin
任何的twisted plugin 必須實現twisted.plugin.IPlugin接口,如果你看那個接口的聲明,你會發現它并沒有指定任何的方法.實現IPlugin 最plugin 來說是標明它的身份.當然,如果要實現一些功能,它還要實現一些其他的接口,我們后面會講到.
但是你怎樣知道一個對象是否實現了一個空的接口? zope.interface 包含了一個叫做implements 的方法,你可以用它聲明一個特定的類實現了一個特定的接口.我們會在我們的poetry server 的plugin 版本中看到一個例子.
IServiceMaker
除IPlugin 之外,我們的plugin也會實現IServiceMaker接口.一個實現了IServiceMaker 的對象知道怎樣去創建一個IService.IServiceMaker 指定了三個屬性和一個方法:
????tapname:我們plugin 的名字."tap" 代表了Twisted Application Plugin.
????description:我們plugin 的描述
????options:一個描述我們的plugin 的要接收的命令行參數的對象
????makeService:可以創建一個IService對象,接收option傳來的參數
我們會在我們的下一版本的poetry server 中講到.
Fast Poetry 3.0
現在我們準備去看一下插件版的Fast poetry,代碼在 twisted/plugins/fastpoetry_plugin.py.
你可能已經注意到我們這次的命名規則和其他的例子不太一樣.那是因為twisted 需要插件文件在twisted/plugins 目錄中.這個目錄不必是一個包,并且你可以有多個twisted/plugins 目錄在你的twisted 能找到的路徑中.
我們的plugin 的第一部分包含了一些poetry protocol,factory,service implementations.和以前一樣,這些文件本應該在一個模塊中,但為了方便,我們把他們全部放在一個plugin 中–讓這些代碼的都是自包含的.
下面是 plugin 的命令行選項:
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.'],
????????]
這些代碼指定了這個plugin 的特定的命令行選項.我們下面看我們plugin 最主要的部分–service maker class:
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.
你應該能認出makeService 中的一些代碼,和tac 那一版本的實現一樣.但這次我們不需要自己創建一個Application對象.我們僅僅創建并返回最上層的service.twisted 會幫助我們完成其他的工作.
在聲明這個類之后,還有唯一的一件事情要做:
service_maker = PoetryServiceMaker()
我們的twisted 腳本會發現這個plugin 實例,并用它創建我們的頂層的service.不像tac 文件,這里我們的變量名是可以任意取的.重要的是我們的對象要實現IPlugin 和 IServiceMaker.
既然我們已經完成了我們的plugin,讓我們運行一下.確保你在twisted-intro 目錄,或者確保twisted-intro目錄在你的python 路徑中. 試著運行twisted 你會發現”fastpoetry” 已經在插件列表中了.
你也會發現在twisted/plugins 目錄中出現了一個dropin.cache 的文件,這個文件是用來加快插件的瀏覽的.
你可以獲得我們插件的幫助信息:
twisted fastpoetry --help
你可以在幫助信息中看到fastpoetry中的選項參數.讓我們運行我們的plugin:
twistd fastpoetry --port 10000 --poem poetry/ecstasy.txt
這樣就可以開啟一個fastpoetry 的守護進程.你可以在當前目錄下看到twisted.pid 和twisted.log 文件.你可以用過下面的命令來關閉它:
kill `cat twisted.pid`
Summary
在這一部分,我們學習了怎樣把我們的twisted server 變成一個守護進程.我們還講了twisted 的logging 系統,怎樣用twisted運行一個以守護進程方式運行的twisted application–用tac配置 文件或者用twisted 插件的方式.在第十七部分,我們將講更多的異步編程的原理,還有在twisted 中callback 的另一種組織方式.
總結
以上是生活随笔為你收集整理的twisted系列教程十六–twisted守护进程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql字符串和数字的互相转换
- 下一篇: ZeroMQ接口函数之 :zmq_set