Twisted入门教程(5)

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

第五部分:由Twited支持的诗歌下载服务客户端

你可以从这里从头开始阅读这个系列

 

抽象地构建客户端

在第四部分中,我们构建了第一个使用Twisted的客户端。它确实能很好地工作,但仍有提高的空间。

首先是,这个客户端竟然有创建网络端口并接收端口处的数据这样枯燥的代码。Twisted理应为我们实现这些例程性功能,省得我们每次写一个新的程序时都要去自己实现。Twisted这样做也将我们从像异步I/O操作中包括许多像异常处理这样的细节处理解放出来。更多的细节处理存在于多平台上运行我们的代码中。如果你那个下午有空,可以翻翻Twisted的WIN32实现源代码,看看里面有多少小针线是来处理跨平台的。

另一问题是与错误处理有关。当运行版本1的Twisted客户端来从并没有提供服务的端口上下载诗歌时,它就会崩溃。我们是可以修正这个错误,但通过下面我们要介绍Twisted的APIs来处理这些类型的错误会更简单。

最后,那个客户端也不能复用。如果有另一个模块需要通过我们的客户端下载诗歌呢?人家怎么知道你的诗歌已经下载完毕?我们不能用一个方法简单地将一首诗下载完成后再传给人家,而在之前让人家处于等待状态。这确实是一个问题,但我们不准备在这个部分解决这个问题—在未来的部分中一定会解决这个问题。

我们将会使用一些高层次的APIs和接口来解决第一、二个问题。Twisted框架是由众多抽象层松散地组合起来的。因此,学习Twisted也就意味着需要学习这些层都提供什么功能,例如每层都有哪些APIs,接口和实例可供使用。接下来我们会通过剖析Twisted最最重要的部分来更好地感受一下Twisted都是怎么组织的。一旦你对Twisted的整个结构熟悉了,学习新的部分会简单多了。

一般来说,每个Twisted的抽象都只与一个特定的概念相关。例如,第四部分中的客户端使用的IReadDescriptor,它就是“一个可以读取字节的文件描述符”的抽象。一个抽象往往会通过定义接口来指定那些想实现个抽象(也就是实现这个接口)对象的形为。在学习新的Twisted抽象概念时,最需要谨记的就是:

多数高层次抽象都是在低层次抽象的基础上建立的,很少有另立门户的。

因此,你在学习新的Twisted抽象概念时,始终要记住它做什么和不做什么。特别是,如果一个早期的抽象A实现了F特性,那么F特性不太可能再由其它任何抽象来实现。另外,如果另外一个抽象需要F特性,那么它会使用A而不是自己再去实现F。(通常的做法,B可能会通过继承A或获得一个指向A实例的引用)

网络非常的复杂,因此Twisted包含很多抽象的概念。通过从低层的抽象讲起,我们希望能更清楚起看到在一个Twisted程序中各个部分是怎么组织起来的。

 

核心的循环体

第一个我们要学习的抽象,也是Twisted中最重要的,就是reactor。在每个通过Twisted搭建起来的程序中心处,不管你这个程序有多少层,总会有一个reactor循环在不停止地驱动程序的运行。再也没有比reactor提供更加基础的支持了。实际上,Twisted的其它部分(即除了reactor循环体)可以这样理解:它们都是来辅助X来更好地使用reactor,这里的X可以是提供Web网页、处理一个数据库查询请求或其它更加具体内容。尽管坚持像上一个客户端一样使用低层APIs是可能的,但如果我们执意那样做,那么我们必需自己来实现非常多的内容。而在更高的层次上,意味着我们可以少写很多代码。

但是当在外层思考与处理问题叶。很容易就忘记了reactor的存在了。在任何一个常见大小的Twisted程序中 ,确实很少会有直接与reactor的APIs交互。低层的抽象也是一样(即我们很少会直接与其交互)。我们在上一个客户端中用到的文件描述符抽象,就被更高层的抽象更好的归纳而至于我们很少会在真正的Twisted程序中遇到。(他们在内部依然在被使用,只是我们看不到而已)

至于文件描述符抽象的消息,这并不是一个问题。让Twisted掌舵异步I/O处理,这样我们就可以更加关注我们实际要解决的问题。但对于reactor不一样,它永远都不会消失。当你选择使用Twisted,也就意味着你选择使用Reactor模式,并且意味着你需要使用回调与多任务合作的“交互式”编程方式。如果你想正确地使用Twisted,你必须牢记reactor的存在。我们将在第六部分更加详细的讲解部分内容。但是现在要强调的是:

图5与图6是这个系列中最最重要的图

我们还将用图来描述新的概念,但这两个图是需要你牢记在脑海中的。可以这样说,我在写Twisted程序时一直想着这两张图。

在我们付诸于代码前,有三个新的概念需要阐述清楚:Transports,Protocols,Protocol Factoies

 

Transports

Transports抽象是通过Twisted中interfaces模块中ITransport接口定义的。一个Twisted的Transport代表一个可以收发字节的单条连接。对于我们的诗歌下载客户端而言,就是对一条TCP连接的抽象。但是Twisted也支持诸如Unix中管道和UDP。Transport抽象可以代表任何这样的连接并为其代表的连接处理具体的异步I/O操作细节。

如果你浏览一下ITransport中的方法,可能找不到任何接收数据的方法。这是因为Transports总是在低层完成从连接中异步读取数据的许多细节工作,然后通过回调将数据发给我们。相似的原理,Transport对象的写相关的方法为避免阻塞也不会选择立即写我们要发送的数据。告诉一个Transport要发送数据,只是意味着:尽快将这些数据发送出去,别产生阻塞就行。当然,数据会按照我们提交的顺序发送。

通常我们不会自己实现一个Transport。我们会去实现Twisted提供的类,即在传递给reactor时会为我们创建一个对象实例。

 

Protocols

Twisted的Protocols抽象由interfaces模块中的IProtocol定义。也许你已经想到,Protocol对象实现协议内容。也就是说,一个具体的Twisted的Protocol的实现应该对应一个具体网络协议的实现,像FTP、IMAP或其它我们自己规定的协议。我们的诗歌下载协议,正如它表现的那样,就是在连接建立后将所有的诗歌内容全部发送出去并且在发送完毕后关闭连接。

严格意义上讲,每一个Twisted的Protocols类实例都为一个具体的连接提供协议解析。因此我们的程序每建立一条连接(对于服务方就是每接受一条连接),都需要一个协议实例。这就意味着,Protocol实例是存储协议状态与间断性(由于我们是通过异步I/O方式以任意大小来接收数据的)接收并累积数据的地方。

因此,Protocol实例如何得知它为哪条连接服务呢?如果你阅读IProtocol定义会发现一个makeConnection函数。这是一个回调函数,Twisted会在调用它时传递给其一个也是仅有的一个参数,即就是Transport实例。这个Transport实例就代表Protocol将要使用的连接。

Twisted包含很多内置可以实现很多通用协议的Protocol。你可以在twisted.protocols.basic找到一些稍微简单点的。在你尝试写新Protocol时,最好是看看Twisted源码是不是已经有现成的存在。如果没有,那实现一个自己的协议是非常好的,正如我们为诗歌下载客户端做的那样。

 

Protocol Factories

因此每个连接需要一个自己的Portocol,而且这个Protocol是我们自己定义类的实例。由于我们会将创建连接的工作交给Twisted来完成,Twisted需要一种方式来为一个新的连接制定一个合适的协议。制定协议就是Protocol Factories的 工作了。

也许你已经猜到了,Protocol Factory的API由IProtocolFactory来定义,同样在interfaces模块中。Protocol Factory就是Factory模式的一个具体实现。buildProtocol方法在每次被调用时返回一个新Protocol实例。它就是Twisted用来为新连接创建新Protocol实例的方法。

 

诗歌下载客户端2.0:第一滴心血

好吧,让我们来看看由Twisted支持的诗歌下载客户端2.0。源码可以在这里twisted-client-2/get-poetry.py。你可以像前面一样运行它,并得到相同的输出。这也是最后一个在接收到数据时打印其任务的客户端版本了。到现在为止,对于所有Twisted程序都是交替执行任务并处理相对较少数量数据的,应该很清晰了。我们依然通过print函数来展示在关键时刻在进行什么内容,但将来客户端不会在这样繁锁。

