剖析Python的Twisted框架的核心特性


Posted in Python onMay 25, 2016

一. reactor
twisted的核心是reactor,而提到reactor不可避免的是同步/异步,阻塞/非阻塞,在Dave的第一章概念性介绍中,对同步/异步的界限有点模糊,关于同步/异步,阻塞/非阻塞可参见知乎讨论。而关于proactor(主动器)和reactor(反应堆),这里有一篇推荐博客有比较详细的介绍。
就reactor模式的网络IO而言,应该是同步IO而不是异步IO。而Dave第一章中提到的异步,核心在于:显式地放弃对任务的控制权而不是被操作系统随机地停止,程序员必须将任务组织成序列来交替的小步完成。因此,若其中一个任务用到另外一个任务的输出,则依赖的任务(即接收输出的任务)需要被设计成为要接收系列比特或分片而不是一下全部接收。
显式主动地放弃任务的控制权有点类似协程的思考方式,reactor可看作协程的调度器。reactor是一个事件循环,我们可以向reactor注册自己感兴趣的事件(如套接字可读/可写)和处理器(如执行读写操作),reactor会在事件发生时回调我们的处理器,处理器执行完成之后,相当于协程挂起(yield),回到reactor的事件循环中,等待下一个事件来临并回调。reactor本身有一个同步事件多路分解器(Synchronous Event Demultiplexer),可用select/epoll等机制实现,当然twisted reactor的事件触发不一定是基于IO,也可以由定时器等其它机制触发。
twisted的reactor无需我们主动注册事件和回调函数,而是通过多态(继承特定类,并实现所关心的事件接口,然后传给twisted reactor)来实现。关于twisted的reactor,有几个需要注意的地方:
twisted.internet.reactor是单例模式,每个程序只能有一个reactor;
尽量在reactor回调函数尽快完成操作,不要执行阻塞任务,reactor本质是单线程,用户回调代码与twisted代码运行在同一个上下文,某个回调函数中阻塞,会导致reactor整个事件循环阻塞;
reactor会一直运行,除非通过reactor.stop()显示停止它,但一般调用reactor.stop(),也就意味着应用程序结束;

二. twisted简单使用
twisted的本质是reactor,我们可以使用twisted的底层API(避开twisted便利的高层抽象)来使用reactor:

# 示例一 twisted底层API的使用
from twisted.internet import reacto
from twisted.internet import main
from twisted.internet.interfaces import IReadDescriptor
import socket

class MySocket(IReadDescriptor):
  def __init__(self, address):
    # 连接服务器
    self.address = address
    self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.sock.connect(address)
    self.sock.setblocking(0)

    # tell the Twisted reactor to monitor this socket for reading
    reactor.addReader(self)
 
 # 接口: 告诉reactor 监听的套接字描述符
  def fileno(self):
    try:
      return self.sock.fileno()
    except socket.error:
      return -1
      
 # 接口: 在连接断开时的回调
  def connectionLost(self, reason):
    self.sock.close()

    reactor.removeReader(self)
 
 # 当应用程序需要终止时 调用:
    # reactor.stop()

 # 接口: 当套接字描述符有数据可读时
  def doRead(self):
    bytes = ''

 # 尽可能多的读取数据
    while True:
      try:
        bytesread = self.sock.recv(1024)
        if not bytesread:
          break
        else:
          bytes += bytesread
      except socket.error, e:
        if e.args[0] == errno.EWOULDBLOCK:
          break
        return main.CONNECTION_LOST

    if not bytes: 
      return main.CONNECTION_DONE
    else:
      # 在这里解析协议并处理数据
      print bytes

示例一可以很清晰的看到twisted的reactor本质:添加监听描述符,监听可读/可写事件,当事件来临时回调函数,回调完成之后继续监听事件。
需要注意:
套接字为非阻塞,如果为阻塞则失去了reactor的意义
我们通过继承IReadDescriptor来提供reactor所需要的接口
通过reactor.addReader将套接字类加入reactor的监听对象中
main.CONNECTION_LOST是twisted预定义的值,通过这些值它我们可以一定程度控制下一步回调(类似于模拟一个事件)
但是上面的MySocket类不够好,主要有以下缺点:
需要我们自己去读取数据,而不是框架帮我们读好,并处理异常
网络IO和数据处理混为一块,没有剥离开来

三. twisted抽象
twisted在reactor的基础上,建立了更高的抽象,对一个网络连接而言,twisted建立了如下三个概念:
Transports:网络连接层,仅负责网络连接和读/写字节数据
Protocols: 协议层,服务业务相关的网络协议,将字节流转换成应用所需数据
Protocol Factories:协议工厂,负责创建Protocols,每个网络连接都有一个Protocols对象(因为要保存协议解析状态)
twisted的这些概念和erlang中的ranch网络框架很像,ranch框架也抽象了Transports和Protocols概念,在有新的网络连接时,ranch自动创建Transports和Protocols,其中Protocols由用户在启动ranch时传入,是一个实现了ranch_protocol behaviour的模块,Protocols初始化时,会收到该连接对应的Transports,如此我们可以在Protocols中处理字节流数据,按照我们的协议解析并处理数据。同时可通过Transports来发送数据(ranch已经帮你读取了字节流数据了)。
和ranch类似,twisted也会在新连接到达时创建Protocols并且将Transport传入,twisted会帮我们读取字节流数据,我们只需在dataReceived(self, data)接口中处理字节流数据即可。此时的twisted在网络IO上可以算是真正的异步了,它帮我们处理了网络IO和可能遇到的异常,并且将网络IO和数据处理剥离开来,抽象为Transports和Protocols,提高了程序的清晰性和健壮性。

