第一个twisted支持的诗歌服务器html
尽管Twisted大多数状况下用来写服务器代码,为了一开始尽可能从简单处着手,咱们首先从简单的客户端讲起。python
让咱们来试试使用Twisted的客户端。源码在twisted-client-1/get-poetry.py。首先像前面同样要开启三个服务器:react
python blocking-server/slowpoetry.py --port 10000 poetry/ecstasy.txt --num-bytes 30 python blocking-server/slowpoetry.py --port 10001 poetry/fascination.txt python blocking-server/slowpoetry.py --port 10002 poetry/science.txt
而且运行客户端:git
python twisted-client-1/get-poetry.py 10000 10001 10002
你会看到在客户端的命令行打印出:程序员
Task 1: got 60 bytes of poetry from 127.0.0.1:10000 Task 2: got 10 bytes of poetry from 127.0.0.1:10001 Task 3: got 10 bytes of poetry from 127.0.0.1:10002 Task 1: got 30 bytes of poetry from 127.0.0.1:10000 Task 3: got 10 bytes of poetry from 127.0.0.1:10002 Task 2: got 10 bytes of poetry from 127.0.0.1:10001 ... Task 1: 3003 bytes of poetry Task 2: 623 bytes of poetry Task 3: 653 bytes of poetry Got 3 poems in 0:00:10.134220
和咱们的没有使用Twisted的非阻塞模式客户端打印的内容接近。这并不奇怪,由于它们的工做方式是同样的。github
下面,咱们来仔细研究一下它的源代码。编程
注意:正如我在第一部分说到,咱们开始学习使用Twisted时会使用一些低层Twisted的APIs。这样作是为揭去Twisted的抽象层,这样咱们就能够从内向外的来学习Tiwsted。可是这就意味着,咱们在学习中所使用的APIs在实际应用中可能都不会见到。记住这么一点就行:前面这些代码只是用做练习,而不是写真实软件的例子。缓存
可民看到,首先建立了一组PoetrySocke
t
的实例。在PoetrySocket
初始化时
,其建立了一个网络
socket
做为本身的属性字段来链接服务器,而且选择了非阻塞模式:
服务器
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect(address) self.sock.setblocking(0)
最终咱们虽然
会提升到不使用
socket
的抽象层次上,但这里咱们仍然须要使用它。在建立完
socket
后,
PoetrySocket
经过方法
addReader
将本身传递给
reactor
:
网络
# tell the Twisted reactor to monitor this socket for reading from twisted.internet import reactor reactor.addReader(self)
这个方法
给
Twisted
提供了一个文件描述符来监视要发送来的数据。为何咱们不传递给
Twisted
一个文件描述符或回调函数而是一个对象实例?而且
Twisted
内部没有任何与这个诗歌服务相关的代码,它怎么知道该如何与咱们的对象实例交互?相信我,我已经查看过了,打开
twisted.internet.interfaces
模块,和我一块儿来搞清楚是怎么回事。
Twisted
接口
在
twisted
内部有不少被称做
接口
的子模块。每一个都定义了一组接口类。因为在
8.0
版本中,
Twisted
使用
zope.interface
做为这些类的基类。但咱们这里并不来讨论它其中的细节。咱们只关心其在
Twisted
的子类,就是你看到的那些。
使用接口
的核心目的之一就是文档化。做为一个
python
程序员,你确定知道
Duck Typing
。(说实话我还真不懂这种编程,但经过查看资料,其实就是动态编程的思想,根据你的动做来肯定你的类型)
翻阅
twisted.internet.interfaces
找到方法的
addReader
定义,它的定义在
IReactorFDSet
中能够找到:
def addReader(reader): """ I add reader to the set of file descriptors to get read events for. @param reader: An L{IReadDescriptor} provider that will be checked for read events until it is removed from the reactor with L{removeReader}. @return: C{None}. """
IReactorFDSet
是一个
Twisted
的
reactor
实现的接口。所以任何一个
Twisted
的
reactor
都会一个
addReader
的方法,如同上面描述的同样工做。这个方法声明之因此没有
self
参数是由于它仅仅关心一个公共接口定义,
self
参数仅仅是接口实现时的一部分(在调用它时,也没有显
式地
传入一个
self
参数)。接口类永远不会被实例化或做为基类来继承实现。
注意
1
:技术上讲,
IReactorFDSet
只会由
reactor
实现用来监听文件描述符。具我所知,如今全部已实现
reactor
都会实现这个接口。
注意
2
:使用接口并不只仅是为了文档化。
zope.interface
容许你显式地来声明一个类实现一个或多个接口,并提供运行时检查这些实现的机制。一样也提供代理这一机制,它能够动态地为一个没有实现某接口的类直接提供该接口。但咱们这里就不作深刻学习了。
注意
3
:你可能已经注意到接口与最近添加到
Python
中虚基类的类似性了。这里咱们并不去分析它们之间的类似性与差别。若你有兴趣,能够读读
Ptyhon
项目的创始人
Glyph
写的一篇关于这个话题的文章。
根据文档的描述能够看出,
addReader
的
reader
参数是要实现
IreadDescriptor
接口的。这也就意味咱们的
PoetrySocket
也必须这样作。
阅读接口模块咱们能够看到下面这段代码:
class IReadDescriptor(IFileDescriptor): def doRead(): """ Some data is available for reading on your descriptor. """
同时
你会看到在咱们的
PoetrySocket
类中有一个
doRead
方法。
当其被
Twisted
的
reactor
调用时,就会采用异步的方式从
socket
中读取数据。所以,
doRead
其实就是一个回调函数,只是没有直接将其传递给
reactor
,而是传递一个实现此方法的对象实例。这也是
Twisted
框架中的惯例—不是直接传递实现某个接口的函数而是传递实现它的对象。这样咱们经过一个参数就能够传递一组相关的回调函数。并且也可让回调函数之间经过存储在对象中的数据进行通讯。
那在
PoetrySocket
中实现其它的回调函数呢?注意到
IReadDescriptor
是
IFileDescriptor
的一个子类。这也就意味任何一个实现
IReadDescriptor
都必须实现
IFileDescriptor
。如果你仔细阅读代码会看到下面的内容:
class IFileDescriptor(ILoggingContext): """ A file descriptor. """ def fileno(): ... def connectionLost(reason): …
我
将文档描述
省略掉
了,但这些函数的功能从字面上就能够理解:
fileno
返回咱们想监听的文件描述符,
connectionLost
是当链接关闭时被调用。你也看到了,
PoetrySocket
实现了这些方法。
最后,
IFileDescriptor
继承了
ILooggingContext
,这里我不想再展示其源码。我想说的是,这就是为何咱们要实现一个
logPrefix
回调函数。你能够在
interface
模块中找到答案。
注意
:你也许注意到了,当链接关闭时,在
doRead
中返回了一个特殊的值。我是如何知道的?说实话,没有它程序是没法正常工做的。我是在分析
Twisted
源码中发现其它相应的方法采起相同的方法。你也许想好好研究一下:但有时一些文档或书的解释是错误的或不完整的。所以可能当你搞清楚怎么回事时,咱们已经完成第五部分了呵呵。
更多关于回调的知识
咱们使用
Twisted
的异步客户端和前面的没有使用
Twisted
的异步客户很是的类似。二者都要链接它们本身的
socket
,并以异步的方式从中读取数据。最大的区别在于:使用
Twisted
的客户端并无使用本身的
select
循环
-
而使用了
Twisted
的
reactor
。
doRead
回调函数是很是重要的一个回调。
Twisted
调用它来告诉咱们已经有数据在
socket
接收完毕。我能够经过图
7
来形象地说明这一过程:
图
7 doRead
回调过程
每当回调
被激活,就轮到咱们的代码将全部可以读的数据读回来而后非阻塞式的中止。正如咱们第三部分说的那样,
Twisted
是不会由于什么异常情况(如没有必要的阻塞)而终止咱们的代码。那么咱们就故意写个会产生异常情况的客户端看看到底能发生什么事情。能够在
twisted-client-1/get-poetry-broken.py中看到源代码。这个客户端与你前面看到的一样有两个异常情况出现:
1.
这个客户端并不没有选择非阻塞式的
socket
2.doRead
回调方法在
socket
关闭链接前一直在不停地读
socket
如今让咱们运行一下这个客户端:
python twisted-client-1/get-poetry-broken.py 10000 10001 10002
咱们出获得如同下面同样的输出:
Task 1: got 3003 bytes of poetry from 127.0.0.1:10000 Task 3: got 653 bytes of poetry from 127.0.0.1:10002 Task 2: got 623 bytes of poetry from 127.0.0.1:10001 Task 1: 3003 bytes of poetry Task 2: 623 bytes of poetry Task 3: 653 bytes of poetry Got 3 poems in 0:00:10.132753
可能
除了任务的完成顺序不太一致外,和我先阻塞式客户端是同样的。这是由于这个客户端是一个阻塞式的。
因为使用
了阻塞式的链接,就将咱们的非阻塞式客户端变成了阻塞式的客户端。这样一来,咱们尽管遭受了使用
select
的复杂但却没有享受到其带来的异步优点。
像诸如
Twisted
这样的事件循环所提供的多任务的能力是须要用户的合做来实现的。
Twisted
会告诉咱们何时读或写一个文件描述符,但咱们必需要尽量高效而没有阻塞地完成读写工做。一样咱们应该禁止使用其它各种的阻塞函数,如
os.system
中的函数。除此以外,当咱们遇到计算型的任务(长时间占用
CPU
),最好是将任务切成若干个部分执行以让
I/O
操做尽量地执行。
你也许已经注意到这个客户端所花费的时间少于先前那个阻塞的客户端。这是因为这个在一开始就与全部的服务创建链接,因为服务是一旦链接创建就当即发送数据,并且咱们的操做系统会缓存一部分发送过来但尚读不到的数据到缓冲区中(缓冲区大小是有上限的)。所以就明白了为何前面那个会慢了:它是在完成一个后再创建下一个链接并接收数据。
但这种小优点仅仅在小数据量的状况下才会得以体现。若是咱们下载三首
20M
个单词的诗,那时
OS
的缓冲区会在瞬间填满,这样一来咱们这个客户端与前面那个阻塞式客户端相比就没有什么优点可言了。
结束语
我没有过多地解释此部分第一个客户端的内容。你可能注意到了,
connectionLost
函数会在没有
PoetrySocket
等待诗歌后关闭
reactor
。因为咱们的程序除了下载诗歌不提供其它服务,因此才会这样作。但它揭示了两个低层
reactor
的
APIs
:
removeReader
和
getReaders
。
还有与咱们客户端使用的
Readers
的
APIs
类同的
Writers
的
APIs
,它们采用相同的方式来监视咱们要发送数据的文件描述符。能够经过阅读
interfaces
文件来获取更多的细节。读和写有各自的
APIs
是由于
select
函数须要分开这两种事件(读或写能够进行的文件描述符)。固然了,能够等待即能读也能写的文件描述符。
第五部分,咱们将使用
Twisted
的高层抽象方式实现另一个客户端,而且学习更多的
Twisted
的接口与
APIs
。