Python の Twisted フレームワークを使用したノンブロッキング ダウンロード プログラムの構築に関するチュートリアルの例

高洛峰
リリース: 2017-02-03 16:42:58
オリジナル
1292 人が閲覧しました

Twisted がサポートする最初の詩サーバー
ほとんどの場合、サーバー コードを記述するために Twisted が使用されますが、最初からできるだけシンプルに始めるために、まず単純なクライアントから始めます。
Twistedのクライアントを使ってみましょう。ソース コードはTwisted-client-1/get-poetry.py にあります。まず、前と同じように 3 つのサーバー

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 の使用方法を学習し始めるときは、低レベルの Twisted API をいくつか使用します。これは、Twisted の抽象化レイヤーを削除して、Tiwsted を徹底的に学習できるようにするために行われます。しかしこれは、学習で使用する API が実際のアプリケーションでは見られない可能性があることを意味します。これは覚えておいてください。上記のコードは単なる演習であり、実際のソフトウェアを作成する例ではありません。
ご覧のとおり、PoetrySocket インスタンスのセットが最初に作成されます。 PoetrySocket が初期化されると、サーバーに接続するための独自の属性フィールドとしてネットワーク ソケットを作成し、ノンブロッキング モードを選択します:

self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect(address)
self.sock.setblocking(0)
ログイン後にコピー

最終的にはソケットを使用しない抽象化レベルに改善しますが、ここでは私たちはまだそれを使用する必要があります。ソケットを作成した後、PoetrySocket は addReader メソッドを通じて自身をリアクターに渡します:

# 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 サブクラスだけです。
インターフェイスを使用する主な目的の 1 つはドキュメント化です。 Python プログラマーとして、ダック タイピングを知っておく必要があります。 (Python の哲学: 「アヒルのように見え、アヒルのように聞こえるなら、アヒルのように扱うことができます。」 したがって、Python オブジェクトのインターフェイスは、他の言語におけるインターフェイス指向のプログラミングのアイデアと同様に、シンプルで統一されるよう努めています。 )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 リアクターによって実装されたインターフェイスです。したがって、どの Twisted リアクターにも addReader メソッドがあり、これは上で説明したように機能します。このメソッド宣言に self パラメーターがない理由は、このメソッドが考慮するのはパブリック インターフェイス定義だけであり、self パラメーターはインターフェイス実装の一部にすぎないためです (呼び出し時に、self パラメーターは明示的に渡されません)。インターフェイス クラスが基本クラスとしてインスタンス化または実装されることはありません。

技術的に言えば、IReactorFDSet は、reactor がファイル記述子をリッスンするためにのみ使用されます。私の知る限り、実装されているすべてのリアクターはこのインターフェイスを実装しています。
インターフェースの使用はドキュメント化のためだけではありません。 zope.interface を使用すると、クラスが 1 つ以上のインターフェイスを実装していることを明示的に宣言し、実行時にこれらの実装をチェックするメカニズムを提供できます。また、インターフェイスを直接実装していないクラスにインターフェイスを動的に提供できるプロキシ メカニズムも提供します。ただし、ここでは詳細な調査は行いません。
最近 Python に追加されたインターフェイスと仮想基本クラスとの類似性に気づいたかもしれません。ここではそれらの類似点と相違点を分析しません。興味があれば、Python プロジェクトの創設者である Glyph が書いたこのトピックに関する記事を読むことができます。
ドキュメントの説明によると、addReader のリーダー パラメータは IReadDescriptor インターフェイスを実装していることがわかります。これは、PoetrySocket も同じことを行う必要があることを意味します。
インターフェイス モジュールを読むと、次のコードが表示されます:

class IReadDescriptor(IFileDescriptor):
  def doRead():
    """
    Some data is available for reading on your descriptor.
    """
ログイン後にコピー

同時に、PoetrySocket クラスに doRead メソッドがあることがわかります。 Twisted のリアクターによって呼び出されると、ソケットからデータを非同期に読み取ります。したがって、doRead は実際にはコールバック関数ですが、リアクターには直接渡されず、このメソッドを実装したオブジェクト インスタンスが渡されます。これは Twisted フレームワークの規則でもあり、インターフェイスを実装する関数を直接渡すのではなく、インターフェイスを実装するオブジェクトを渡します。このようにして、パラメータを通じて関連するコールバック関数のセットを渡すことができます。また、オブジェクトに格納されたデータを通じてコールバック関数間で通信することも可能です。
PoetrySocket に他のコールバック関数を実装する場合はどうですか? IReadDescriptor は IFileDescriptor のサブクラスであることに注意してください。これは、IReadDescriptor を実装する人は誰でも IFileDescriptor を実装する必要があることを意味します。コードを注意深く読むと、次のことがわかります:

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来形象地说明这一过程:

Python の Twisted フレームワークを使用したノンブロッキング ダウンロード プログラムの構築に関するチュートリアルの例

每当回调被激活,就轮到我们的代码将所有能够读的数据读回来然后非阻塞式的停止。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的缓冲区会在瞬间填满,这样一来我们这个客户端与前面那个阻塞式客户端相比就没有什么优势可言了。

