-
设计Twisted应用程序
by{ guangboo }, published {2009-11-12}, Tag { Python / Twisted / }
2.4 设计Twisted应用程序
2.4.1 目标
本文讲述好的Twisted应用是如何构造的,对Twisted初学者来说这很有用的,他们希望书写整洁,易维护的代码,这反映了良好的编程习惯。 读者会想熟悉使用Twisted来进行Deferreds异步(8页)及服务端(13页)和客户端(17页)编程。
2.4.2 标准设计的示例: TwistedQuotes
TwistedQuotes是一个很简单的插件,它很好的展示了Twisted的强大,它会输出很小的内核功能 ——每日报价——它能通过Twisted的支持接口来访问:网页、email、即时消息、特定的每日报价协议等。 设置项目TwistedQuotes查看《建立TwistedQuotes项目目录》(22页)。
应用核心预览quoters.py
from random import choice from zope.interface import implements from TwistedQuotes import quoteproto class StaticQuoter: """ Return a static quote. """ implements(quoteproto.IQuoter) def __init__(self, quote): self.quote = quote def getQuote(self): return self.quote class FortuneQuoter: """ Load quotes from a fortune-format file. """ implements(quoteproto.IQuoter) def __init__(self, filenames): self.filenames = filenames def getQuote(self): quoteFile = file(choice(self.filenames)) quotes = quoteFile.read().split('\n%\n') quoteFile.close() return choice(quotes)此代码清单为我们展示了Twisted Quotes系统是怎么一回事,该代码没有任何与外界交流的方式,但它提供了一个简介和明确的抽象:“给我每日报价”。
注意,此模块还没有导入任何Twisted功能!这种方式是为了系统的集成。如果你的“业务对象”没有卡在UI中,你就可以让这个模块以不同的协议、GUI、文件格式等集成那些对象。拥有这样的类就提供了相互之间分离的方式,允许彼此见单独使用。
这种方式下,Twisted本身对你的程序逻辑的影响是微乎其微的,尽管Twisted“dot products”具有很强的交互性,他们也采用这种做法。你可以单独使用他们,因为彼此独立。他们以明确的方式通信,并且只在通信提供了一些额外的特性。因此,你可以拿twisted.enterprise和twisted.web一起使用,但两者并不彼此依赖,因为他们都围绕着Deferreds来集成(102页)。
你的Twisted应用也应该尽量做到这种方式,有(至少)一个实现你特定功能的模块独立于任何UI代码。
下一步,我们会需要以展示给用户的方式来想象这个抽象逻辑。我们要通过编写Twisted服务端协议来实现,它会响应连接它的客户端,给客户端发生一个报价,然后关闭连接。注意:不要太注重它的细节——90%的用户接口实现都和Twisted没有关系,有大量的文章介绍不同的实现方式。
quoteproto.py
from zope.interface import Interface from twisted.internet.protocol import Factory, Protocol class IQuoter(Interface): """ An object that returns quotes. """ def getQuote(): """ Return a quote. """ class QOTD(Protocol): def connectionMade(self): self.transport.write(self.factory.quoter.getQuote()+'\r\n') self.transport.loseConnection() class QOTDFactory(Factory): """ A factory for the Quote of the Day protocol. @type quoter: L{IQuoter} provider @ivar quoter: An object which provides L{IQuoter} which will be used by the L{QOTD} protocol to get quotes to emit. """ protocol = QOTD def __init__(self, quoter): self.quoter = quoter这是一个非常简单的Protocol实现,这里再次介绍了这个模式。Protocol基本上没有自己的逻辑,刚好可以与一个生成报价(一个Quoter)的对象和可以向TCP连接(一个Transport)传输字节的对象绑在一起。当客户端连接服务器时,QOTD实例被创建,它的connectionMade方法被调用。
QOTDFactory的作用是指定Twisted框架如何创建可以处理连接的Protocol实例,Twisted不会实例化QOTDFactory,你要之后在Twisted插件中自己完成。
注意:你可以在《服务端》(13页)中查阅关于Protocol和Factory更多信息。
我们只要有了抽象——Quoter——我们就有了将其连接到网络的机制——QOTD协议——下一步要做的事情就是将功能链上的最好一环节摆在抽象和用户间。该环节允许用户选择一个Quoter并配置协议,编写该配置在《Applicatoin HOWTO》介绍(160页)。
-
建立 Twistedquotes应用
by{ guangboo }, published {2009-11-12}, Tag { Python / Twisted / }
2.3 建立 TwistedQuotes应用
2.3.1 目标.
本文档介绍如何使用其他一些文档来建立TwistedQuotes应用,例如Twisted应用设计(24页)。
2.3.2 建立TwistedQuotes项目目录
为运行Twisted Quotes示例,你会需要做下面的工作:
- 在系统中创建TwistedQuotes目录;
- 将下面的文件放到TwistedQuotes目录下:
__init__.py"""Twisted Quotes."""
quoters.pyfrom random import choice from zope.interface import implements from TwistedQuotes import quoteproto class StaticQuoter: """ Return a static quote. """ implements(quoteproto.IQuoter) def __init__(self, quote): self.quote = quote def getQuote(self): return self.quote class FortuneQuoter: """ Load quotes from a fortune-format file. """ implements(quoteproto.IQuoter) def __init__(self, filenames): self.filenames = filenames def getQuote(self): quoteFile = file(choice(self.filenames)) quotes = quoteFile.read().split('\n%\n') quoteFile.close() return choice(quotes)quoteproto.pyfrom zope.interface import Interface from twisted.internet.protocol import Factory, Protocol class IQuoter(Interface): """ An object that returns quotes. """ def getQuote(): """ Return a quote. """ class QOTD(Protocol): def connectionMade(self): self.transport.write(self.factory.quoter.getQuote()+'\r\n') self.transport.loseConnection() class QOTDFactory(Factory): """ A factory for the Quote of the Day protocol. @type quoter: L{IQuoter} provider @ivar quoter: An object which provides L{IQuoter} which will be used by the L{QOTD} protocol to get quotes to emit. """ protocol = QOTD def __init__(self, quoter): self.quoter = quoterplugins.tmlregister("Quote of the Day TAP Builder", "TwistedQuotes.quotetap", description=""" Example of a TAP builder module. """, type="tap", tapname="qotd") - 将TwsitedQuotes目录的上一级目录添加到Python path,例如,如果TwistedQuotes目录是/tmp/TwistedQuotes,那么就将/tmp添加到Python path。在Unix下,可以使用export PYTHONPATH=/my/stuff:$PYTHONPATH,Winodws下,在系统变量PYTHONPATH前添加/my/stuff;。
- 在Python解释器中试着导入包:
Python 2.1.3 (#1, Apr 20 2002, 22:45:31) [GCC 2.95.4 20011002 (Debian prerelease)] on linux2 Type "copyright", "credits" or "license" for more information. >>> import TwistedQuotes >>> # No traceback means you’re fine.
-
Python装饰器
by{ guangboo }, published {2009-11-09}, Tag { Python / }
python装饰器介绍
Python 2.2中引入的 classmethod() 和 staticmethod() 内置函数,你可以这样调用classmethod():
class A: def foo(self, y): print y foo = classmethod(foo)也可以这样:
class A: @classmethod def foo(self, y): print y虽然classmethod是内置函数,但它和普通的函数没有两样,因此你也可以创建自己的函数转化逻辑,如:
def mydecorator(func): print 'mydecorator was called.' func.name = 'myfun' return func
该修饰器将稍微修改了原函数的逻辑:首先打印一段文字“mydecorator was called.”,然后给原函数添加了一个name属性,属性值为"myfun",之后再将函数放回,使用方法和调用结果如下:
class A: @mydecorator def foo(self, y): print y if __name__ == '__main__': a = A() a.foo(1)运行结果为:
mydecorator was called. 1
以上简介我们可以大致了解了python装饰器的作用。
装饰器的调用逻辑
如上一节的示例中我们发现,使用装饰器的时候是@mydecorator,或者是foo=mydecorator(foo),而不是@mydecorator(),或者foo=mydecorator(foo)()。显然,我们要的装饰器是个函数,而不是函数的结果。因为我们要将我们要修饰的函数foo(即老函数)传递给修饰函数mydecorator,并且修饰函数mydecorator也返回一个新函数(上一的新函数是在老函数的基础上添加了一个属性)。
带参数装饰器
上一节的介绍装饰器的调用过程,我们可以明白,@mydecorator是个函数,而且是个函数就可以,实际上是必须要实现__call__方法的对象就可以了。也就是说装饰器可以定义成实现了__call__方法的class也可以是其他的任何返回函数的“对象”,后者实现较复杂,但功能强大。
实现__call__方法的class示例:
class mydecorator: def __init__(self, func): self.func = func def __call__(self): print “mydecorator was called.” return self.func使用方法同前面的示例一样,因为我们可以像函数一样调用该类:mydecorator(),不同的是,该类是以被装饰的函数作为构造参数,而不是作为__call__的参数。更复杂的装饰器:
def mydecorator(arg): print “arg is ”, arg def newdec(func): print ‘new decorator was called.’ def replacedec(self): print ‘replace decorator was called.self.count=’,self.count return func(self) return replacedec return newdec
使用示例:
class A2: def __init__(self): self.count = -1 @mydecorator('yes') def method(self): print self.count if __name__ == '__main__': a = A2() a.method()输出结果:
Args is yes new decorator was called. decorator was called. self.count=-1 -1
要注意的是replacedec函数,可以看出该函数有一个参数self,该参数就是method的self参数,即A2对象。可以进一步总结出,装饰器的调用过程,对装饰器嵌套函数的解释:首先将”yes”参数传递装饰器mydecorator,然后将被装饰的函数作为参数传递给newdec嵌套函数,然后将被装饰的函数的参数做为参数传递给下一层嵌套的函数replacedec。如图:

多个参数的示例:
def mydecorator(arg1, arg2): print "level 1 : Arg1=%s, arg2=%s" % (arg1, arg2) def newdec(func): print 'level 2 : newdec was called. the arg is func=%s' % func def replace(self, x, y): print "level 3 : replace was called. self is %s, x=%s, y=%s" % (str(self), str(x), str(y)) return func(self, x, y) return replace return newdec class A2: def __init__(self): pass @mydecorator('Hello', 'word.') def method(self, x, y): print x, y if __name__ == '__main__': a = A2() a.method(1, 2)
运行结果为:level 1 : Arg1=Hello, arg2=word. level 2 : newdec was called. the arg is func=<function method at 0x04A06130> level 3 : replace was called. self is <__main__.A2 instance at 0x04A05850>, x=1, y=2 1 2
-
Python开发Activex组件
by{ guangboo }, published {2009-11-08}, Tag { Python / }
Python强的功能就在于它无所不能。
使用win32com模块开发window ActiveX的示例:(如果你还没有装win32com模块的话,请到http://python.net/crew/skippy/win32/Downloads.html下载)。
# SimpleCOMServer.py class PythonUtilities: _public_methods_ = ['SplitString'] _reg_progid_ = "Python.Utilities" _reg_clsid_ = "{A6688635-62F5-41cb-AF54-CBA84C2F0F86}" def SplitString(self, val): return "Hello world ", val if __name__ == '__main__': print "Registering COM server..." import win32com.server.register win32com.server.register.UseCommandLine(PythonUtilities)在console下运行:python SimpleCOMServer.py
在HTML页面中调用该Activex组件:
window.onload = function(){ var obj = new ActiveXObject("Python.Utilities"); alert(obj.SplitString("Hel")); } -
Python Configparser模块
by{ guangboo }, published {2009-10-18}, Tag { Python / }
ConfigParser – 配置文件解析
该模块定义了ConfigParser类,该类实现了基本的配置文件的解析,配置文件提供了类似与WINDOWS下的INI文件结构。你可以使用该类编写最终用户很容易自定义的Python程序。
警告:该库不包括值类型前缀,该前缀应用与INI语法的扩展版本—Windows Registry中。
例如: [My Section]
foodir: %(dir)s/whatever
dir=frob将会解析“%(dir)s”为“dir”的值(这里是“frob”)。该模块包含所以需要的扩展。
默认值可以通过他们作为字典传递给ConfigParser构造器来知道,另外还会默认传递给get()方法,它会覆盖其他所以方法。
Class RawConfigParser([defaults])
基本配置类,当传递defaults时,会初始化到内置字典中。该类不支持智能修复,2.3版本新特征。Class ConfigParser([defaults])
继承之RawConfigParser类,实现了智能特性,为get(),items()方法添加了可选参数。Defaults中的值必须能填补“%()s”。注意__name__是内置的default;该值是section的名称,它会被defaults提供的任何值所覆盖。所以的用于填补的option名称都会通过optionxform()方法传递,就像其他任何option名称一样。例如,使用optionxform()的默认实现(将option名称转化成小写),“foo %(bar)s”和“foo %(BAR)s”的值相等。
Class SafeConfigParser([defaults])
继承至ConfigParser,实现了更多智能特征,实现更有可预见性,新的应用更偏好这个版本,如果他们不需要对python老版本的兼容性,2.3版本。Exception NoSectionError
当没有发现给定section时抛出。Exception DuplicateSectionError
如果add_section()方法被调用时,提供的section参数的值已经存在时抛出。Exception NoOptionError
指定option不存在时抛出。Exception InterpolationError
执行字符串填补时抛出的异常的基类。Exception InterpolationDepthError
当填补字符串因为迭代次数超过了MAX_INTERPOLATION_DEPTH值时抛出的异常,InterpolationError的子类。Exception InterpolationMissingOptionError
当option引用的值不存在时抛出,该异常为InterpolationError的子类,2.3版本新加。Exeption InterpolationSyntaxError
当原文件格式没有遵守规定的语法时抛出的异常,继承至InterpolationError,2.3版本。Exception MissingSectionHeaderError
尝试解析没有section头的文件时抛出。Exception ParsingError
解析文件时发生错误。MAX_INTERPOLATION_DEPTH
get()方法当raw参数为false时,递归的对大深度。只适用与ConfigParser类。RawConfigParser对象
RawConfigParser实例的方法:
defaults()
返回全部示例中所以defaults。sections()
返回有效的section列表,DEFAULT不包含在列表中。add_section(section)
为实例添加一个section,如果给定的section名已经存在,将抛出DuplicateSectionError异常。has_section(section)
判断给定的section名在配置文件中是否存在,DEFAULT section不包括。options(section)
返回给定的section下的所有可用option的列表。has_option(section, option)
如果section存在,并包含给定的option,返回true,放在返回false, 1.6版本新增。read(filenames)
尝试解析文件列表,如果解析成功返回文件列表。如果filenames是string或Unicode string,将会按单个文件来解析。如果在filenames中的文件不能打开,该文件将被忽略。这样设计的目的是,让你能指定本地有可能是配置文件的列表(例如,当前文件夹,用户的根目录,及一些全系统目录),所以在列表中存在的配置文件都会被读取。如果文件都不存在,那么ConfigParser实例会包含空数据集。一个需要从配置文件读取初始化数据的应用程序,应该使用readfp()方法来加载所需要的文件,之后可以使用read()方法来获取任何可能的文件:import ConfigParser, os config = ConfigParser.ConfigParser() config.readfp(open('defaults.cfg')) config.read(['site.cfg', os.path.expanduser('~/.myapp.cfg')])2.4版本之后,返回成功解析的文件列表。
readfp(fp[, filename])
从文件或fp(值使用该对象的readline()方法)中的似文件类读取并解析配置数据,如果filename被省略,fp有一个name属性,该属性用于获取filename;默认是“<???>”。get(section, option)
获取section下option的值。getint(section, option)
强制指定section下的option的值,作为Int类型返回的方便方法。getfloat(section, option)
强制section下的option值,作为float类型返回的方法方法。getboolean(section, option)
强制section下的option值,作为布尔类型返回的方法方法。注意可接受的option的true值有“1”,“yes”,“true”及“on”,可接受的false值有“0”,“no”,“false”,“off”。字符串值不检测大小写,其他值会抛出ValueError异常。itmes(section)
返回给定section下的所以option的(name, value)对列表。set(section, option, value)
如果给定的setion存在,为option设定给定的值;否则抛出NoSectionError异常。当可能使用RawConfigParser(或者ConfigParser的参数raw为true)来在内部存储非字符串值,所以功能(包括填补和输出到文件)只能使用字符串值来归档。1.6版本新增。write(fileobject)
将配置表示写入指定文件类,该表示以后可以通过调用read()来解析,1.6版本新增。remove_option(section, option)
从指定section中删除指定option,如果section不存在,抛出NoSectionError异常;如果option存在,则删除,并返回True;否则返回false。1.6版本新增。remove_section(section)
从配置文件中删除指定的section,如果section确实存在,返回true,否则返回false。optionxform(option)
将输入文件中,或客户端代码传递的option名转化成内部结构使用的形式。默认实现返回option的小写形式;子类可能重写该方法或客户端代码可能将该方法作为实例的属性,以此来影响它的行为。将此用于str(),例如,会使option名称大小写敏感。ConfigParser对象
ConfigParser类扩展了RawConfigParser的一些接口方法,添加了一些可选参数。
get(section, option [, raw[, vars]])
获取给定section下的option的值,所以“%”占位符在返回值中被填补,基于构造时传递的默认值,就像option,vars也被提供,除非raw参数为true。items(section, [, raw[, vars]])
返回给定section下的所以option的(name, value)对列表。可选参数同get方法,2.3版本新增。SafeConfigParser对象
SafeConfigParser类实现了ConfigParser相同的接口,新增如下方法:
set(section, option, value)
如果给定的section存在,给option赋值;否则抛出NoSectionError异常。Value值必须是字符串(str或unicode);如果不是,抛出TypeError异常,2.4版本新增。 -
Twisted指南
by{ guangboo }, published {2009-09-17}, Tag { Python / Twisted / }
2 指南
2.1 服务端
2.1.1 概述
Twisted框架旨在构建灵活、强大的服务端,灵活性在于书写服务端程序的方式的层次性。
本文档介绍Protocl层,该层你将进行协议的解析和处理。如果你正在实现或应用该层,那么首先要阅读如何使用Twisted编程、编写Twisted插件(143页)章节,然后在阅读该章节。该章节仅仅涉及到TCP、SSL及Unix socket服务端,后面会有单独的一章来介绍UDP(92页)。
协议的处理类通常是twisted.internet.protocol.Protocol的子类。大多数协议处理都继承自该类或其更高级的子类。协议类对根据需要为每一个连接都示例化一个对象,然后在连接完成后销毁。即Protocol没有保存持久化的配置[J1] 。
持久化配置保存在工厂类中,它通常继承至twisted.internet.protocol.Factory。默认的工厂类会在每个Protocol对象中实例化,并赋值给指向自己的factory属性。[J2] 并允许每个Protocol访问、甚至修改持久化配置。
这为在多个不同的端口或地址能提供相同的服务很有用。这就是为什么Factory不监听连接的原因,事实上它根本对网络一无所知。了解更多请查看twisted.internet.interfaces.IReactorTCP.listenTCP,及其他IReactor*.listen* API。
该章节会解释每一步。
2.1.2 Protocols
如上所述,本节主要介绍辅助类和函数。Twisted协议使用异步方式处理数据,意思是说协议从不等待事件,而是一旦从网络接收到数据就会响应事件。
简单示例如下:
from twisted.internet.protocol import Protocol
class Echo(Protocol):
def dataReceived(self, data):
self.transport.write(data)
这是最简单的协议,它打印出所介绍到的数据,没有响应任何事件。下面是一个Protocol响应其他事件的例子:
from twisted.internet.protocol import Protocol
class QOTD(Protocol):
def connectionMade(self):
self.transport.write("An apple a day keeps the doctor away\r\n")
self.transport.loseConnection()
该协议一断简单的文字响应了初始连接,然后终止连接。
ConnectionMade事件通常是设置连接对象的建立时期,就像一些初始问候语(就像上面的QOTD协议,它基于RFC865规范)。connectionLost事件发生于特定连接销毁之后。如示例:
from twisted.internet.protocol import Protocol
class Echo(Protocol):
def connectionMade(self):
self.factory.numProtocols = self.factory.numProtocols+1
if self.factory.numProtocols > 100:
self.transport.write("Too many connections, try later")
self.transport.loseConnection()
def connectionLost(self, reason):
self.factory.numProtocols = self.factory.numProtocols-1
def dataReceived(self, data):
self.transport.write(data)
这里的connectionMade和connectionLost协助完成统计工厂内活动协议的个数。当出太多活动协议时connectionMade立即关闭连接。
使用Protocol
本节,介绍测试协议的简单方法。(为观察如何编写产品级的Twisted服务端,尽管,你还需要阅读编写Twisted插件章节(143页))。
下面是前面讨论的QOTD服务端的运行。
from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor
class QOTD(Protocol):
def connectionMade(self):
self.transport.write("An apple a day keeps the doctor away\r\n")
self.transport.loseConnection()
# Next lines are magic:
factory = Factory()
factory.protocol = QOTD
# 8007 is the port you want to run under. Choose something >1024
reactor.listenTCP(8007, factory)
reactor.run()
不用在意最好6行代码—-不久就会在本章节学到。
Protocol帮助
很多协议都构建在类似低级抽象之上,最流行的网络协议以行为基础,而行通常以CR-LF组合来结束。
然而,不少协议是混合型的——基于行的部分和原始数据部分。示例中包括HTTP/1.1和自由网络协议。
针对这些情况,出现了一个LineReceiver协议。该协议使用两个不同的事件处理——lineReceived和rawDataReceived。默认的,只有lineReceived会对每一行各调用一次。然而,如何setRawMode被调用的话,协议将会调用rawDataReceived,直到setLineMode被调用,它才返回使用lineReceived。
使用行接收器的简单应用:
from twisted.protocols.basic import LineReceiver
class Answer(LineReceiver):
answers = {’How are you?’: ’Fine’, None : "I don’t know what you mean"}
def lineReceived(self, line):
if self.answers.has_key(line):
self.sendLine(self.answers[line])
else:
self.sendLine(self.answers[None])
注意分隔线不是行的一部分。
还有一些其他不太流行的辅助类,如基于netstring协议和prefixed-message-length 协议。
状态机
很多Twisted的协议处理都需要写状态机来记录它们的状态。这里有一下对写状态机有帮助的建议:
l 不要写太大的状态机。宁可一次写一个处理一级抽象的状态机。
l 使用Python的动态性,创建开放式的状态机。参见SMTP客户端代码。
l 不要使用Protocol处理代码与特定的应用程序代码混淆。当协议不得不调用特定应用程序的代码的时,也一定要确保作为一个方法来调用。
2.1.3 工厂
如上所述,一般使用twisted.internet.protocol.Factory类,没有必要使用其子类。然而,有时会有协议的特定工厂配置或其他考虑。这时,有必要派生Factory的子类。
对于简单的特定协议的工厂类实例化,简单的实例化Factory,设置给protocol属性。
from twisted.internet.protocol import Factory
from twisted.protocols.wire import Echo
myFactory = Factory()
myFactory.protocol = Echo
如果有必要为特定的配置构造简单的工厂,那么工厂函数通常很有用:
from twisted.internet.protocol import Factory, Protocol
class QOTD(Protocol):
def connectionMade(self):
self.transport.write(self.factory. quote+’\r\n’)
self.transport.loseConnection()
def makeQOTDFactory(quote=None):
factory = Factory()
factory.protocol = QOTD
factory.quote = quote or ’An apple a day keeps the doctor away’
return factory
Factory有两个方法来执行特定应用程序的构建和销毁(当Factory经常被持久化时,往往不适合在__init__和__del__方法中做这些工作,这往往太早或太迟)。
允许工厂的Protocol记录指定日志文件的示例:
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
class LoggingProtocol(LineReceiver):
def lineReceived(self, line):
self.factory.fp.write(line+’\n’)
class LogfileFactory(Factory):
protocol = LoggingProtocol
def __init__(self, fileName):
self.file = fileName
def startFactory(self):
self.fp = open(self.file, ’a’)
def stopFactory(self):
self.fp.close()
放在一起
现在,你知道什么是工厂了,并想要使用配置引用服务端来运行QOTD,不是吗?没问题,代码如下:
from twisted.internet.protocol import Factory, Protocol
from twisted.internet import reactor
class QOTD(Protocol):
def connectionMade(self):
self.transport.write(self.factory.quote+’\r\n’)
self.transport.loseConnection()
class QOTDFactory(Factory):
protocol = QOTD
def __init__(self, quote=None):
self.quote = quote or ’An apple a day keeps the doctor away’
reactor.listenTCP(8007, QOTDFactory("configurable quote"))
reactor.run()
可能不理解的就是最好两行代码。
listenTCP是连接Factory到网络的方法。它使用了反应器的接口,可以让很多不同的循环处理网络代码,无需修改最终用户代码。如上所述,如果你想编写产品级的Twisted服务端代码,而不仅仅是20行的小程序,你会希望使用Application对象(160页)。