twisted系列教程十六–twisted守护进程

来源:互联网 发布:http请求 mac地址 编辑:程序博客网 时间:2024/06/06 21:37
原文:http://krondo.com/blog/?p=1209
 Chinese by Yang Xiaowei andCheng Luo

 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 的另一种组织方式.
0 0
原创粉丝点击