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

作者:[email protected]://krondo.com/?p=1445  译者:杨晓伟(采用意译)

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

第一个twisted支持的诗歌服务器

尽管Twisted大多数情况下用来写服务器代码,为了一开始尽量从简单处着手,我们首先从简单的客户端讲起。

让我们来试试使用Twisted的客户端。源码在twisted-client-1/get-poetry.py。首先像前面一样要开启三个服务器:

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

并且运行客户端:

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的非阻塞模式客户端打印的内容接近。这并不奇怪,因为它们的工作方式是一样的。

下面,我们来仔细研究一下它的源代码。

注意:正如我在第一部分说到,我们开始学习使用Twisted时会使用一些低层TwistedAPIs。这样做是为揭去Twisted的抽象层,这样我们就可以从内向外的来学习Tiwsted。但是这就意味着,我们在学习中所使用的APIs在实际应用中可能都不会见到。记住这么一点就行:前面这些代码只是用作练习,而不是写真实软件的例子。

可民看到,首先创建了一组PoetrySocket的实例。在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是一个Twistedreactor实现的接口。因此任何一个Twistedreactor都会一个 addReader的方法,如同上面描述的一样工作。这个方法声明之所以没有self参数是因为它仅仅关心一个公共接口定义,self参数仅仅是接口实现时的一部分(在调用它时,也没有显式地传入一个self参数)。接口类永远不会被实例化或作为基类来继承实现。

注意1:技术上讲,IReactorFDSet只会由reactor实现用来监听文件描述符。具我所知,现在所有已实现reactor都会实现这个接口。

注意2:使用接口并不仅仅是为了文档化。zope.interface允许你显式地来声明一个类实现一个或多个接口,并提供运行时检查这些实现的机制。同样也提供代理这一机制,它可以动态地为一个没有实现某接口的类直接提供该接口。但我们这里就不做深入学习了。

注意3:你可能已经注意到接口与最近添加到Python中虚基类的相似性了。这里我们并不去分析它们之间的相似性与差异。若你有兴趣,可以读读Ptyhon项目的创始人Glyph写的一篇关于这个话题的文章

根据文档的描述可以看出,addReaderreader参数是要实现IreadDescriptor接口的。这也就意味我们的PoetrySocket也必须这样做。

阅读接口模块我们可以看到下面这段代码:

class IReadDescriptor(IFileDescriptor):

    def doRead():
        """
        Some data is available for reading on your descriptor.
        """

同时你会看到在我们的PoetrySocket类中有一个doRead方法。当其被Twistedreactor调用时,就会采用异步的方式从socket中读取数据。因此,doRead其实就是一个回调函数,只是没有直接将其传递给reactor,而是传递一个实现此方法的对象实例。这也是Twisted框架中的惯例—不是直接传递实现某个接口的函数而是传递实现它的对象。这样我们通过一个参数就可以传递一组相关的回调函数。而且也可以让回调函数之间通过存储在对象中的数据进行通信。

那在PoetrySocket中实现其它的回调函数呢?注意到IReadDescriptorIFileDescriptor的一个子类。这也就意味任何一个实现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循环-而使用了Twistedreactor

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。由于我们的程序除了下载诗歌不提供其它服务,所以才会这样做。但它揭示了两个低层reactorAPIsremoveReadergetReaders

还有与我们客户端使用的ReadersAPIs类同的WritersAPIs,它们采用相同的方式来监视我们要发送数据的文件描述符。可以通过阅读interfaces文件来获取更多的细节。读和写有各自的APIs是因为select函数需要分开这两种事件(读或写可以进行的文件描述符)。当然了,可以等待即能读也能写的文件描述符。

第五部分,我们将使用Twisted的高层抽象方式实现另外一个客户端,并且学习更多的Twisted的接口与APIs

时间: 2024-10-12 02:28:19

第四部分:由Twisted支持的诗歌客户端的相关文章

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

作者:[email protected]http://krondo.com/?p=1522  译者:杨晓伟(采用意译) 你可以从这里从头开始阅读这个系列 抽象地构建客户端 在第四部分中,我们构建了第一个使用Twisted的客户端.它确实能很好地工作,但仍有提高的空间. 首先是,这个客户端竟然有创建网络端口并接收端口处的数据这样枯燥的代码.Twisted理应为我们实现这些例程性功能,省得我们每次写一个新的程序时都要去自己实现.Twisted这样做也将我们从像异步I/O操作中包括许多像异常处理这样的

