在第四部分我们写了我们的第一个twisted client.它工作的非常好,但仍旧有提升的空间

首先,这个twisted client 包含了一些比如创建sockets 和从这些sockets 中接收数据的细节.twisted 已经对这些操作提供了支持,所以我们没有必要自己再去实现.这个特别有用因为异步的I/O在处理异常方面需要一些技巧,如果需要支持多平台的话需要更多的技巧.如果你有一个空闲的下午,你可以在twisted 源码中搜索以下win32,你可以看到因为平台问题到底引入了多少问题.
另外一个问题就是出错处理,试着在没有启动server的情况下运行twisted client, 它会崩溃掉,我们会修复现在的twisted client,用twisted 的api 你会发现这很容易.
最后,这个client 不是特别可以复用.另外一个模块怎样来用我们的client? “calling” module 是怎样知道我们什么时候执行完下载的? 我们不能仅仅写一个函数直接返回结果,那样会引起阻塞.这些不是我们今天要全部解决的问题,我们会在后面的几篇教程中解决最后一个问题.

我们今天要解决是是第一个和第二个问题,主要是通过一些高级的api 和一些接口.twisted 框架是抽象层松耦合的,学习twisted 就是要学习这些层能提供什么.比如,,每一层能提供什么api,接口,还有什么实现是可用的.我们不会详细的讲解每一层的抽象,我们仅仅去看一些最重要的碎片去理解twisted是怎样被组合起来的.一但你对twisted 的结构全貌了解了,你再去学习twisted 的其他的部分就会容易很多.

一般来说,每一个twisted 的抽象都跟一个特别的概念相关.比如, 第四部分中的twisted client 使用的 IReadDescriptor 就是一个抽象–”可以读取内容的文件描述符”.一个twisted 的抽象被一个接口定义,这个接口指定了一个被这个抽象实例化了的对象应该有什么样的表现.你要记住的是当你学一个twisted 的抽象时:

大多数的高层的抽象是用底层的建立起来的,而不是替换它们

所以当你学习一个新的twisted 抽象时,记住它做了什么和没做什么.特别的,对于一些早期的抽象,可能抽象A 实现了特色F,然后F再也没有被其他的抽象实现.当然,假如抽象B需要特色F,它用使用A而不是再去实现一遍F.
网络是个复杂的主题,因此twisetd 中包含很多种抽象.从低级的抽象开始,我们可以清晰的得到一个结构–它们在一个正常运行的系统中是怎样结合在一起的.
Loopiness in the Brain
reactor是我们目前接触的最重要的抽象,也是twisetd中最重要的抽象.在一个twisted程序的中间,不管你的程序有多少层,总有一个reactor 循环在来维持着这个程序不停的运行.twisted 中的其他的抽象都不会提供这样的功能.twisted 的其他的部分都可以被看做可以让我们更容易使用reactor 的物体,比如做一个web服务或者查询一个sql语句.可能我们还会用一个些底层的api,就像twisetd client 1.0,但是这就需要我们自己去实现更多的内容.如果用高级api则就意味着我们需要写更少的代码.

但是在我们写twisted 外层的东西的时候我们经常忘了reactor 的存在,在一个正常的twisted 的应用中,只有很少的一部分会直接用到reactor api.对于一些底层的抽象来说也是这样.我们在twisted client 1.0 中的使用的文件描述符抽象就被高级的抽象包装,以至于在实际的twisted程序中并不会被用到.

就文件描述符来说,这并不是一个问题.让twisted 来处理异步的I/O 可以让我们专注于我们要解决的问题. 对于reactor 就不一样了,reactor 不会不出现,当你选择使用twisted时候你已经选择了reactor模式,这就意味着 reactor模式的代码会用callback 和 多任务合作. 如果你想用好twisted,你必须时候想着reactor 的存在,我们会在第六章讲关于这个的更多的内容,现在你要记住的是:

图片五和图片六是最重要的两张图片

我们将会继续使用图片来说明新的概念,但这两张图片你需要印到你的心里.这两张图片确实在我心里在我用twisted写代码的时候.

在我们进入代码之前,还有三个抽象需要我们去了解:Transports,Protocols和Protocol Factories.

Transports

Transport 抽象在twisted interfaces 模块中被ITransport 定义,一个twisted transport 代表了一个可以接收和发送数据的连接.在我们的client 中tranport 就想一个tcp 连接.但是twisted也支持UNIX PIPES 和 UDP socket 类型的I/O. transport 抽象代表了任何一个它们其中的一种连接,并处理每种连接带来的各种异步I/O的细节.

如果你查看了ITransport 中定义的方法,你不会发现有用来接收数据的方法.那是因为transport 被用来从连接中异步的读数据,然后把读到的数据传递通过callback传递给我们.所以transport 的写相关的方法为了防止阻塞并不是立即把数据交给我们.告诉transport 去写数据意味着:在不产生阻塞的情况下尽快的把数据传送出去.

