哪个Python异步库最适合我的代码?Asyncore?Twisted?

18

我正在开发一个程序,需要同时读取两个“网络源”。我想尝试异步方式而不是使用线程。这让我想知道应该使用哪个库...

我已经编写了一些简单的示例代码,它们大致演示了我的程序将要做的事情:

import sniffer

def first():
    for station in sniffer.sniff_wifi():
        log(station.mac())

def second():
    for station in sniffer.sniff_ethernet():
        log(station.mac())

first()
second()

这两个sniffer方法看起来有点像这样:

def sniff_wifi(self):

    while True:
        yield mac_address

while True循环显然会使它们变成阻塞的。

我希望使用asyncore,因为它是标准库的一部分。没有第三方依赖项是一个好处。然而,如果您建议我使用它,那并不意味着我不会使用它...

我能否用asyncore实现我想做的事情?如果可以的话,你能给我展示如何将我的示例代码转换为'asyncore代码'吗?你知道任何好的asyncore教程吗?

3个回答

50
Twisted在几乎所有方面都更好。它更便携、更丰富、更简单、更可扩展、维护更好、文档更好,而且可以制作美味煎蛋卷。Asyncore就算是过时了。
很难在短时间内展示Twisted的所有优势(我怎么能用一个简短的例子展示http/dns/ssh/smtp/pop/imap/irc/xmpp/process-spawning/multi-threading服务器呢?),所以我将专注于人们似乎对Twisted有的最常见的误解之一:它比asyncore更复杂或更难使用。

让我们从一个asyncore的例子开始。为了避免偏见,我将使用来自仍然喜欢asyncore的其他人的示例。这是一个简单的asyncore示例摘自Richard Jones的博客(为了简洁起见省略了注释)。

首先,这是服务器端:

import asyncore, socket

class Server(asyncore.dispatcher):
    def __init__(self, host, port):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.bind(('', port))
        self.listen(1)

    def handle_accept(self):
        socket, address = self.accept()
        print 'Connection by', address
        EchoHandler(socket)

class EchoHandler(asyncore.dispatcher_with_send):
    def handle_read(self):
        self.out_buffer = self.recv(1024)
        if not self.out_buffer:
            self.close()

s = Server('', 5007)
asyncore.loop()

这是客户端:

import asyncore, socket

class Client(asyncore.dispatcher_with_send):
    def __init__(self, host, port, message):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((host, port))
        self.out_buffer = message

    def handle_close(self):
        self.close()

    def handle_read(self):
        print 'Received', self.recv(1024)
        self.close()

c = Client('', 5007, 'Hello, world')
asyncore.loop()

这段代码存在一些较为隐晦的情况,但是解释它们很无聊也很复杂,而且这段代码已经使得答案变得足够长了。
现在,以下是用Twisted完成基本相同功能的代码。首先是服务器端的代码:
from twisted.internet import reactor, protocol as p

class Echo(p.Protocol):
    def dataReceived(self, data):
        self.transport.write(data)

class EchoFactory(p.Factory):
    def buildProtocol(self, addr):
        print 'Connection by', addr
        return Echo()

reactor.listenTCP(5007, EchoFactory())
reactor.run()

现在,客户端:

from twisted.internet import reactor, protocol as p

class EchoClient(p.Protocol):
    def connectionMade(self):
        self.transport.write(self.factory.data)

    def dataReceived(self, data):
        print 'Received:', data
        self.transport.loseConnection()

class EchoClientFactory(p.ClientFactory):
    protocol = EchoClient
    def __init__(self, data):
        self.data = data

reactor.connectTCP('localhost', 5007, EchoClientFactory('Hello, world'))
reactor.run()

我想引起你的注意的有几点。首先,即使是这种微不足道的事情,Twisted示例也比asyncore短25%。 asyncore需要40行,而Twisted只需要30行。随着您的协议变得更加复杂,这种差异将变得越来越大,因为您需要编写越来越多的支持代码,而这些支持代码在Twisted中已经为您提供。

