zl程序教程

您现在的位置是:首页 >  其他

当前栏目

twisted系列教程十四— pre-fireed deferred

教程 系列 十四 Pre Twisted deferred
2023-09-14 09:13:27 时间

Introduction

在这一部分我们将要学习deferred 类的另外的一个方面.为了促进讨论,我们要为我们的poetry service增加一个server.假设我们有大量的内部的client 想要连接一个相同的外部的server.假设这个server已经很慢而且已经负载很高了.我们不想再让server上连接更多的client 了.

所以我们会创建一个缓存代理服务器.当一个client 连接到proxy的时候,这个proxy或者从外部的server获取到一首诗或者就返回一个之前已经缓存了的内容.我们可以让我们的client 都连接proxy,我们的外部的server 的负载就会很小.我们可以用图片三十来描述这个过程:


图片三十

思考一下当一个client连接到proxy 之后会发生什么,假如这个proxy 的缓存是不存在的,这个proxy必须异步等待外部的server 返回一个结果然后才能返回client.到目前为止还不错,我们已经知道怎样去处理返回deferred 的异步的函数.另一方面,假如在缓存中已经有了一个一首诗,这个proxy 会把它立即返回,一点也不用等待.所以proxy获取一首诗的内容可以是同步的或者是异步的.

所以 我们能做些什么假如我们有一个有时异步有时同步的函数?twisted 提供了很多选项,并且它们依据deferred 的一个我们没有讲的特色:你可以在你返回deferred之前触发它.

这个是管用的,因为尽管你不能触发一个deferred 两次,但是你可以在deferred 触发之后向deferred 中增加callbacks 和 errbacks.当你这样做的时候,deferred 会继续的触发 callback/errback 链 从上次它离开的地方.一个很重要的一点是一个已经触发的deferred 可以立即触发新假如的callback.

图片三十一展示了一个已经被触发的deferred:


图片三十一

如果我们现在向其中加入一对callback/errback,这个deferred 会立即的触发新加入的callback,就像图片三十二:


图片三十二

我们测试这个deferred 的新特色通过代码twisted-deferred/defer-11.py.试着运行一下并看看deferred 被触发之后又加入callback 之后会发生什么.注意在第一个例子中每个新的callback是怎样被立即触发的.

第二个例子展示了我们怎样pause() 一个deferred不让它立即触发callback的.当我们都准备好的时候,我们可以用unpause().其实暂停deferred 的原理和 当一个callback 返回deferred 导致外部的deferred 暂停的原理是一样的.

Proxy 1.0

现在让我们看一下第一版的poetry proxy –twisted-server-1/poetry-proxy.py,因为这个proxy 同时扮演了client 和 server 的角色,它有两对 Protocol/Factory.一个用来为poetry 服务,另一个用来从外部的server 获取诗歌的内容.我们就不看为client 服务的protocol/factory 了,和以前的版本一样.

让我们来看 ProxyService,proxy中的server-side protocol 利用它来从外部的server获取一首诗:

class ProxyService(object):

    poem = None # the cached poem

    def __init__(self, host, port):
        self.host = host
        self.port = port

    def get_poem(self):
        if self.poem is not None:
            print 'Using cached poem.'
            return self.poem

        print 'Fetching poem from server.'
        factory = PoetryClientFactory()
        factory.deferred.addCallback(self.set_poem)
        from twisted.internet import reactor
        reactor.connectTCP(self.host, self.port, factory)
        return factory.deferred

    def set_poem(self, poem):
        self.poem = poem
        return poem

重要的方法是get_poem.假如在缓存里已经有一首诗存在,直接返回.如果没有的话,我们向外部的server 发起一个连接并返回一个deferred,如果等待的诗来到则触发deferred.get_poem 是一个只有一部分时间是异步的.

怎样来处理那样的一个函数呢?让我们来看一下server-side protocol/factory :

class PoetryProxyProtocol(Protocol):

    def connectionMade(self):
        d = maybeDeferred(self.factory.service.get_poem)
        d.addCallback(self.transport.write)
        d.addBoth(lambda r: self.transport.loseConnection())

class PoetryProxyFactory(ServerFactory):

    protocol = PoetryProxyProtocol

    def __init__(self, service):
        self.service = service

这个factory 是很简单的,它只保存了一个proxy service 的引用,这样可以让protocol 实例可以调用get_poem 方法.protocol 是核心所在.它没有直接的调用get_poem,而是使用了一个twisted.internet.defer 的封装—maybeDeferred.

maybeDeferred 函数拿到一个函数的引用,并加上了一些参数,maybeDeferred 会最终调用这个函数,并且做如下的工作:

    如果这个函数返回了一个deferred,maybeDeferred 也返回这个deferred,或者
    假如这个函数返回了一个Failure,maybeDeferred 返回一个已经被触发的deferred ,并带着failure参数,或者
    假如这个函数返回了一个正常的值,maybeDeferred返回一个已经被触发的deferred,并带着这个正常的值作为参数,或者
    假如这个函数抛出了一个错误,maybeDeferred会返回一个已经被触发的deferred,并带着由这个错误转化来的failure作为参数

 