クライアントを抽象的に構築する
まず第一に、このクライアントには実際には、ネットワーク ポートを作成し、ポートでデータを受信するなどの退屈なコードが含まれています。 Twisted はこれらのルーチン関数を実装してくれるため、新しいプログラムを作成するたびに自分で関数を実装する必要がなくなります。これは、非同期 I/O (前のクライアントを参照) に関連するいくつかのトリッキーな例外処理から解放されるため、特に便利です。クロスプラットフォームの場合は、さらにトリッキーな詳細が必要になります。午後に自由な時間があれば、Twisted の WIN32 実装ソース コードを調べて、クロスプラットフォームを処理するための小さなスレッドがいくつあるかを確認できます。
もう 1 つの問題は、エラー処理に関連しています。バージョン 1 を実行している Twisted クライアントが、提供されていないポートから詩をダウンロードすると、クラッシュします。もちろん、このエラーは修正できますが、この種のエラーは、以下で紹介する Twisted の API を使用して処理する方が簡単です。
最後に、そのクライアントは再利用できません。クライアントを通じて詩をダウンロードする必要がある別のモジュールがある場合はどうなるでしょうか?あなたの詩がダウンロードされたことを人々はどのようにして知るのでしょうか?詩をダウンロードして、それを他の人に渡して、事前に待機状態にしておくという方法は使用できません。これは確かに問題ですが、このセクションでは取り上げません。将来のセクションで必ず取り上げます。
最初と 2 番目の問題を解決するために、いくつかの高レベル API とインターフェイスを使用します。 Twisted フレームワークは、多くの抽象化レイヤーで大まかに構成されています。したがって、Twisted を学習するということは、各レイヤーでどのような API、インターフェイス、インスタンスが利用できるのかなど、これらのレイヤーがどのような機能を提供するのかを学ぶことも意味します。次に、Twisted がどのように構成されているかをよりよく理解するために、Twisted の最も重要な部分を分析します。 Twisted の全体的な構造に慣れると、新しい部分を学ぶのがはるかに簡単になります。
一般的に言えば、各 Twisted 抽象化は特定の概念にのみ関連します。たとえば、パート 4 でクライアントが使用する IReadDescriptor は、「バイトを読み取ることができるファイル記述子」を抽象化したものです。抽象化は、多くの場合、インターフェイスを定義することによって、抽象化を実装する (つまり、インターフェイスを実装する) オブジェクトの動作を指定します。新しい Twisted 抽象化の概念を学ぶときに覚えておくべき最も重要なことは次のとおりです:
ほとんどの高レベルの抽象化は低レベルの抽象化に基づいて構築されており、独立したものはほとんどありません。
そのため、新しい Twisted 抽象化を学ぶときは、それが何をするのか、何をしないのかを常に思い出してください。特に、初期の抽象化 A が機能 F を実装する場合、機能 F は他の抽象化によって実装される可能性は低くなります。さらに、別の抽象化が F 機能を必要とする場合、F 自体を実装する代わりに A を使用します。 (通常、B は A を継承するか、A のインスタンスへの参照を取得します)
ネットワークは非常に複雑なので、Twisted には多くの抽象的な概念が含まれています。低レベルの抽象化から始めることで、Twisted プログラムのさまざまな部分がどのように構成されているかをより明確に理解できるようになります。
コアループ本体

学ぶ必要がある最初の抽象化であり、Twisted で最も重要なものは、reactor です。 Twisted によって構築されたすべてのプログラムの中心には、プログラムのレイヤーがいくつであっても、プログラムを継続的に実行するリアクター ループが常に存在します。リアクターほど基本的なサポートはありません。実際、Twisted の他の部分 (つまり、リアクター ループを除く) は次のように理解できます。これらはすべて、X がリアクターのコンテンツをより適切に使用できるようにするために使用されます。前のクライアントのように低レベル API の使用にこだわることもできますが、そこにこだわる場合は、多くのコンテンツを自分たちで実装する必要があります。そして、より高いレベルでは、記述できるコードの量が大幅に減ることを意味します。
しかし、外部から問題を考えたり対処したりしていると、リアクターの存在を忘れがちです。一般的なサイズの Twisted プログラムでは、実際に、リアクター API との直接的なやり取りはほとんどありません。低レベルの抽象化についても同様です (つまり、それらを直接操作することはほとんどありません)。以前のクライアントで使用したファイル記述子の抽象化は、より高いレベルの抽象化によって非常によく要約されているため、実際の Twisted プログラムではめったに遭遇しません。 (それらはまだ内部的に使用されており、私たちがそれを見ることができないだけです)
ファイル記述子の抽象化メッセージに関しては、これは問題ではありません。 Twisted に非同期 I/O 処理の主導権を握らせて、実際に解決しようとしている問題により集中できるようにします。しかし、リアクターの場合は異なり、決して消えることはありません。 Twisted の使用を選択した場合、それは Reactor パターンの使用を選択したことを意味し、コールバックとマルチタスクを使用した「対話型」プログラミングが必要であることを意味します。

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

Python の Twisted フレームワークを使用したノンブロッキング ダウンロード プログラムの構築に関するチュートリアルの例

一旦初始化到这一步后,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的回调过程:

Python の Twisted フレームワークを使用したノンブロッキング ダウンロード プログラムの構築に関するチュートリアルの例

一旦诗歌下载完成,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()
ログイン後にコピー

   

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

更多Python の Twisted フレームワークを使用したノンブロッキング ダウンロード プログラムの構築に関するチュートリアルの例相关文章请关注PHP中文网!

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート