-
Twisted From Scratch,Or The Evolution Of Finger
by{ guangboo }, published {2009-12-07}, Tag { Twisted / }
2.5 Twisted from Scratch, or The Evolution of Finger
2.5.1 介绍
当人们使用Twisted时,一般都会觉得Twisted强大的不可思议。甚至不知道从何说起。
本指南从会从基础开始,大量运用框架中的重要特征,慢慢构建功能全面的Twisted应用。这里会提供大量的代码,所以不用担心。
我们正关注的应用是“finger”服务,它由UNIX服务器提供支持,沿用了传统的熟悉的服务方式。我们将在标准服务的基础上,进行略微的扩展,以显示Twisted的高级特性。
2.5.2 主要内容
本教程分11部分:
- The Evolution of Finger:构建简单的finger服务;
- The Evolution of Finger:添加新特征;
- The Evolution of Finger:清理代码;
- The Evolution of Finger:转移到基于框架的组件;
- The Evolution of Finger:可插件化后台;
- The Evolution of Finger:web前台;
- The Evolution of Finger:使用Perspective Broker支持Twisted客户端;
- The Evolution of Finger:为多协议使用单子工厂;
- The Evolution of Finger:Twisted finger客户端;
- The Evolution of Finger:创建finger库;
- The Evolution of Finger:配置、打包finger服务。
-
设计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.
-
Twisted教程-客户端
by{ guangboo }, published {2009-11-11}, Tag { Twisted / }
客户端
2.2.1 概述.
Twisted框架设计的很灵活,可以编写功能强大的客户端。灵活的代价在于编写客户端方法的多一些层次,文档涵盖了使用TCP,SSL和Unix sockets,UDP来创建客户端,它们分别都有介绍(92页)。
基本上,实际实现协议的解析和处理的是在Protocol类中。该类通常继承至twisted.internet.protocol.Protocol,大多数协议处理程序要么继承至该类,要么是其子类。当你连接到服务器时协议类就会实例化一个实体,在你离开的时候,被释放。就是说持久的配置不会驻留在Protocol中。
持久配置被保存在Factory类中,它通常继承至twisted.internet.protocol.ClientFactory。默认的工厂类只实例化Protocol,并赋值给它的factory属性,这就允许Protocol访问,有可能修改持久化配置。
2.2.2 协议
如上所述,协议、辅助类和函数是大部分代码。Twisted协议以异步形式处理数据,就是说协议从不等待事件,而是在收到事件时回复。 示例:
from twisted.internet.protocol import Protocol from sys import stdout class Echo(Protocol): def dataReceived(self, data): stdout.write(data)
这是一个最简单的协议,只是简单的将接收到的数据写入标准输出中去,还有很多的事件它没有响应。下面时候Protocol响应其他事情的示例:
from twisted.internet.protocol import Protocol class WelcomeMessage(Protocol): def connectionMade(self): self.transport.write("Hello server, I am the client!\r\n") self.transport.loseConnection()该协议连接到服务器,发送一条欢迎消息,然后终止连接。
connectionMade事件通常发生在Protocol对象设置的地方,同时初始化问候语(就像上面的WelcomeMessage协议)。特定的Protocol对象会在connectionLost事件中被清除。
2.2.3 简易单用户客户端
很多情况下,协议只需要连接服务器一次,程序只想获取一个协议连接的实例。这些情况下,twisted.internet.protocol.ClientCreator提供了必要的API。
from twisted.internet import reactor from twisted.internet.protocol import Protocol, ClientCreator class Greeter(Protocol): def sendMessage(self, msg): self.transport.write("MESSAGE %s\n" % msg) def gotProtocol(p): p.sendMessage("Hello") reactor.callLater(1, p.sendMessage, "This is sent in a second") reactor.callLater(2, p.transport.loseConnection) c = ClientCreator(reactor, Greeter) c.connectTCP("localhost", 1234).addCallback(gotProtocol)2.2.4 ClientFactory
我们使用reactor.connect*和ClientFactory,该ClientFactory负责创建Protocol,并接收与连接状态相关的事件,它可以做一些工作,如在连接错误事件中进行重新连接。下面是一个简单ClientFactory的示例,它使用Echo协议(见上述)并打印出连接状态。
from twisted.internet.protocol import Protocol, ClientFactory from sys import stdout class Echo(Protocol): def dataReceived(self, data): stdout.write(data) class EchoClientFactory(ClientFactory): def startedConnecting(self, connector): print 'Started to connect.' def buildProtocol(self, addr): print 'Connected.' return Echo() def clientConnectionLost(self, connector, reason): print 'Lost connection, Reason:', reason def clientConnectionFailed(self, connector, reason): print 'Connection failed. Reason:', reason为使EchoClientFactory连接服务器,添加如下代码:
from twisted.internet import reactor reactor.connectTCP(host, port, EchoClientFactory()) reactor.run()
注意:clientConnectionFailed在不能建立连接时被调用,clientConnectionLost在创建连接后被释放时调用。
重新连接
很多时候,由于网络错误,客户端连接就会丢失意义。有一种方法,在连接中断时,disconnection会调用connector.connect()来重新进行连接:
from twisted.internet.protocol import ClientFactory class EchoClientFactory(ClientFactory): def clientConnectionLost(self, connector, reason): connector.connect()
作为第一个参数传递的connector是connection和protocol的接口,当连接失败,factory收到clientConnectionLost事件时,factory可以调用connector.connect()来从头开始建立连接。
然而,大部分需要这个功能的程序应该是实现ReconnectingClientFactory,它在连接中断或失败时尝试重新连接,不断的延时并不断尝试重新连接。
下面是实现了ReconnectingClientFactory的Echo协议:
from twisted.internet.protocol import Protocol, ReconnectingClientFactory from sys import stdout class Echo(Protocol): def dataReceived(self, data): stdout.write(data) class EchoClientFactory(ReconnectingClientFactory): def startedConnecting(self, connector): print 'Started to connect.' def buildProtocol(self, addr): print 'Connected.' print 'Resetting reconnection delay' self.resetDelay() return Echo() def clientConnectionLost(self, connector, reason): print 'Lost connection. Reason:', reason ReconnectingClientFactory.clientConnectionLost(self, connector, reason) def clientConnectionFailed(self, connector, reason): print 'Connection failed. Reason:', reason ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
2.2.5 高级示例: ircLogBot
ircLogBot简介
到现在的客户端还相当简单,更复杂的示例在Twisted文档的doc/examples目录下面。
from twisted.words.protocols import irc from twisted.internet import reactor, protocol from twisted.python import log import time, sys class MessageLogger: """ An independent logger class (because separation of application and protocol logic is a good thing). """ def __init__(self, file): self.file = file def log(self, message): """Write a message to the file.""" timestamp = time.strftime("[%H:%M:%S]", time.localtime(time.time())) self.file.write('%s %s\n' % (timestamp, message)) self.file.flush() def close(self): self.file.close(); class LogBot(irc.IRCClient): """A logging IRC bot.""" nickname = "twistedbot" def connectionMade(self): irc.IRCClient.connectionMade(self) self.logger = MessageLogger(open(self.factory.filename, "a")) self.logger.log("[connected at %s]" %time.asctime(time.localtime(time.time()))) def connectionLost(self, reason): irc.IRCClient.connectionLost(self, reason) self.logger.log("[disconnected at %s]" % time.asctime(time.localtime(time.time()))) self.logger.close() #callbacks for events def signedOn(self): """Called when bot has succesfully signed on to server.""" self.join(self.factory.channel) def joined(self, channel): """This will get called when the bot joins the channel.""" self.logger.log("[I have joined %s]" % channel) def privmsg(self, user, channel, msg): """This will get called when the bot receives a message.""" user = user.split('!', 1)[0] self.logger.log("<%s> %s" % (user, msg)) #Check to see if they're sending me a private message if channel == self.nickname: msg = "It isn't nice to whisper! Play nice with the group." self.msg(user, msg) return #Otherwise check to see if it is a message directed at me if msg.startswith(self.nickname + ":"): msg = "%s: I am a log bot" % user self.msg(channel, msg) self.logger.log("<%s> %s" % (self.nickname, msg)) def action(self, user, channel, msg): """This will get called when the bot sees someone do an action.""" user = user.split('!', 1)[0] self.logger.log("* %s %s" % (user, msg)) #irc callbacks def irc_NICK(self, prefix, params): """Called when an IRC user changes their nickname.""" old_nick = prefix.split('!')[0] new_nick = params[0] self.logger.log("%s is now known as %s" % (old_nick, new_nick)) class LogBotFactory(protocol.ClientFactory): """A factory for LogBots A new protocol instance will be created each time we connect to the server. """ #the class of the protocol to build when new connection is mede protocol = LogBot def __init__(self, channel, filename): self.channel = channel self.filename = filename def clientConnectionLost(self, connector, reason): """If we get disconnected, reconnect to server.""" connector.connect() def clientConnectionFailed(self, connector, reason): print "connection failed:", reason reactor.stop() if __name__ == '__main__': #initialize logging log.startLogging(sys.stdout) #create factory protocol and application f = LogBotFactory(sys.argv[1], sys.argv[2]) #connect factory to this host and port reactor.connectTCP("irc.freenode.net", 6667, f) #run bot reactor.run()ircLogBot.py连接IRC服务器,调入一个频道并将所以流量记录到文件中,它表明了连接中断时重新连接的连接层逻辑,以及Factory中的持久数据。
Factory中的持久数据
由于Protocol实例在每次连接时都会重新创建,客户端需要一些方法来跟踪应该被持久化的数据。在这个logging bot案例中,它需要知道哪个频道被记录及记录在哪里。
from twisted.internet import protocol from twisted.protocols import irc class LogBot(irc.IRCClient): def connectionMade(self): irc.IRCClient.connectionMade(self) self.logger = MessageLogger(open(self.factory.filename, "a")) self.logger.log("[connected at %s]" %time.asctime(time.localtime(time.time()))) def signedOn(self): self.join(self.factory.channel) class LogBotFactory(protocol.ClientFactory): protocol = LogBot def __init__(self, channel, filename): self.channel = channel self.filename = filename当protocol被创建,它就会得到factory的引用赋值给self.factory,它可以通过自身的逻辑对factory的属性进行访问。在LogBot案例中,它打开文件,并连接factory中的通道存储。
2.2.6 延伸
本书中所使用的Protocol类是实现了IProtocol接口的基本类,该接口在大多少Twisted应用都广泛应用。要了解完整的IProtocol接口,请参阅API文档。
本书中在一些示例中使用的transport属性提供了ITCPTransport接口,要了解有关该接口的详细情况,参阅ITCPTransport的API文档。
接口是用于指定一个类所拥有的或如何使用的方法和属性的。参阅Components:Interfaces和Adapters(文档148页)。
-
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页)。
-
Twisted简介
by{ guangboo }, published {2009-09-14}, Tag { Python / Twisted / }
1 介绍
1.1 Twisted的愿景
其他许多文档都是致力于定义Twisted。如果现在你能猜出我写此书的意图,你就知道我不仅试着解释Twisted是什么,而且说明它应该是什么。
首先,Twisted很有趣。它起源于一款游戏,并在游戏中用于商业活动中,它将会,并且我个人也希望它能给用户带来entertaining的交互的用户体验。
Twisted对网络应用开发者来说是一个平台。Python语言本身就很强大,但它缺少很多其他语言都在极力添加的功能。现在好了,Twisted是一款很好的(稍微特别的)纯python框架或库,这取决于你如何看待它,并且它不断的在完善。
作为一种平台,Twisted应专注于集成。理想情况下,所有功能都能通过任何协议访问;至少,这些功能应该通过至少一种协议,使用适当统一的用户接口来进行配置。Twisted开发的下一个阶段会致力于配置系统上,该系统将统一当前很多完全不相干的基础框架,并且非程序员也能让它们附加在一起工作。
1.2 Twisted的高级特性概述

