剖析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过滤函数filter()使用自定义函数过滤序列实例
Aug 26 Python
Python编写生成验证码的脚本的教程
May 04 Python
python中判断文件编码的chardet(实例讲解)
Dec 21 Python
Python实现PS滤镜的旋转模糊功能示例
Jan 20 Python
python和shell监控linux服务器的详细代码
Jun 22 Python
python 读取竖线分隔符的文本方法
Dec 20 Python
python集合是否可变总结
Jun 20 Python
Python符号计算之实现函数极限的方法
Jul 15 Python
python实现点击按钮修改数据的方法
Jul 17 Python
Tensorflow训练模型越来越慢的2种解决方案
Feb 07 Python
解析Python 偏函数用法全方位实现
Jun 26 Python
Django数据库(SQlite)基本入门使用教程
Jul 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
php解析json数据实例
2014/08/19 PHP
学习php设计模式 php实现门面模式(Facade)
2015/12/07 PHP
浅谈PHP链表数据结构(单链表)
2016/06/08 PHP
PHP通过加锁实现并发情况下抢码功能
2016/08/10 PHP
PHP实现数组转JSon和JSon转数组的方法示例
2018/06/14 PHP
php闭包中使用use声明变量的作用域实例分析
2018/08/09 PHP
PHP封装请求类实例分析【基于Yii框架】
2019/10/17 PHP
Javascript中的call()方法介绍
2015/03/15 Javascript
js获取页面description的方法
2015/05/21 Javascript
jquery基础知识第一讲之认识jquery
2016/03/17 Javascript
AngularJS 入门教程之事件处理器详解
2016/08/19 Javascript
Bootstrap对话框使用实例讲解
2016/09/24 Javascript
node.js缺少mysql模块运行报错的解决方法
2016/11/13 Javascript
jsonp跨域请求实现示例
2017/03/13 Javascript
ionic3+Angular4实现接口请求及本地json文件读取示例
2017/10/11 Javascript
angular 未登录状态拦截路由跳转的方法
2018/10/09 Javascript
JavaScript模板引擎应用场景及实现原理详解
2018/12/14 Javascript
nvm、nrm、npm 安装和使用详解(小结)
2019/01/17 Javascript
微信小程序自定义头部导航栏(组件化)
2019/11/15 Javascript
vue npm install 安装某个指定的版本操作
2020/08/11 Javascript
[03:09]DOTA2亚洲邀请赛 LGD战队出场宣传片
2015/02/07 DOTA
Python基于PycURL自动处理cookie的方法
2015/07/25 Python
使用Python对IP进行转换的一些操作技巧小结
2015/11/09 Python
python实现对求解最长回文子串的动态规划算法
2018/06/02 Python
Jupyter Notebook的连接密码 token查询方式
2020/04/21 Python
python性能测试工具locust的使用
2020/12/28 Python
canvas学习笔记之2d画布基础的实现
2019/02/21 HTML / CSS
从零实现一个自定义html5播放器的示例代码
2017/08/01 HTML / CSS
韩国女装NO.1网店:STYLENANDA
2016/09/16 全球购物
联想法国官方网站:Lenovo法国
2018/10/18 全球购物
会计毕业生自荐信
2013/11/21 职场文书
幼儿园实习自我鉴定
2013/12/15 职场文书
加工操作管理制度
2014/01/19 职场文书
2014年统计工作总结
2014/11/21 职场文书
2016年优秀共产党员先进事迹材料
2016/02/29 职场文书
pytorch 实现在测试的时候启用dropout
2021/05/27 Python