换句话说,从maybeDeferred 返回的值一定是一个deferred,即使你传递过去的函数不会返回deferred.这就让我们可以安全的调用一个同步的函数,并把它当作一个返回deferred异步的函数.


注意一:这里仍有一点不一样,被一个同步的函数返回的deferred 是已经被触发过的,所以任何的你加入的callback 和errback 都会被立即调用,而不是在一些reactor loop 的迭带之后.
注意二:也许给一个一定会返回deferred 的函数命名为maybeDeferred 不是一个特别好的选择.

一但这个protocol 有了一个真正的deferred,它可以增加一些callback把诗送到client,并关闭相应的连接.这个就是我们的第一个poetry proxy.

Running the Proxy

要测试我们的代理的话,先开启一个poetry server,像下面这样:

python twisted-server-1/fastpoetry.py --port 10001 poetry/fascination.txt

然后开启一个proxy server:

python twisted-server-1/poetry-proxy.py --port 10000 10001

也就说proxy 运行在10000端口,poetry server 运行在10001端口.
下面你可以运行一个client 连接proxy:

python twisted-client-4/get-poetry.py 10000

我们使用了一个早期的没有poetry transformations 的client 版本.你可以看到一首诗出现在client 的窗口里,还有一些文字说明它正在从server 下载.如果你再运行client 一次,这个proxy 会告诉你它正在使用缓存起来的poem.

Proxy 2.0

我们前面已经说过,还有另外一种方法可以实现我们的需求.在Porxy 2.0 中有说明,代码见twisted-server-2/poetry-proxy.py.既然我们可以在返回deferred之前触发它,我们可以让proxy service 在缓存中已经存在这首诗的时候返回一个已经触发过的deferred.下面是proxy service 中get_poem 的新版本:

def get_poem(self):
    if self.poem is not None:
        print 'Using cached poem.'
        # return an already-fired deferred
        return succeed(self.poem)

    print 'Fetching poem from server.'
    factory = PoetryClientFactory()
    factory.deferred.addCallback(self.set_poem)
    from twisted.internet import reactor
    reactor.connectTCP(self.host, self.port, factory)
    return factory.deferred

这个defer.succeed 函数是创建一个已经触发的deferred并返回一个值的很便捷的方法.查看一下它的实现你会发现它就是创建一个deferred ,并用callback()触发 的封装.如果我们想返回一个已经失败了的deferred 我们可以用defer.fail.

在这个版本中,因为get_poem 已经返回了一个deferred,protocol 类不再需要maybeDeferred:

class PoetryProxyProtocol(Protocol):

    def connectionMade(self):
        d = self.factory.service.get_poem()
        d.addCallback(self.transport.write)
        d.addBoth(lambda r: self.transport.loseConnection())

除了这两个地方的变化之外,其他的没什么变化了.你可以像上面的方法一样来运行它.

Summary

在这一部分我们学到了怎样deferred 在被返回之前被调用,因而我们可以在同步的程序使用它,我们有两种方法去实现它:

    我们可以使用maybeDeferred来处理时而返回deferred 时而返回正常结果的函数
    我们可以用defer.succed 和 defer.fail 提前触发我们的deferred,所以我们的有时同步有时异步的函数可以总是返回deferred

 

我们可以使用他们中的任何一个方法.前一个强调了我们的函数不是总是异步的,而后一个让代码更简单.没有一个定论非要使用哪个.

两种方法都可以是因为我们可以向deferred中增加callback/errback 在它被触发之后.它也解了我们在第九部分提出的疑问.我们了解到在deferred中,不管是最后一个callback 或者errback 失败,错误会在deferred 被垃圾回收的时候才被报告出来.现在我们知道因为什么了–因为我们可以一直向一个deferred 对象中增加一个callback/errback 对,直到最后一个对deferred 的引用也消失了,twisted 才能认定这个错误没有被处理.

所以,这就是deferred了吗?我们已经知道deferred 的全部了吗? 对于大部分来说,是的.但是twisted 包含了很多我们还没有探寻到的很多种交替使用deferred 的方式.同时,twisted 的开发人员也在不停的增加新的特色.在将来的发布的版本中,deferred 会有更多的能力.我们会在以后的章节中讲到,但首先我们需要从deferred中休息一下,看一些twisted 的其他的方面.

----
20120821 16:08
运行 Proxy 1.0 是做为一个代理服务器,客户端是连接到这个 Proxy 1.0 上而不是外部的 server 上。如果 Proxy 1.0 中有缓存数据的话,将取到的数据通过 PretryProxyProtocol 中的 d.addCallback(self.transport.write) 发给先前的客户端。否则向外部的 server 发起一个连接用于取数据,再通过同样的方式发送给客户端。