四、配置nginx支持php

                                四.配置nginx支持php     在Ubuntu下搭建LNMP环境.编译安装mysql,nginx,php.最后在LNMP前提下安装composer,并且安装laravel框架.第四步,配置nginx支持php. 首先建立存放网页文件的目录,执行"sudo mkdri /usr/local/server/www".然后进入到该目录中,"cd/usr/local/server/www". 修改ngin

企业运维之域控篇(十四)-域共享文件(服务端&客户端)设置

在公司我们这类杂工最多面对的也就是文件共享服务器.....这个是公司的重中之重,希望公司的领导与我们这类的杂工能够关注:免得一失足成千古恨!!!! 共享文件的作用:主要是在方便大家共同拥有. 共享服务器里的文件只能是暂时存放,而不是让它成为永久的仓库... 可能是人的懒性吧,所以每个公司的员工都是喜欢直接在共享文件里进行工作操作(如:编辑等等),其实这个是最点服务器资源与危险的事情. eg: 当你编辑好文件,保存后才发觉原来改错了,要恢复?那真是有些悲剧了(自己找不到需要的资料,同时也造成同事不

DATASNAP中间件支持安卓手机客户端

DATASNAP的中间件不仅支持WINDOWS客户端调用,也支持安卓手机,苹果手机客户端调用,当然也包括各种平板客户端调用. 咏南DATASNAP中间件支持安卓手机客户端.测试环境:DELPHI XE8编译,手机系统是安卓5.1,界面使用FIREMONKEY. DELPHI编写手机客户端的注意事项: 1)客户端必须 uses IPPeerClient,否则连接中间件的时候会报错: 2)不再需要USES MIDASLIB:在工程里面引用反而会报错: 3)中间件的远程方法参数不支持WIDESTRIN

javaIO流(四)--输入与输出支持

一.打印流 如果现在要想通过程序实现内容的输出,核心的本质一定要依靠OutputStream类来支持但是OutputStream类有一个最大的缺点,这个类的数据输出操作功能有限,所有的数据一定要转为字节数组后才可以进行才操作:public void write(byte b[]) throws IOException,假设说项目中可能输出的是long,double,date,在这样的情况下就必须将这些数据转变为字节的形式来进行处理,这样的处理一定是非常麻烦的.所以在开发之中最初的时候为了解决此类

Dynamic CRM 2013学习笔记(四十四)CRM技术支持

有时我们经常遇到一些CRM的问题,一时又无法解决,这时我们可能要找下外援,下面列出一些基本的技术支持.   1. CRM 论坛 https://community.dynamics.com/crm/f/117.aspx   2. Office 365       3. CRM自带的帮助   4. 微软官方支持 http://www.microsoft.com/dynamics/customer/en-US/service-plans.aspx   5. 第三方合作者 http://dynamic

第四节:设计支持加载项的应用程序

构建可扩展的应用程序时,接口是中心.可用基类来代替接口,但接口通常是首选的,因为它允许加载项开发人员选择他们自己的基类.例如,假如你要写一个应用程序,它能无缝的加载和使用别人创建的类型.下面描述了如何设计这样的应用程序. 创建一个“宿主SDK”(Host SDK)程序集,它定义了一个接口,接口的方法作为宿主应用程序和加载项之间的通信机制使用.为接口方法定义参数和返回值时,尝试使用MSCorLib.dll中定义的其他接口或类型.如果要传递并返回自己定义的数据类型,也在宿主的SDK程序集中定义他们.

实体框架 (EF) 入门 => 四、CodeFirst 枚举支持

当使用 Code First 开发时,通常是从编写用来定义概念(域)模型的 .NET Framework 类开始. 插入记录没有为 Budget 赋值. 数值类型默认值为0,数据库中都为not null,如果不设置Requird特性,可以不赋值,保存时自动使用默认值. 默认值是保存时EF在初始化类时赋给的. Enum类型数据库字段类型为int,保存Enum类型对应的序号,使用时显示相应的字符. 枚举类型并不会映射到数据库中.

四种有能力取代Cookies的客户端Web存储方案

目前在用户的网络浏览器中保存大量数据需要遵循几大现有标准,每一种标准都拥有自己的优势.短板.独特的W3C标准化状态以及浏览器支持级别.但无论如何,这些标准的实际表现都优于广泛存在的cookies机制. 今天的Web应用程序开始在客户端中执行大量数据处理工作,甚至可能需要以脱机方式完成任务.可以说,客户端数据存储对于下一代Web应用程序的发展起到了至关重要的作用. 然而直到现在,cookies仍然是用户浏览器中最常见的数据存储机制.如果一款Web应用需要重复访问某些数据,则只有两种方式可供选择:要