目前咱们所写的服务器仅仅运行在终端窗口,结果经过 print
语句输出到屏幕.这对于开发来讲已经足够,但对于产品级的部署还远远不够. 健壮的产品级服务器应该:python
运行一个 daemon 进程,这个进程不与任何终端或用户会话相关.由于没有人愿意当某用户登出时服务自动关闭.react
将调试和错误信息发送到一系列滚转日志文件, 或者 syslog 服务.git
放弃太高的权限,好比,在运行前切换到较低权限.github
咱们能够利用Twisted提供的 twistd
脚本得到全部以上功能. 可是首先须要稍稍修改咱们的代码.数据库
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
对象.一个服务集合仅仅是一个普通的类容器,具备如下方法:
经过名字查找某服务( getServiceNamed )
遍历集合中的服务( __iter__)
添加一个服务到集合( addService)
从集合中移除一个服务( removeService)
一个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显示了咱们刚刚创建的应用的结构:
图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
须要注意的几点:
你能够看到Twisted日志系统的输出, 包括 PoetryFactory
调用 log.msg
.可是咱们在 tac
文件中没有安装 logger
, 因此twistd
会帮咱们安装.
你能够看到咱们的两个主要服务 PoetryService
和 TCPServer
启动了.
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源码文件,这一点为咱们设置带来巨大便利. 可是咱们有时用不到这样的便利性.对于诗歌服务器,咱们一般只关心一小部分选项:
须要服务的诗歌
服务端口
监听接口
为了几个简单的变量创建一个 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 的函数,它能够用来声明一个特定类实现了一个特定的接口. 咱们将在插件版的诗歌服务器中看到这种使用.
除了 IPlugin
,咱们的插件还实现 IServiceMaker
接口. 一个实现了 IServiceMaker
接口的对象知道如何建立 IService
,它将成为运行程序的核心. IServiceMaker
指定了三个属性和一个方法:
tapname: 表明插件名字的字符串. "tap"表明"Twisted Application Plugin". 注:老版本的Twisted还使用"tapfiles"文件,不过这个功能如今已经取消了.
description: 插件的描述, twistd
将以它做为帮助信息输出.
options: 一个表明这个插件接受的命令行选项的对象.
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回调函数的方法.
修正 tac
文件以在另一个端口服务另一首诗. 使用另一个 MultiService
对象以保持每首诗的服务是分离的.
建立一个新的 tac
文件来启动一个诗歌代理服务器.
修正插件文件使其可接受第二个可选诗歌文件和服务端口.
为诗歌代理服务器建立一个新的插件.