# 示例二 twisted抽象的使用
from twisted.internet import reactor
from twisted.internet.protocol import Protocol, ClientFactory
class MyProtocol(Protocol):
 
 # 接口: Protocols初始化时调用,并传入Transports
 # 另外 twisted会自动将Protocols的factory对象成员设为ProtocolsFactory实例的引用
 #   如此就可以通过factory来与MyProtocolFactory交互
 def makeConnection(self,trans):
    print 'make connection: get transport: ', trans
    print 'my factory is: ', self.factory
    
 # 接口: 有数据到达
  def dataReceived(self, data):
    self.poem += data
    msg = 'Task %d: got %d bytes of poetry from %s'
    print msg % (self.task_num, len(data), self.transport.getPeer())
 
 # 接口: 连接断开
  def connectionLost(self, reason):
    # 连接断开的处理


class MyProtocolFactory(ClientFactory):

 # 接口: 通过protocol类成员指出需要创建的Protocols
  protocol = PoetryProtocol # tell base class what proto to build

  def __init__(self, address):
    self.poetry_count = poetry_count
    self.poems = {} # task num -> poem
    
 # 接口: 在创建Protocols的回调
  def buildProtocol(self, address):
    proto = ClientFactory.buildProtocol(self, address)
    # 在这里对proto做一些初始化....
    return proto
    
 # 接口: 连接Server失败时的回调
  def clientConnectionFailed(self, connector, reason):
    print 'Failed to connect to:', connector.getDestination()
    
def main(address):
 factory = MyClientFactory(address)
  host, port = address
  # 连接服务端时传入ProtocolsFactory
  reactor.connectTCP(host, port, factory) 
  reactor.run()

示例二要比示例一要简单清晰很多,因为它无需处理网络IO,并且逻辑上更为清晰,实际上ClientFactory和Protocol提供了更多的接口用于实现更灵活强大的逻辑控制,具体的接口可参见twisted源代码。

四. twisted Deferred
twisted Deferred对象用于解决这样的问题:有时候我们需要在ProtocolsFactory中嵌入自己的回调,以便Protocols中发生某个事件(如所有Protocols都处理完成)时,回调我们指定的函数(如TaskFinished)。如果我们自己来实现回调,需要处理几个问题:
如何区分回调的正确返回和错误返回?(我们在使用异步调用时,要尤其注意错误返回的重要性)
如果我们的正确返回和错误返回都需要执行一个公共函数(如关闭连接)呢?
如果保证该回调只被调用一次?
Deferred对象便用于解决这种问题,它提供两个回调链,分别对应于正确返回和错误返回,在正确返回或错误返回时,它会依次调用对应链中的函数,并且保证回调的唯一性。

d = Deferred()
# 添加正确回调和错误回调
d.addCallbacks(your_ok_callback, your_err_callback)
# 添加公共回调函数
d.addBoth(your_common_callback)

# 正确返回 将依次调用 your_ok_callback(Res) -> common_callback(Res)
d.callback(Res)
# 错误返回 将依次调用 your_err_callback(Err) -> common_callback(Err)
d.errback(Err)

# 注意,对同一个Defered对象,只能返回一次,尝试多次返回将会报错

twisted的defer是异步的一种变现方式,可以这么理解,他和thread的区别是,他是基于时间event的。
有了deferred,即可对任务的执行进行管理控制。防止程序的运行,由于等待某项任务的完成而陷入阻塞停滞,提高整体运行的效率。
Deferred能帮助你编写异步代码,但并不是为自动生成异步或无阻塞的代码!要想将一个同步函数编程异步函数,必须在函数中返回Deferred并正确注册回调。

五.综合示例

下面的例子,你们自己跑跑,我上面说的都是一些个零散的例子,大家对照下面完整的,走一遍。 twisted理解其实却是有点麻烦,大家只要知道他是基于事件的后,慢慢理解就行了。

#coding:utf-8
#xiaorui.cc
from twisted.internet import reactor, defer
from twisted.internet.threads import deferToThread
import os,sys
from twisted.python import threadable; threadable.init(1)
deferred =deferToThread.__get__
import time
def todoprint_(result):
  print result
def running():
  "Prints a few dots on stdout while the reactor is running."
#   sys.stdout.write("."); sys.stdout.flush()
  print '.'
  reactor.callLater(.1, running)
@deferred
def sleep(sec):
  "A blocking function magically converted in a non-blocking one."
  print 'start sleep %s'%sec
  time.sleep(sec)
  print '\nend sleep %s'%sec
  return "ok"