1.3 Twisted异步编程
该文档介绍异步变成模型,Twisted延迟抽象,该延迟抽象就是一个“承诺”的结果,而且会传递一个最终发生的结果给处理函数。
对于熟悉Python语言,并且至少对网络核心概念如服务端,客户端,socket有认识的人来说,关于Twisted的文档中该文档还是比较新的。该文档将概述并发程序设计(interleaving多个任务)和Twisted的并发模型:非模块化代码或异步代码。
讨论完并发模型后,会介绍函数返回Defferred对象时候处理结果的方法。[J1]
1.3.1 并发程序设计介绍
很多计算型的任务会花费很长时间完成,两个原因可能使任务花费这些时间:
1. 密集计算(如大数据的因数分解),需要相当多CPU时间来计算结果;
2. 非密集计算,但不得不等待有效的数据来产生结果。
等待回复
网络编程的一个重要特点就是等待数据。设想,你有一个函数,用于发送包含一些概要性信息的邮件。该函数需要连接到远程服务器,等待远程服务器的响应,然后检测远程服务器能否处理邮件,然后再等待回复,之后发送邮件,再等待确认,最后断开连接。
任何一个环节都有可能花费很长的时间,你的程序也许使用的是所有可能模型中最简单的,但在这些模型中它确实是坐等数据被发生、接收[J2] 。但这种情况下,有明显的、根本的局限性:它不能同时发送多封邮件;事实上它除了发送一封邮件外,其他任何事情都不能做。
从此刻起,除了最简单的网络编程外都要避免这种模型。你可以使用多个不同模型中的一种,让你的程序在某些任务继续之前处于等待状态时,能继续处理手头上的其他任何任务。
拒绝等待数据
有很多编写网络程序的方法,例如:
1. 每个连接在不同的操作系统进程中处理,这种情况下,操作系统就要注意一个进程在等待,而其他都在运行的情况;
2. 每个连接在不同的线程中处理①,这种情况,线程框架就要考虑一个线程在等待,而其他都在运行的情况;
3. 使用非阻塞系统调用,在同一个线程中处理所有连接。
非模块化调用
使用Twisted框架时,标准的模型是第三种模型:非阻塞调用。
当在一个线程中处理多个连接时,调度是应用程序的责任,而不是操作系统的,当每个连接准备读或写时,它通常通过调用一个已登记的函数来实现——通常被称之为异步,事件驱动,或基于回调机制的编程方式。
该模型中,之前发生邮件的功能就像:
1. 调用一个连接函数,连接远程服务器;
2. 连接函数立即返回,同时当连接被建立时,邮件发送程序就会被调用;
3. 一旦连接建立,连接装置就会通知邮件发送函数连接已经建立。
以上的序列与之前的阻塞序列有什么优点?优点就是邮件发送函数在连接打开之前不能做下面一部分工作的时候,其他程序可以做其他任务, 就像其他邮件连接首次开始序列[J3] 。从此,整个程序就不再等待连接了。
回发
经典异步模型在通知应用程序数据有效时,是通过回调通知的。应用程序调用函数请求数据,请求还传递了一个回调函数,该函数会在数据有效时被调用,并将数据作为一个参数传递给回调函数。回调函数因此执行应用程序任何需要该数据的任务[J4] 。
同步编程方式是:函数请求数据,等待数据,处理数据。异步编程则是:请求数据,当数据有效时,调用回调函数。
1.3.2 延迟对象Deferreds
Twisted使用Deferred对象来管理回调序列。客户端应用程序将一系列的函数附加到Deferred对象中,以便当异步请求的结果有效时候被调用(该系列函数被称之为回调序列或回调链);如果在异步请求中出现错误也将被调用(此时被称之为错误回调或错误回调链)。当结果有效时,异步代码第一次调用回调函数,或者发生错误时,第一次调用错误回调函数,Defferred对象会将每个回调函数或错误回调函数的结果传递给链上的下一个函数。
1.3.3 Deferreds 解决方案
第二种并发问题——非密集计算任务,它涉及明显的延迟——Deferreds旨在帮助解决该问题。硬盘访问、数据库访问和网络访问的延迟功能都要使用该类,尽管延迟时间各不相同。
Deferred旨在使Twisted程序等待数据,而不要在数据到达之前处于挂起状态。它们通过为回调给程序和应用程序的函数提供简单管理接口来实现。程序始终知道通过调用Deferred.callback来使得结果有效或发生错误时调用Deferred.errback。应用程序根据他们希望被调用的次序,来附加回调函数和错误回调函数来设置结果处理。
Deferred的基本思想及解决问题的方案,就是尽量保持CPU处于活动状态。如果一个任务正在等待数据,宁可CPU(和程序)空闲着也还在等待数据(称之为“阻塞”的过程)的话,这时程序就可以执行其他操作,当等到一些数据有效的信号后,再返回到原进程。
Twisted中,函数发送信号给调用函数,说明自己正在等待返回Deferred对象。当数据有效时,程序触发Deferred上的回调函数来处理数据。[J5]
1.3.4 Deferreds – 数据到达的信号
在前面发生邮件的示例中,主函数调用一个函数连接远程服务器。异步不要求连接函数立即返回结果,好让主函数能做其他的工作。那么主函数或其控制程序是如何知道连接还不存在的呢?连接一旦存在又是如何使用的呢?
Twisted针对该情况有一个信号对象。当连接函数返回时,返回一个twisted.internet.defer.Deferred对象作为操作没有完成的信号。
Deferred有两个目的。一是表示“我是一个信号,表示你让我做的事还没有结果”。二是当数据有效时,你可以要求Deferred做一些工作。
回调函数
一旦数据有效,你要告诉Deferred如何处理数据的方法是添加回调函数——数据有效时就让Deferred调用该函数。
一个返回Deferred对象的Twisted库函数是twisted.web.client.getPage。下面的示例中,我们调用getPage函数,它返回一个Deferred对象,我们将附加一个回调函数,在数据有效时候用来处理页面的内容:
from twisted.web.client import getPage from twisted.internet import reactor def printContents(contents): ''' This is the ’callback’ function, added to the Deferred and called by it when the promised data is available ''' print "The Deferred has called printContents with the following contents:" print contents # Stop the Twisted event handling system -- this is usually handled # in higher level ways reactor.stop() # call getPage, which returns immediately with a Deferred, promising to # pass the page contents onto our callbacks when the contents are available deferred = getPage(’http://twistedmatrix.com/’) # add a callback to the deferred -- request that it run printContents when # the page content has been downloaded deferred.addCallback(printContents) # Begin the Twisted event handling system to manage the process -- again this # isn’t the usual way to do this reactor.run()
Deferred对象最普遍的用法就是附加两个回调函数,第一个回调函数的处理结果将作为参数传递给第二个回调函数:
from twisted.web.client import getPage from twisted.internet import reactor def lowerCaseContents(contents): ''' This is a ’callback’ function, added to the Deferred and called by it when the promised data is available. It converts all the data to lower case ''' return contents.lower() def printContents(contents): ''' This a ’callback’ function, added to the Deferred after lowerCaseContents and called by it with the results of lowerCaseContents ''' print contents reactor.stop() deferred = getPage(’http://twistedmatrix.com/’) # add two callbacks to the deferred -- request that it run lowerCaseContents # when the page content has been downloaded, and then run printContents with # the result of lowerCaseContents deferred.addCallback(lowerCaseContents) deferred.addCallback(printContents) reactor.run()
错误处理:错误回调函数
正如在得出结果之前异步函数就返回了,也有可能是出现了一些错误而返回的,如连接失败、错误的数据、协议错误等等。正如你添加回调函数到Deferred,由它在预期的数据有效时调用,你同样可以添加错误处理函数(错误回调函数)到Deferred,由它在发生错误或得不到数据时调用:
from twisted.web.client import getPage from twisted.internet import reactor def errorHandler(error): ’’’ This is an ’errback’ function, added to the Deferred which will call it in the event of an error ’’’ # this isn’t a very effective handling of the error, we just print it out: print "An error has occurred: <%%s%>" % str(error) # and then we stop the entire process: reactor.stop() def printContents(contents): ’’’ This a ’callback’ function, added to the Deferred and called by it with the page content ’’’ print contents reactor.stop() # We request a page which doesn’t exist in order to demonstrate the # error chain deferred = getPage(’http://twistedmatrix.com/does-not-exist’) # add the callback to the Deferred to handle the page content deferred.addCallback(printContents) # add the errback to the Deferred to handle any errors deferred.addErrback(errorHandler) reactor.run()
1.3.5 结论
这份文档中,你会:
1. 发现为什么无所不能的网络程序需要拥有并发机制;
2. 学到Twisted框架支持在异步调用机制中的并发;
3. 学到Twisted框架拥有管理回调链的Deferred对象;
4. 发现getPage函数如何返回Deferred对象的;
5. 附加回调函数和错误回调函数到Deferred;及
6. 发现Deferred的回调链及错误回调链的使用。
参考
由于Deferred抽象在Twisted编程中是如此核心的部分,这里有其他几个关于它的详细指南:
1. Using Deferreds (page 102), 更完整的Deferred应用指南,包括Deferred链。
2. Generating Deferreds (page 111), 关于创建Deferred、使用回调链的指南。
Twisted网络是一个可兼容的Python事件循环的集合。它控制代码将事件发送给感兴趣的观察者和可移动的API,使得观察者不必理会哪个事件循环在运行。因此,不同的循环使用相同的代码是完全可能的。从基本的Twisted,甚至移动设备中选择基本的循环用到各种GUI工具的循环中,如GTK+或Tk。
Twisted网络包含各种反应器API的接口,它们的用法在文档的低级Twisted章节。这些API分别是IReactorCore,IReactorTCP,IReactorSSL,IReactorUNIX,IReactorUDP,IReactorTime,IReactorProcess,IReactorMulticast和IReactorThreads。反应器API运行做出非持久性的调用。
Twisted还涵盖了各种传输接口,包括ITransport及其先关。这些接口使得编写Twsited网络代码免于考虑底层传输的实现。
IProtocolFactory接口制定了工厂的规范,它通常是第三方库中很大一部分内容。
[J1]After discussing the concurrency model of which Deferreds are a part, it will introduce the methods of handlingresults when a function returns a Deferred object.
[J2]Your program might use the simplest of all possible models,in which it actually sits and waits for data to be sent and received
[J3]like begin the opening sequence for other email connections.
[J4]The callback function should therefore perform whatever tasks it was that the application needed that data for.
[J5]the program activates the callbacks on that Deferred to process the data.