其次,Twisted提供了完整的抽象。使用asyncore示例,您必须使用socket模块来执行实际的网络操作;asyncore仅提供多路复用。如果您需要在Windows等平台上具有可移植性,这将成为问题。这也意味着asyncore完全缺乏在其他平台上进行异步子进程通信的工具;您无法将任意文件描述符插入Windows上的select()调用中。

第三,Twisted示例是传输中立的。 EchoEchoFactoryEchoClientEchoClientFactory都不特定于TCP。 您可以将这些类制作成库,通过更改底部的一个connectTCP/listenTCP调用,仅通过SSH、SSL、UNIX套接字或管道进行连接。 这很重要,因为在协议逻辑中直接支持诸如TLS之类的内容非常棘手。例如,在TLS中的“写”会触发较低级别的“读”。 因此,您需要将这些问题分开以正确处理它们。
最后,针对您的用例,如果您直接处理MAC地址和以太网帧,则Twisted包含Twisted Pair,这是一个用于处理IP和以太网级别网络的低级库。 这不是Twisted中最活跃的部分;代码相当古老。 但是,它应该有效,如果出现问题,我们会认真处理任何错误并(最终)确保它们得到修复。据我所知,asyncore没有可比拟的库,并且它肯定不包含任何此类代码。

6
这将是《扭曲》作者的不错开场白。 - pyfunc
1
+1。非常详细和简洁,但我对你的表现毫不意外。 :) - Noufal Ibrahim
4
绝对是一个很棒的回答,更不用说它来自于Twisted的首席架构师了。 - Overmind Jiang
1
一些asyncore无法处理的“晦涩”情况是从accept返回的EMFILEENOMEM,或者从recv返回的EWOULDBLOCK。这些情况只会在相当隐晦的条件下发生(评论长度限制将阻止我完全描述它们),但Twisted将会默默地处理它们,而asyncore将会崩溃其主循环。 - Glyph
@Glyph 不是完全正确的,因为每个异常(不包括退出异常)都被捕获了:http://hg.python.org/cpython/file/2.7/Lib/asyncore.py#l79。但是,在`recv`上没有处理 EWOULDBLOCK 似乎是一个错误。 - schlamar
显示剩余2条评论

3
Asyncore是一个不错的模块,但功能不是很丰富,因此在应用程序增长时可能会遇到问题。话虽如此,它很适合原型制作。这种方法非常简单。您可以在一个类中定义处理某些事件的方法(例如,在可读取数据时,在可写入数据时等),然后从asyncore.dispatcher(我想是这个类)中进行子类化。
关于该模块的官方文档以及Doug Hellmann的PyMOTW文章都是了解文档和示例的好资料。
如果您的协议是对话形式的(例如,发送这个,接收那个),则可以查看标准库中还分发的asynchat模块以获取想法。
Twisted是一种更重型的方法。考虑到它的使用频率,我相信它将更适用于更大的项目,但由于我没有任何第一手经验,所以无法再多说什么。

asyncore/asynchat似乎只有在直接绑定到套接字处理时才有用。在我的代码中,sniffer处理所有的套接字操作并产生结果。我只想让asyncore运行我的firstsecond函数,并允许我在sniffers返回数据时发出回调。我该怎么做?(顺便说一句,我没有给你投反对票) - dave
1
asyncore/asynchat为您提供了一个面向对象的包装器,围绕select函数调用(通常是所有异步事物构建的基础)进行操作。select操作文件描述符,因此asyncore/asynchat也是如此。请给我几分钟时间,为您准备可行的内容。 - Noufal Ibrahim
我认为Glyph的答案比我的更完整。 - Noufal Ibrahim

0

Curl被设计为在所有方面都是非阻塞的,并避免使用select,在异步I/O期间是一项昂贵的操作。在低级别上,curl使用最优化的解决方案,因此到目前为止还没有一个框架能够比curl表现更好,尽管可能有类似性能的框架。

话虽如此,你考虑编写自己的套接字怎么样?在Python中非常容易,一旦你知道自己在做什么并且清楚自己的目标,就可以给你带来惊人的性能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接