def test(n,m):
  print "fun test() is start"
  m=m
  vals = []
  keys = []
  for i in xrange(m):
    vals.append(i)
    keys.append('a%s'%i)
  d = None
  for i in xrange(n):
    d = dict(zip(keys, vals))
  print "fun test() is end"
  return d
if __name__== "__main__":
#one
  sleep(10).addBoth(todoprint_)
  reactor.callLater(.1, running)
  reactor.callLater(3, reactor.stop)
  print "go go !!!"
  reactor.run()
#two
  aa=time.time()
  de = defer.Deferred()
  de.addCallback(test)
  reactor.callInThread(de.callback,10000000,100 )
  print time.time()-aa
  print "我这里先做别的事情"
  print de
  print "go go end"
Python 相关文章推荐
Python StringIO模块实现在内存缓冲区中读写数据
Apr 08 Python
Python中join和split用法实例
Apr 14 Python
Python中type的构造函数参数含义说明
Jun 21 Python
Python画图学习入门教程
Jul 01 Python
Python2.7编程中SQLite3基本操作方法示例
Aug 09 Python
python机器学习之神经网络(二)
Dec 20 Python
便捷提取python导入包的属性方法
Oct 15 Python
python自定义线程池控制线程数量的示例
Feb 22 Python
Python3.7 新特性之dataclass装饰器
May 27 Python
Python底层封装实现方法详解
Jan 22 Python
基于Python实现下载网易音乐代码实例
Aug 10 Python
Python OpenCV形态学运算示例详解
Apr 07 Python
实例解析Python的Twisted框架中Deferred对象的用法
May 25 #Python
详解Python的Twisted框架中reactor事件管理器的用法
May 25 #Python
使用Python的Twisted框架编写非阻塞程序的代码示例
May 25 #Python
Python的Twisted框架中使用Deferred对象来管理回调函数
May 25 #Python
使用Python的Twisted框架构建非阻塞下载程序的实例教程
May 25 #Python
Python的Twisted框架上手前所必须了解的异步编程思想
May 25 #Python
Python的re模块正则表达式操作
May 25 #Python
You might like
漫荒推荐:画风超赞的国风漫画推荐 超长假期不无聊
2020/03/08 国漫
php 301转向实现代码
2008/09/18 PHP
PHP为表单获取的URL 地址预设 http 字符串函数代码
2010/05/26 PHP
关于php操作mysql执行数据库查询的一些常用操作汇总
2013/06/24 PHP
PHP数组和explode函数示例总结
2015/05/08 PHP
详解Yii2 rules 的验证规则
2016/12/02 PHP
通过MSXML2自动获取QQ个人头像及在线情况(给初学者)
2007/01/22 Javascript
Javascript结合css实现网页换肤功能
2009/11/02 Javascript
JAVASCRIPT实现的WEB页面跳转以及页面间传值方法
2010/05/13 Javascript
基于jquery的inputlimiter 实现字数限制功能
2010/05/30 Javascript
jQuery创建自定义的选择器用以选择高度大于100的超链接实例
2015/03/18 Javascript
AngularJS中指令的四种基本形式实例分析
2016/11/22 Javascript
xmlplus组件设计系列之按钮(2)
2017/04/26 Javascript
jQuery轮播图实例详解
2018/08/15 jQuery
vue.js实现只能输入数字的输入框
2019/10/19 Javascript
[02:51]2014DOTA2 TI小组赛总结中国军团全部进军钥匙球馆
2014/07/15 DOTA
python模拟登录百度代码分享(获取百度贴吧等级)
2013/12/27 Python
python从sqlite读取并显示数据的方法
2015/05/08 Python
Python判断列表是否已排序的各种方法及其性能分析
2016/06/20 Python
Python自动化测试Eclipse+Pydev 搭建开发环境
2016/08/15 Python
Python+matplotlib实现填充螺旋实例
2018/01/15 Python
python实现图书管理系统
2018/03/12 Python
python读取图片任意范围区域
2019/01/23 Python
python 生成器和迭代器的原理解析
2019/10/12 Python
基于Python实现简单学生管理系统
2020/07/24 Python
python中zip()函数遍历多个列表方法
2021/02/18 Python
关于HTML5 Placeholder新标签低版本浏览器下不兼容的问题分析及解决办法
2016/01/27 HTML / CSS
详解canvas.toDataURL()报错的解决方案全都在这了
2020/03/31 HTML / CSS
给学校的建议书范文
2014/05/15 职场文书
2014年酒店工作总结与计划
2014/11/17 职场文书
幼儿园中班教师个人工作总结
2015/02/06 职场文书
员工工作表扬信
2015/05/05 职场文书
调研报告的主要写法
2019/04/18 职场文书
我对PyTorch dataloader里的shuffle=True的理解
2021/05/20 Python
JavaWeb 入门:Hello Servlet
2021/07/16 Java/Android
Python+腾讯云服务器实现每日自动健康打卡
2021/12/06 Python