twisted学习笔记

最近学习了Linux的IO模型,看到IO多路复用的时候发现,曾经学习过一遍但没学懂的Twisted框架正是使用了这个模型。于是转头翻出了twisted的教程,重新学习了一遍。在理解了底层IO模型之后再看Twisted,觉得容易理解了不少。

Twisted是用 Python 实现的基于事件驱动的异步的网络引擎框架。它封装了大部分主流的网络协议(传输层或应用层),如 TCP、UDP、SSL/TLS、HTTP、IMAP、SSH、IRC以及FTP等。

这篇文章是我学习Twisted与异步编程入门后的笔记,是为了整理下思路和方便以后查看。但是如果刚准备学习Twisted,这篇文章除了[Reactor设计模式]这里外,其他的并没有什么帮助。

Reactor设计模式

Twisted在底层使用的也是select(默认),poll,epoll。所以先看看Linux的IO模型会有助于理解Twisted的核心思想。


这是IO多路复用模型的Reactor设计模式。用户线程只需要对IO操作进行事件注册,然后就可以处理其他的工作。而Reactor线程通过select检查socket状态,等socket被激活后,再去通知用户线程读取数据。用户线程可以不断地注册多个IO事件,让Reactor去轮询这些socket是否被激活,并通知用户线程用相应的回调函数处理被激活的sockect。
Twisted的核心思想也是基于此,它Reactor和内核交互的所有工作,我们要做的就是确定通信协议和编写回调函数这两部分。

Factory和Protocols

Twisted网络框架中通过Factory和Protocol对事件处理过程进行了抽象。

Protocols

每一个连接都需要一个Protocol实例,它规定了具体的网络协议如FTP、TCP等。在twisted.protocols下可以找到很多常见的通信协议。我们要做的就是重载一个我们需要的协议实现,然后规定建立连接做什么、收到数据做什么、断开连接做什么这些事件处理流程。

Factory

Factory是协议工厂每一个Protocol都是由Factory生成,所以Factory会保存一些全局的数据和处理流程。Factory和Protocol相互绑定,Factory中的protocol属性指向他要生成的Protocol协议类型,而Protocol中的factory属性指向生成它的Factroy。简单点说,Protocol定义的每个阶段做什么中具体做了什么,是从Factory中引用而来的。

刚开始觉得这种定义事件处理过程的方式非常繁琐,一个流程却要分成几个类再相互引用,实在麻烦。但是学完教程回过头看,却觉得这样反而会更清晰。因为在一个网络应用中,同一个通信协议上可能会有不同的处理流程,这样将协议和处理流程分开的方式会方便与代码复用,也降低了耦合度。还有,当协议和处理流程比较复杂时,Factory和Protocol代码的作用清晰,反而更容易理清思路。

Deferred回调

Deferred是异步操作中立即返回的对象,代表着一个将会到达但可能还没有到达的结果,所以可以把接下来的操作传递给Deferred ,等返回结果后,就立即执行操作。听起来有点像JavaScript中的Promise。Deferred是一对回调链,一个是为针对正确结果,另一个针对错误结果。


至于为何要把回调设计成这种形式,我觉得有这么几个原因:

  1. 一个异步应用可能可以拆分为多个异步过程,把每个过程的回调串联起来可以让整个过程一目了然,也方便对单个过程的重构。
  2. 异步网络的异常处理非常重要,用两条回调链可以确保任何回调结果都得到处理。

Deferred的处理过程大概是这样:


每一个阶段返回的只可能是两种结果:正确或错误。如果返回正确结果,就按绿线往下;如果返回错误结果,就按红线往下。这种回调链,非常清晰的展示了程序流程和异常处理过程。

Example

用教程中的一个例子来分析下。这个例子共三个文件,fastpoetry.py是一个TCP服务器,当客户端与其建立连接后就向客户端发送一段诗歌;transformedpoetry.py是一个转换服务器,将客户端发送过来的文本转换成小写格式;get-poetry.py是客户端,先从TCP服务器下载文本,再将文本发送给转换服务器得到小写内容,最后将结果打印。
首先看fastpoetry.py

class PoetryProtocol(Protocol):
    def connectionMade(self):
        self.transport.write(self.factory.poem)
        self.transport.loseConnection()