在第二个版本中,sockets不会再出现了。我们甚至不需要引入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类的实例对象。这是一个专门针对诗歌下载客户端的Protocol Factory,将它传递给reactor可以让Twisted为我们创建一个PeotryProtocol实例。

值得注意的是,从一开始我们既没有实现Factory也没有去实现Protocol,不像在前面那个客户端中我们去实例化我们PoetrySocket类。我们只是继承了Twisted在twisted.internet.protocol 中提供的基类Factory的基类是twisted.internet.protocol.Factory但我们使用客户端专用(即不像服务器端那样监听一个连接,而是主动创建一个连接)的ClientFactory子类来继承。

我们同样利用了TwistedFactory已经实现了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的实现buildProtocol过程是:安装(创建一个实例)我们设置在protocol变量上的Protocol类与在这个实例(此处即PoetryProtocol的实例)的factory属性上设置一个产生它的Factory的引用(此处即实例化PoetryProtocol的PoetryClientFactory)。这个过程如图8所示:

第五部分:由Twisted支持的诗歌客户端
 

图8:Protocol的生成过程

正如我们提到的那样,位于Protocol对象内的factory属性字段允许在都由同一个factory产生的Protocol之间共享数据。由于Factories都是由用户代码来创建的(即在用户的控制中),因此这个属性也可以实现Protocol对象将数据传递回一开始初始化请求的代码中来,这将在第六部分看到。

值得注意的是,虽然在Protocol中有一个属性指向生成其的Protocol Factory,在Factory中也有一个变量指向一个Protocol类,但通常来说,一个Factory可以生成多个Protocol。

在Protocol创立的第二步便是通过makeConnection与一个Transport联系起来。我们无需自己来实现这个函数而使用Twisted提供的默认实现。默认情况是,makeConnection将Transport的一个引用赋给(Protocol的)transport属性,同时置(同样是Protocol的)connected属性为True,正如图9描述的一样:

第五部分:由Twisted支持的诗歌客户端
 

图9:Protocol遇到其Transport

一旦初始化到这一步后,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())

每次dateReceved被调用就意味着我们得到一个新字符串。由于与异步I/O交互,我们不知道能接收到多少数据,因此将接收到的数据缓存下来直到完成一个完整的协议规定格式的消息。在我们的例子中,诗歌只有在连接关闭时才下载完毕,因此我们只是不断地将接收到的数据添加到我们的.poem属性字段中。

注意我们使用了Transport的getHost方法来取得数据来自的服务器信息。我们这样做只是与前面的客户端保持一致。相反,我们的代码没有必要这样做,因为我们没有向服务器发送任何消息,也就没有必要知道服务器的信息了。

我们来看一下dataReceved运行时的快照。在2.0版本相同的目录下有一个twisted-client-2/get-poetry-stack.py。它与2.0版本的不同之处只在于:

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()看见没,有我们在1.0版本客户端的doRead回调函数。我们前面也提到过,Twisted在建立新抽象层进会使用已有的实现而不是另起炉灶。因此必然会有一个IReadDescriptor的实例在辛苦的工作,它是由Twisted代码而非我们自己的代码来实现。如果你表示怀疑,那么就看看twisted.internet.tcp中的实现吧。如果你浏览代码会发现,由同一个类实现了IWriteDescriptor与ITransport。因此 IreadDescriptor实际上就是变相的Transport类。可以用图10来形象地说明dateReceived的回调过程: 
 图10:dateReceived回调过程 
一旦诗歌下载完成,PoetryProtocol就会通知它的PooetryClientFactory: 
def connectionLost(self, reason):     
  self.poemReceived(self.poem) 
def poemReceived(self, poem):    
  self.factory.poem_finished(self.task_num, poem)

当transport的连接关闭时,conncetionLost回调会被激活。reason参数是一个twisted.python.failure.Failure的实例对象,其携带的信息能够说明连接是被安全的关闭还是由于出错被关闭的。我们的客户端因认为总是能完整地下载完诗歌而忽略了这一参数。