我们并不会实现我们自己的transport 或者创建一个,而是我们用twsited 已经帮我们实现的transport,reactor 在创建连接的时候会帮我们创建transport.

Protocols

twsited protocol 是在interfaces模块中被IProtocol 定义.Protocol 对象实现了protocol,也就是说,一个twisted Protocol 的实现应该是实现了一个特别的网络协议,就像FTP或者IMAP或者其他的我们自己实现的协议.我们的poetry protocol,也是一种协议,在连接建立之后开始传送数据,连接断开表示诗已经传送完毕.

严格的来说,每一个twisted Protocol 对象的实例都为一个特别的连接实现一个协议.所以我们程序的每一个连接都会需要一个Protocol 的实例.这就让Protocol 实例自然而然的成为了存储协议状态和部分累积数据(因为在异步io中我们每一次接收的数据块不是固定大小的)的地方.

所以Protocol 实例是怎样知道它是在为哪一个连接负责? 假如我们看一下IProtocol 的定义,我们将会看到makeConnection.这个方法是一个callback,twisted代码会调用它,一个transport 实例作为参数.transport 会被连接和协议用到.

twisted 已经包含了大量的协议的实现.你可以看到一些简单的协议在twisted.protocols.basic,在你想写一个新的协议之前你最好去查找一下源代码看有没有已经实现的协议符合你的要求.如果没有的话,去实现你自己的协议也是很简单的事情.

Protocol Factories

每一个连接需要它自己的Protocol,这个Protocol 应该是我们实现的一个类的实例.既然我们让twisted 来处理创建连接,twisted 需要一种当一个连接被建立时就会有一个合适的protocol为它准备好的方法. 创建Protocol实例的任务就交给Protocol Factories.

也许你已经猜到,Protocol Factory API 被IProtocolFactory 定义,也在interfaces 模块中.Protocol Factories 是工场设计模式的一个例子. buildProtocol 在每次调用的时候会返回一个Protocol 的实例,这个就是twisted 用来为每个新的连接新建protocol 的方法.

Get Poetry 2.0: First Blood.0
现在让我们来看一下Twisted poery client 的2.0 版本.代码在twisted-client-2/get-poetry.py.你可以运行它然后它的输出和1.0 版本的很像.这个也会是最后一个会打印任务号的client.到现在为止你应该对多任务交互运行和每一次只会读取一小块数据比较清楚了.我们仍旧会用打印语句来告诉你现在运行到什么地方了,但是在不久的将来这些代码不会这么冗长.
在client 2.0 中,socket 已经消失了.我甚至不用导入socket 模块,并且我们永远不会再提到socket,以及文件描述符了.我们用以下的代码让reactor 去创造连接:

factory = PoetryClientFactory(len(addresses))

from twisted.internet import reactor

for address in addresses:
    host, port = address
    reactor.connectTCP(host, port, factory)

我们可以来看一下connectTCP,前面的两个参数是自解释的,第三个是PoetryClientFactory 的实例,这个是poetry client 的的Protocol Factory,传递给reactor,然后twisted 就可以创造PoetryProtocol.

注意一下我们实现Factory 或 Protocol 的时候并不是乱写的,不像PoetrySocket对象,我们是继承了twisted 提供的twisted.internet.protocol和twisted.internet.protocol.Factory.但我们这里是用的ClientFactory
clientfactory 是专门为client 准备的(client 是建立连接,而server 是在等待连接).
我们也利用twsited factory 实现了buildProtocol,我们在子类中调用了父类中的buildProtocol方法:

def buildProtocol(self, address):
    proto = ClientFactory.buildProtocol(self, address)
    proto.task_num = self.task_num
    self.task_num += 1
    return proto

基类是怎样知道去建立那些protocol呢?注意我们已经在PoetryClientFactory 中设置protocol 属性了:

class PoetryClientFactory(ClientFactory):

task_num = 1

protocol = PoetryProtocol # tell base class what proto to build

Factory 基类通过我们设置的protocol 属性来实现buildProtocol,并在新建立的protocol实例上设置factory属性,这时的factory是ProtocolFactory 的引用.这个过程可以用图片八来表明:

图片八

就像我们在上面提到的,factory 做为Protocol 的属性,这样被创造出来的Protocol 就可以通过factory来进行共享状态.由于factory 是被我们自己写的代码控制的(相对于被twisted 控制的部分),拥有相同的factory 属性可以让protocol 对象把运行时的状态交给我们写的代码.(这两段是根据自己的理解翻译过来的), 在第六部分我们会看到具体的应用.

