Maison > Article > développement back-end > Un exemple de tutoriel sur la création d'un programme de téléchargement non bloquant à l'aide du framework Twisted de Python
Le premier serveur de poésie supporté par twisted
Bien que Twisted soit principalement utilisé pour écrire du code serveur, afin de démarrer le plus simplement possible dès le début, nous commencerons par un simple client.
Essayons d'utiliser le client de Twisted. Le code source est dans twisted-client-1/get-poetry.py. Tout d'abord, ouvrez les trois serveurs comme avant :
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
et exécutez le client :
python twisted-client-1/get-poetry.py 10000 10001 10002
Vous verrez la commande du client impressions en ligne :
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
ce qui est proche de ce que notre client en mode non bloquant sans impression Twisted. Ce n’est pas surprenant puisqu’ils fonctionnent de la même manière.
Maintenant, regardons de plus près son code source.
Remarque : lorsque nous commencerons à apprendre à utiliser Twisted, nous utiliserons des API Twisted de bas niveau. Ceci est fait pour supprimer la couche d'abstraction de Twisted afin que nous puissions apprendre Tiwsted de l'intérieur. Mais cela signifie que les API que nous utilisons dans l’apprentissage peuvent ne pas être visibles dans les applications réelles. N'oubliez pas ceci : le code précédent n'est qu'un exercice, pas un exemple d'écriture d'un vrai logiciel.
Comme vous pouvez le voir, un ensemble d'instances PoetrySocket est d'abord créé. Lorsque PoetrySocket est initialisé, il crée une socket réseau comme son propre champ d'attribut pour se connecter au serveur et sélectionne le mode non bloquant :
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect(address) self.sock.setblocking(0)
Finalement, nous améliorerons pour ne pas utiliser C'est le niveau d'abstraction du socket, mais nous devons toujours l'utiliser ici. Après avoir créé le socket, PoetrySocket se transmet au réacteur via la méthode addReader :
# tell the Twisted reactor to monitor this socket for reading from twisted.internet import reactor reactor.addReader(self)
Cette méthode fournit à Twisted un descripteur de fichier pour surveiller les données à envoyer. Pourquoi ne transmettons-nous pas à Twisted un descripteur de fichier ou une fonction de rappel mais une instance d'objet ? Et il n'y a pas de code dans Twisted lié à ce service de poésie, alors comment sait-il comment interagir avec notre instance d'objet ? Croyez-moi, je l'ai vérifié, ouvrez le module twisted.internet.interfaces et rejoignez-moi pour comprendre ce qui se passe.
Interface Twisted
Il existe de nombreux sous-modules appelés interfaces à l'intérieur de Twisted. Chacun définit un ensemble de classes d'interface. Depuis la version 8.0, Twisted utilise zope.interface comme classe de base pour ces classes. Mais nous ne discuterons pas des détails ici. Nous ne nous intéressons qu'à ses sous-classes Twisted, celles que vous voyez ici.
L'un des objectifs principaux de l'utilisation des interfaces est la documentation. En tant que programmeur Python, vous devez connaître Duck Typing. (Philosophie Python : "S'il ressemble à un canard, s'il sonne comme un canard, vous pouvez le traiter comme un canard." Par conséquent, l'interface des objets Python s'efforce d'être simple et unifiée, similaire aux idées de programmation orientées interface dans d'autres langues.) Lire twisted.internet .interfaces trouve la définition addReader de la méthode, et sa définition peut être trouvée dans 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 est une interface implémentée par le réacteur de Twisted. Par conséquent, tout réacteur Twisted aura une méthode addReader, qui fonctionne comme décrit ci-dessus. La raison pour laquelle cette déclaration de méthode n'a pas de paramètre self est qu'elle ne se soucie que de la définition d'une interface publique et que le paramètre self n'est qu'une partie de l'implémentation de l'interface (lors de son appel, un paramètre self n'est pas explicitement transmis). Les classes d'interface ne sont jamais instanciées ou implémentées en tant que classes de base.
Techniquement parlant, IReactorFDSet ne sera utilisé par Reactor que pour écouter les descripteurs de fichiers. Autant que je sache, tous les réacteurs implémentés implémentent désormais cette interface.
L'utilisation des interfaces ne sert pas uniquement à la documentation. zope.interface vous permet de déclarer explicitement qu'une classe implémente une ou plusieurs interfaces et fournit un mécanisme pour vérifier ces implémentations au moment de l'exécution. Il fournit également un mécanisme proxy, qui peut fournir dynamiquement une classe qui n'implémente pas d'interface directement avec l'interface. Mais nous ne ferons pas ici une étude approfondie.
Vous avez peut-être remarqué la similitude entre les interfaces et les classes de base virtuelles récemment ajoutées à Python. Nous n’analyserons pas ici les similitudes et les différences entre eux. Si vous êtes intéressé, vous pouvez lire un article sur ce sujet rédigé par Glyph, le fondateur du projet Python.
D'après la description du document, on peut voir que le paramètre reader de addReader consiste à implémenter l'interface IReadDescriptor. Cela signifie que notre PoetrySocket doit faire de même.
En lisant le module d'interface, nous pouvons voir le code suivant :
class IReadDescriptor(IFileDescriptor): def doRead(): """ Some data is available for reading on your descriptor. """
En même temps, vous verrez qu'il existe une méthode doRead dans notre classe PoetrySocket. Lorsqu'il est appelé par le réacteur de Twisted, il lit les données du socket de manière asynchrone. Par conséquent, doRead est en fait une fonction de rappel, mais elle n'est pas transmise directement au réacteur, mais une instance d'objet qui implémente cette méthode est transmise. C'est également la convention dans le framework Twisted : au lieu de transmettre directement la fonction qui implémente une interface, vous transmettez l'objet qui l'implémente. De cette façon, nous pouvons transmettre un ensemble de fonctions de rappel associées via un paramètre. Et il est également possible de communiquer entre les fonctions de rappel via les données stockées dans l'objet.
Que diriez-vous d'implémenter d'autres fonctions de rappel dans PoetrySocket ? Notez que IReadDescriptor est une sous-classe de IFileDescriptor. Cela signifie que toute personne qui implémente IReadDescriptor doit implémenter IFileDescriptor. Si vous lisez attentivement le code, vous verrez ce qui suit :
class IFileDescriptor(ILoggingContext): """ A file descriptor. """ def fileno(): ... def connectionLost(reason): …
我将文档描述省略掉了,但这些函数的功能从字面上就可以理解:fileno返回我们想监听的文件描述符,connectionLost是当连接关闭时被调用。你也看到了,PoetrySocket实现了这些方法。
最后,IFileDescriptor继承了ILoggingContext,这里我不想再展现其源码。我想说的是,这就是为什么我们要实现一个logPrefix回调函数。你可以在interface模块中找到答案。
注意:你也许注意到了,当连接关闭时,在doRead中返回了一个特殊的值。我是如何知道的?说实话,没有它程序是无法正常工作的。我是在分析Twisted源码中发现其它相应的方法采取相同的方法。你也许想好好研究一下:但有时一些文档或书的解释是错误的或不完整的。
更多关于回调的知识
我们使用Twisted的异步客户端和前面的没有使用Twisted的异步客户非常的相似。两者都要连接它们自己的socket,并以异步的方式从中读取数据。最大的区别在于:使用Twisted的客户端并没有使用自己的select循环-而使用了Twisted的reactor。 doRead回调函数是非常重要的一个回调。Twisted调用它来告诉我们已经有数据在socket接收完毕。我可以通过图7来形象地说明这一过程:
每当回调被激活,就轮到我们的代码将所有能够读的数据读回来然后非阻塞式的停止。Twisted是不会因为什么异常状况(如没有必要的阻塞)而终止我们的代码。那么我们就故意写个会产生异常状况的客户端看看到底能发生什么事情。可以在twisted-client-1/get-poetry-broken.py中看到源代码。这个客户端与你前面看到的同样有两个异常状况出现:
这个客户端并没有选择非阻塞式的socket
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的缓冲区会在瞬间填满,这样一来我们这个客户端与前面那个阻塞式客户端相比就没有什么优势可言了。
Construire le client de manière abstraite
Tout d'abord, ce client a en fait un code aussi ennuyeux que la création d'un port réseau et la réception de données sur le port. Twisted est censé implémenter ces fonctions de routine pour nous, nous évitant ainsi d'avoir à les implémenter nous-mêmes à chaque fois que nous écrivons un nouveau programme. Ceci est particulièrement utile, car cela nous libère de certaines des manipulations délicates des exceptions impliquées dans les E/S asynchrones (voir le client précédent), qui impliquent des détails plus délicats si l'on veut que ce soit multiplateforme. Si vous avez du temps libre un après-midi, vous pouvez consulter le code source d'implémentation WIN32 de Twisted et voir combien de petits threads il existe pour gérer plusieurs plates-formes.
Un autre problème est lié à la gestion des erreurs. Lorsque le client Twisted exécutant la version 1 télécharge de la poésie à partir d'un port qui n'est pas desservi, il plante. Bien sûr, nous pouvons corriger cette erreur, mais il est plus facile de gérer ces types d'erreurs via les API de Twisted, que nous présenterons ci-dessous.
Enfin, ce client ne peut pas être réutilisé. Et s'il y avait un autre module qui devait télécharger de la poésie via notre client ? Comment les gens savent-ils que vos poèmes ont été téléchargés ? Nous ne pouvons pas utiliser une méthode pour simplement télécharger un poème et ensuite le transmettre à d'autres, mais les laisser auparavant dans un état d'attente. Il s’agit effectivement d’un problème, mais nous n’allons pas l’aborder dans cette section – il le sera certainement dans une prochaine section.
Nous utiliserons des API et des interfaces de haut niveau pour résoudre les premier et deuxième problèmes. Le framework Twisted est vaguement composé de nombreuses couches d'abstraction. Par conséquent, apprendre Twisted signifie également apprendre quelles fonctions fournissent ces couches, telles que les API, interfaces et instances disponibles pour chaque couche. Ensuite, nous analyserons les parties les plus importantes de Twisted pour avoir une meilleure idée de la façon dont Twisted est organisé. Une fois que vous serez familiarisé avec la structure globale de Twisted, l’apprentissage de nouvelles parties sera beaucoup plus facile.
En général, chaque abstraction Twisted est liée à un seul concept spécifique. Par exemple, le client de la partie 4 utilise IReadDescriptor, qui est une abstraction d'un « descripteur de fichier capable de lire des octets ». Une abstraction spécifie souvent le comportement des objets qui souhaitent implémenter l'abstraction (c'est-à-dire implémenter l'interface) en définissant une interface. Lors de l'apprentissage de nouveaux concepts d'abstraction Twisted, la chose la plus importante à retenir est la suivante :
La plupart des abstractions de haut niveau sont construites sur la base d'abstractions de bas niveau, et peu sont indépendantes.
Ainsi, lorsque vous apprenez une nouvelle abstraction Twisted, rappelez-vous toujours ce qu'elle fait et ne fait pas. En particulier, si une première abstraction A implémente la fonctionnalité F, il est peu probable que la fonctionnalité F soit implémentée par une autre abstraction. De plus, si une autre abstraction nécessite la fonctionnalité F, elle utilisera A au lieu d’implémenter F elle-même. (Habituellement, B peut hériter de A ou obtenir une référence à une instance de A)
Le réseau est très complexe, donc Twisted contient de nombreux concepts abstraits. En commençant par une abstraction de bas niveau, nous espérons voir plus clairement comment sont organisées les différentes parties d’un programme Twisted.
Core Loop Body
La première abstraction que nous devons apprendre, et la plus importante dans Twisted, est le réacteur. Au centre de chaque programme construit via Twisted, quel que soit le nombre de couches de votre programme, il y aura toujours une boucle de réacteur qui pilote le programme en continu. Il n’y a pas de support plus basique que le réacteur. En fait, les autres parties de Twisted (c'est-à-dire, à l'exception de la boucle du réacteur) peuvent être comprises de cette façon : elles sont toutes destinées à aider X à mieux utiliser le réacteur, où X peut être pour fournir des pages Web, traiter une requête de requête de base de données, ou tout autre contenu plus spécifique. Bien qu'il soit possible d'insister sur l'utilisation des API de bas niveau comme le client précédent, si nous insistons pour le faire, nous devrons alors implémenter nous-mêmes une grande partie du contenu. Et à un niveau supérieur, cela signifie que nous pouvons écrire beaucoup moins de code.
Mais lorsqu’on réfléchit et traite les problèmes de l’extérieur, il est facile d’oublier l’existence du réacteur. Dans tout programme Twisted de toute taille courante, il y aura en effet très peu d'interactions directes avec les API des réacteurs. Il en va de même pour les abstractions de bas niveau (c'est-à-dire que nous interagissons rarement directement avec elles). L'abstraction du descripteur de fichier que nous avons utilisée dans le client précédent est si bien résumée par des abstractions de niveau supérieur que nous la rencontrons rarement dans les vrais programmes Twisted. (Ils sont toujours utilisés en interne, nous ne pouvons tout simplement pas le voir)
Quant au message d'abstraction du descripteur de fichier, ce n'est pas un problème. Laissez Twisted prendre la tête du traitement des E/S asynchrones afin que nous puissions nous concentrer davantage sur le problème que nous essayons réellement de résoudre. Mais c'est différent avec le réacteur, ça ne disparaîtra jamais. Lorsque vous choisissez d'utiliser Twisted, cela signifie que vous choisissez d'utiliser le modèle Reactor, et cela signifie que vous avez besoin d'une programmation « interactive » utilisant des rappels et le multitâche.
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
因此每个连接需要一个自己的Protocol,而且这个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为我们创建一个PoetryProtocol实例。
值得注意的是,从一开始我们既没有实现Factory也没有去实现Protocol,不像在前面那个客户端中我们去实例化我们PoetrySocket类。我们只是继承了Twisted在twisted.internet.protocol 中提供的基类。Factory的基类是twisted.internet.protocol.Factory,但我们使用客户端专用(即不像服务器端那样监听一个连接,而是主动创建一个连接)的ClientFactory子类来继承。
我们同样利用了Twisted的Factory已经实现了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)。这个过程如图
正如我们提到的那样,位于Protocol对象内的factory属性字段允许在都由同一个factory产生的Protocol之间共享数据。由于Factories都是由用户代码来创建的(即在用户的控制中),因此这个属性也可以实现Protocol对象将数据传递回一开始初始化请求的代码中来,这将在第六部分看到。
值得注意的是,虽然在Protocol中有一个属性指向生成其的Protocol Factory,在Factory中也有一个变量指向一个Protocol类,但通常来说,一个Factory可以生成多个Protocol。
在Protocol创立的第二步便是通过makeConnection与一个Transport联系起来。我们无需自己来实现这个函数而使用Twisted提供的默认实现。默认情况是,makeConnection将Transport的一个引用赋给(Protocol的)transport属性,同时置(同样是Protocol的)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())
每次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的回调过程:
一旦诗歌下载完成,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()
注意,回调是在工厂内部而不是协议内部实现。由于协议是在连接建立后才创建的,而工厂能够在连接未能成功建立时捕获消息。
更多Un exemple de tutoriel sur la création dun programme de téléchargement non bloquant à laide du framework Twisted de Python相关文章请关注PHP中文网!