工厂会在所有的诗歌都下载完毕后关闭reactor。再次重申:我们代码的工作就是用来下载诗歌-这意味我们的PoetryClientFactory缺少复用性。我们将在下一部分修正这一缺陷。值得注意的是,poem_finish回调函数是如何通过跟踪剩余诗歌数的:

...

    self.poetry_count -= 1

 

    if self.poetry_count == 0:

        …

如果我们采用多线程以让每个线程分别下载诗歌,这样我们就必须使用一把锁来管理这段代码以免多个线程在同一时间调用poem_finish。但是在交互式体系下就不必担心了。由于reactor只能一次启用一个回调。

新的客户端实现在处理错误上也比先前的优雅的多,下面是PoetryClientFactory处理错误连接的回调实现代码:

def clientConnectionFailed(self, connector, reason):

    print 'Failed to connect to:', connector.getDestination()

    self.poem_finished()

注意,回调是在工厂内部而不是协议内部实现。由于协议是在连接建立后才创建的,而工厂能够在连接未能成功建立时捕获消息。


 

结束语:

版本2的客户端使用的抽象对于那些Twisted高手应该非常熟悉。如果仅仅是为在命令行上打印出下载的诗歌这个功能,那么我们已经完成了。但如果想使我们的代码能够复用,能够被内嵌在一些包含诗歌下载功能并可以做其它事情的大软件中,我们还有许多工作要做,我们将在第六部分讲解相关内容。

 

登录乐搏学院官网http://www.learnbo.com/

或关注我们的官方微博微信,还有更多惊喜哦~

转载于:https://my.oschina.net/learnbo/blog/838408

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/457198.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

**print('人生苦短 我爱Python')**

print(‘人生苦短 我爱Python’) 一、变量 **""" 1.代码自上而下执行 2_运算符和表达式.一行一句,不要把多个语句写到一行上,可读性不好 3中文只能出现在引号里,其他地方不能出现中文 4不能随意缩进 """**pr…

笔记本(华硕UL80VT)软件超频setFSB

Warning !!!If you are a beginner, do not use this software. This software is for power users only. Use "SetFSB.exe" at your own risk.试了setfsb各种版本,基本不能打开。还有官网的免费版,居然不能用,真是很奇怪。 官网&a…

Node.js~在linux上的部署

我们以centOS为例来说说如何部署node.js环境 一 打开centos,然后开始下载node.js包 curl --silent --location https://rpm.nodesource.com/setup_6.x | bash - yum -y install nodejs 二 安装gcc环境 yum install gcc-c make 安装完成! 三 安装nodejs的npm,这是一个包程序工具…

LeetCode题解-3-Longest Substring Without Repeating Characters

2019独角兽企业重金招聘Python工程师标准>>> 解题思路 首先要读懂题目,它要求的是找到最长的子串,并且子串中没有出现重复的字符。 我的想法,是用一个map存储每个字符最后出现的位置,还要有个变量start,它用…

java从哪学到哪_Java JVM怎么学习啊?从哪方面入手?

叮当猫咪一、 JVM的生命周期  1. JVM实例对应了一个独立运行的java程序它是进程级别  a) 启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点  b) 运行。m…

JMeter处理Cookie与Session

cookie 和session 的区别: 1、cookie数据存放在客户的浏览器上,session数据放在服务器上。 2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗 考虑到安全应当使用session。 3、session会在一定时间内保存在服务器上。当…

Maximum sum(poj 2479)