注意,在factory 做Protocol 属性的时候,这时的factory 是 Protocol Factory 的一个实例,factory 的protocol 属性是Protocol 类的引用而不是一个实例的引用,因为一个factory会创建多个protocol实例.

Protocol 结构 用transport 连接一个protocol 的第二个阶段是用makeConnection方法.我们没有必要自己实现这个方法因为twisted 的基类提供了一个默认的实现.默认的,makeConnection有一个引用了Transport 的叫做transport的属性,并设置了connected属性为True,图片九描述了这个过程:

图片九

一但初始完这些,Protocol 就可以干点真正的工作了–把底层的数据流转化为高级的协议信息数据流,处理数据的主要的方法为dataReceived,我们的客户端是这样实现的:

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.getHost())

每一次dataReceived 被调用,我们就会以字符串的形式得到一个新的字节序列.因为是异步IO操作,我们不清楚我们会得到多少数据,所以我们会进行缓冲直到有一个完整的protocol 消息.在我们的client 中,诗不会停止直到连接断开,所以我们不停的像我们的.poem属性中增加字节.
我们在Transport中使用getHost 方法来区别这些数据来自哪一个server,我们以前的client中也是这么做的.否则我们的代码没有必要用Tansport.因为我们不用向server端送出任何数据.

让我们快速的看一下当dataReceived 被调用的时候发生了什么?在我们的client 2.0相同目录下,有一个client叫做 twisted-client-2/get-poetry-stack.py,与2.0 版本不同的是dataReceived 被改成了这样:

def dataReceived(self, data):
    traceback.print_stack()
    os._exit(0)

这个改变会让程序打印出堆栈信息,然后退出.你可以运行它像这样

python twisted-client-2/get-poetry-stack.py 10000

将会有如下的输出:

File "twisted-client-2/get-poetry-stack.py", line 125, in
poetry_main()

... # I removed a bunch of lines here

File ".../twisted/internet/tcp.py", line 463, in doRead # Note the doRead callback
return self.protocol.dataReceived(data)
File "twisted-client-2/get-poetry-stack.py", line 58, in dataReceived
traceback.print_stack()

你会看到我们在twised client 1.0 中用到的doRead callback .就像我们之前说的,twisted 用底层的抽象来建立上一层的抽象,并不是替换掉它们.所以在这个过程中仍就会有一个IReadDescriptor的实现在起作用,它仅仅被twisted 实现了而不是我们自己的代码.如果你好奇的话,twisted 的实现在 twisted.internet.tcp 中.如果你跟踪源代码,你会发现有一些对象实现了IWriteDescriptor 和 ITransport.所以Transport 最终是IReadDescriptor 的表现形式.我们可以用一张图片来形象化dataReceived, 图片十:


图片十

一但一首小诗完成了下载,PoetryProtocol 对象会通知PoetryClientFactory:

def connectionLost(self, reason):
    self.poemReceived(self.poem)

def poemReceived(self, poem):
    self.factory.poem_finished(self.task_num, poem)

connectionLost callback 被触发当transport 的连接断开之后,reason 参数是一个twisted.python.failure.Failure对象带着额外的信息来告诉我们这个连接是正常的断开还是由于错误.我们的client 忽略这个值假设我们已经接收到整个的诗.
factory 会停掉reactor 在所有的诗被处理完之后.再一次的假设我们的的程序唯一做的事情就是下载诗,这样就造成我们的PoetryClientFactory 对象不可重用,我们会解决这个问题在下一部分.但是要注意poem_finished callback 是怎样记录剩余没有完成的诗的数量.

...
    self.poetry_count -= 1

if self.poetry_count == 0:
        ...

假如我们正在写一个多进程程序,每一首诗在一个不同的进程中进行,我们应该保护这部分的代码用一个锁防止两个进程或多个进程同时调用poem_finished.否则的话 我们可能会关闭reactor 两次.但是在一个reactive 的系统中我们不需要这样.reactor 一次只能运行一个callback,所以这种情况不会发生.

我们的新的client 在处理错误的方面比client 1.0 更优雅.下面是PoetryClientFactory 类中的出错处理:

def clientConnectionFailed(self, connector, reason):
    print 'Failed to connect to:', connector.getDestination()
    self.poem_finished()

注意这个callback 是在factory里,不是在protocol 里.因为protocol 是在连接建立之后才被创建的.factory 会获取到信息当一个连接不能被建立的时候.

A simpler client
尽管我们的client 已经很简单了,我们可以让它更简单如果不用任务号的话,这个是简化了的2.1 版本:twisted-client-2/get-poetry-simple.py

Wrapping Up
client 2.0 版本使用了twisted 用户比较熟悉的抽象.假如我们需要一个命令行的client,打印出诗然后退出,我们可以停在这里然后让我们的程序停止.但是假如我们需要一些可复用的代码,一些可以用在一个大型系统里面的代码,不光用来下载诗还用来做其他的事情,我们仍旧有很多工作要去做.在第六部分我们将会近一步的尝试.