class PoetryFactory(ServerFactory):
    protocol = PoetryProtocol

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

这里定义了发送文本的协议,当有客户端连接时,就向其发送文本,完成后断开连接,就这么简单。

transformedpoetry.py

class TransformService(object):

    def cummingsify(self, poem):
        return poem.lower()

class TransformProtocol(NetstringReceiver):

    def stringReceived(self, request):
        if '.' not in request:  # bad request
            self.transport.loseConnection()
            return

        xform_name, poem = request.split('.', 1)

        self.xformRequestReceived(xform_name, poem)

    def xformRequestReceived(self, xform_name, poem):
        new_poem = self.factory.transform(xform_name, poem)

        if new_poem is not None:
            self.sendString(new_poem)

        self.transport.loseConnection()


class TransformFactory(ServerFactory):

    protocol = TransformProtocol

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

    def transform(self, xform_name, poem):
        thunk = getattr(self, 'xform_%s' % (xform_name,), None)

        if thunk is None: # no such transform
            return None

        try:
            return thunk(poem)
        except:
            return None # transform failed

    def xform_cummingsify(self, poem):
        return self.service.cummingsify(poem)

其实处理过程很简单,就是接收客户发来的文本,然后转换成小写后返回给客户端。不过transform函数实现了用户指定可用服务,即在用户发送的内容中获取其需要的服务,然后匹配服务器提供的服务,匹配成功就执行。

以上两个服务器都是使用了Twisted内置的协议,代码都显得很简单和清晰,处理过程都是实时的,所以也没用到回调。

get-poetry.py
客户端稍微复杂些,有两个异步过程,先是从TCP服务器获取文本,再将文本发送给转换服务器,获得小写内容。

def poetry_main():
    ...
    for address in addresses:
    host, port = address
    d = get_poetry(host, port)
    d.addCallback(try_to_cummingsify)
    d.addCallbacks(got_poem, poem_failed)
    d.addBoth(poem_done)

这里可以看到Deferred回调链的全貌,主链是在get_poetry中定义的deferred

def get_poetry(host, port):
    d = defer.Deferred()
    from twisted.internet import reactor
    factory = PoetryClientFactory(d)
    reactor.connectTCP(host, port, factory)
    return d

随后添加try_to_cummingsify回调

def try_to_cummingsify(poem):
    d = proxy.xform('cummingsify', poem)

    def fail(err):
        print >>sys.stderr, 'Cummingsify failed!'
        return poem

    return d.addErrback(fail)

不过,这里要注意的是try_to_cummingsify中又定义了一个新的小回调链

class TransformClientFactory(ClientFactory):

    protocol = TransformClientProtocol

    def __init__(self, xform_name, poem):
        self.xform_name = xform_name
        self.poem = poem
        self.deferred = defer.Deferred()

try_to_cummingsify返回给主回调链的是自己定义的deferred,相当于在deferred的回调链中又返回了一个 deferred。这时要等待内层的deferred被激活,主回调链才会继续往下。
随后用got_poempoem_failed分别处理获取文本成功和失败的回调结果。最后用poem_done判断整个程序是否执行完毕。

测试:
先启动TCP服务器和转换服务器:

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

再开启客户端,就可以看到转换好的文本:

python twisted-client-6/get-poetry.py 10001 10002 

others

内敛回调——使用生成器代替deferred回调
DeferredList——管理所有的异步操作
twistd——开启Twisted守护进程的脚本(有点像supervisord)
Deferred.cancel——取消还没有返回结果的deferred回调

这些内容都是Twisted的重要功能,但不影响对Twisted主要思想的理解,所以就大概看了下,需要用到的时候再会过来细读。

end

学完这个教程,首先感觉是Twisted真是如其名般的绕,比如get-poetry.py中要分析出某一部分具体的功能,得跟进层层回调。但当理解过后,又会觉得其实思路很清晰。
可能只有需要一个用到TCP等偏底层协议的对性能有些要求的服务器的时候,才会想到去用Twisted这个老框架。但是Twisted会让你对异步编程的模式有基本的认识,而对处理流程的分解这种思路在其他的地方也是可以借鉴的。

Comments
Write a Comment