题意:给一段数列,将这个数列分成两部分,使两部分的最大子段和的和最大,输出和/*看数据没想到是(O)n的算法,求出从前向后的最大子段和和从后向前的最大子段和,然后枚举断点。 第一次提交不小心折在数组最小值…

蚂蚁分类信息系统 5.8 信息浏览量后台自定义设置

mymps 蚂蚁分类信息是一款基于PHPMySQL的建站系统,为在各种服务器上架设分类信息以及地方门户网站提供完美的解决方案. mymps5.8 下载 蚂蚁分类系统 5.8下载 蚂蚁分类系统下载 mymps下载 蚂蚁分类信息系统 5.8 原信息浏览量后台无法自定义,现增加后台自定义浏览量…

python编写四位数验证码

def verifycode(request):#引入绘图模块from PIL import Image, ImageDraw, ImageFont#引入随机函数模块import random#定义变量,用于画面的背景色、宽、高bgcolor (random.randrange(20, 100), random.randrange(20, 100), random.randrange(20, 100))width 100h…

php 计算数据偏离度,关于偏离度的测算方法

2015年6月技术总结——关于偏离度的测算方法研究院公用事业部 路璐引言《原理》中说“偏离度是指每一种偿债来源与财富创造能力的距离,所体现的是偿债来源对债务安全的保障程度,唯有通过揭示偿债来源与财富创造能力偏离度才能真正区别每一种偿债来源的风…

Django中celery配置总结

情景: 用户发起request,并等待response返回。在本些views中,可能需要执行一段耗时的程序,那么用户就会等待很长时间, 造成不好的用户体验,比如发送邮件、手机验证码等。 使用celery后,情况就不…

test.php.bak,MongoDB热备份工具:解决官方版备份缺陷

贺春旸,凡普金科DBA团队负责人,《MySQL管理之道:性能调优、高可用与监控》第一、二版作者,曾任职于中国移动飞信、安卓机锋网。致力于MariaDB、MongoDB等开源技术的研究,主要负责数据库性能调优、监控和架构设计。工具…

zookeeper工作原理、安装配置、工具命令简介

1 Zookeeper简介Zookeeper 是分布式服务框架,主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等等。 ZooKeeper是一个分布式的,开放源码的分布式应用程序…

流式大数据处理的三种框架:Storm,Spark和Samza

许多分布式计算系统都可以实时或接近实时地处理大数据流。本文将对三种Apache框架分别进行简单介绍,然后尝试快速、高度概述其异同。 Apache Storm 在Storm中,先要设计一个用于实时计算的图状结构,我们称之为拓扑(topology&#x…

linux_bash_shell_cheat_sheet(自译)

【说明】 发现错误或不足请务必联系我!!! linux_bash_shell_cheat_sheet.pdf (英文原本以及译本下载,链接失效请私信或邮箱联系) 转载于:https://www.cnblogs.com/15ho/p/5947534.html

热血街头Java,下载_我爱法语 V3.01 多国语言版_6z6z下载站

我爱法语是一款功能强大的法语电子词典工具。融合了法汉,汉法,法法,英法,法英,英汉,法意等各类词库。该工具使用灵活,操作简单,充分吸收了法汉、汉法、法法、英法、法英、英汉、法意…

BZOJ 1087 [SCOI2005]互不侵犯King ——状压DP

【题目分析】 沉迷水题&#xff0c;吃枣药丸。 【代码】 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define F(i,j,k) for (int ij;i<k;i) #define ll long long int cot[512],c1[512]…

java中为什么设计包装类,Java 中为什么要设计包装类

最近文章更新频率慢了&#xff0c;因为最近在准备暑期实习&#xff0c;之前寻思着一边复习一边写文章&#xff0c;两全其美。后来发现一篇读起来比较舒服的文章写出来加上配图得花上四五个小时甚至更多&#xff0c;但这个知识点我可能半个小时就能复习完了&#xff0c;春招在即…

Python的Django框架中forms表单类的使用方法详解

Form Form的验证思路 前端&#xff1a;form表单 后台&#xff1a;创建form类&#xff0c;当请求到来时&#xff0c;先匹配&#xff0c;匹配出正确和错误信息。 Django的Form验证实例&#xff1a; 创建project&#xff0c;进行基础配置文件配置 settings.py settings.py之…

生动形象的理解API是如何工作的!

API(Application Programming Interface,应用程序编程接口) 简单来说&#xff0c;就是其他人开发出来一块程序&#xff0c;你想用&#xff0c;他会告诉你调用哪个函数&#xff0c;给这个函数传什么参数&#xff0c;然后又会返回给你一个什么样的结果&#xff0c;你不需要知道他…