twisted系列教程五–改进twisted poetry client相关推荐

  1. twisted系列教程十六–twisted守护进程

    Introduction 到目前为止我们写的server 还运行在一个终端里面,通过print 语句向外输出内容.开发的时候这样做是很有好处的,但是当你部署一个产品的时候这样就不好了.一个生产环境中的 ...

  2. Python Twisted系列教程16:Twisted 进程守护

    作者:dave@http://krondo.com/twisted-daemonologie/  译者: Cheng Luo 你可以从"第一部分 Twist理论基础"开始阅读:也可 ...

  3. python twisted教程_Python Twisted系列教程1:Twisted理论基础

    前言: 最近有人在Twisted邮件列表中提出诸如"为任务紧急的人提供一份Twisted介绍"的的需求.值得提前透露的是,这个序列并不会如他们所愿.尤其是介绍Twisted框架和基 ...

  4. twisted系列教程十一 — 一个twisted 的服务端

    A Twisted Poetry Server 既然我们已经学了这么多twisted client 的编写,现在让我们来用twisted来重新实现一下我们的poetry server 吧.我们要多谢谢 ...

  5. python twisted教程_Python Twisted系列教程16:Twisted 进程守护

    Fast Poetry 3.0 你可能注意到与其他例子不同, 我们命名了一个不同的目录. 这是因为 twistd 需要插件文件位于 twisted/plugins 目录中, 同时在你的Python搜索 ...

  6. C#微信公众号开发系列教程五(接收事件推送与消息排重)

    C#微信公众号开发系列教程五(接收事件推送与消息排重) 原文:C#微信公众号开发系列教程五(接收事件推送与消息排重) 微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续 ...

  7. Linux求平方脚本,Linux Shell脚本系列教程(五):数学运算

    这篇文章主要介绍了Linux Shell脚本系列教程(五):数学运算,本文讲解了使用let.(())和[]进行算术运算.使用expr进行算术运算.使用bc进行算术运算三种方法,需要的朋友可以参考下 在 ...

  8. SpringCloud系列教程(五)之SpringCloud Gateway 网关聚合开发文档 swagger knife4j 和登录权限统一验证【Hoxton版】

    阅读提醒: 本文面向的是有一定springboot基础者 本次教程使用的Spring Cloud Hoxton RELEASE版本 由于knife4j比swagger更加友好,所以本文集成knife4 ...

  9. 米思齐(Mixly)图形化系列教程(五)-if……else……与逻辑运算

    目录 比较运算 逻辑运算符 if esle 说明 例子 if程序的嵌套 例子 教程导航 联系我们 比较运算和逻辑运算返回两种结果,条件成立(真true)与不成立(假false) 比较运算 下表显示了支 ...

最新文章

  1. java io 缓冲流_记忆系列-Java IO的缓存输入输出流(高效流)
  2. 双轮摩托车模安装测试
  3. python opencv 实现任意角度的透视变换
  4. 解决webserver tcp连接大量CLOSE_WAIT 问题
  5. PHP中的加强型接口Traits
  6. 正在中止线程 异常处理
  7. 工作到了第七年,说说自己每天的工作(一)
  8. MAC下go语言的安装和配置
  9. php yii vendor,Yii2的安装--解决无Vendor目录的问题
  10. spring的核心组件
  11. 卡尔曼滤波原理及公式推导
  12. 分布式环境下限流方案的实现
  13. 【基于51单片机驱动ST7789VW的TFT显示屏240x240
  14. 帝国cms站群全自动采集入库程序源码分享
  15. 乐视max70老款_这货是电视?超大尺寸乐视TV Max70试玩
  16. fitbit aria体脂秤二次开发遇到的问题
  17. 从深度学习到LSTM
  18. 腾讯WeTest:为用户开新篇,七周年全球惠享巨献
  19. Spring特点与工作原理
  20. Python----第十次作业

热门文章

  1. 聚类分析(一)基本思想
  2. linux启动大叶机制,DPDK-Suricata应用部署
  3. 1.9 编程基础之二分查找 12 最长平台 python
  4. js原生实现过渡效果的返回顶部功能实例
  5. stm32与micropython_在NUCLEO_H743上玩MicroPython
  6. Web笔记-session盗用安全问题(Spring Boot获取所有session及提高安全性)
  7. Qt工作笔记-使用hiredis连接及查询Redis
  8. Java笔记-使用RabbitMQ的Java接口生产数据并消费
  9. Qt工作笔记-QSS中关于QScrollBar的设置
  10. java判断斐波那契数列_Java 实例 - 斐波那契数列