(转) 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. 为诗歌代理服务器建立一个新的插件.

相关文章
相关标签/搜索