Какая библиотека асинхронного Python лучше всего подходит для моего кода? Asyncore? Витая?

У меня есть программа, над которой я работаю, которая будет считываться из двух "сетевых источников" одновременно. Я хотел попробовать асинхронный подход, а не использовать потоки. Это заставило меня задуматься о том, какую библиотеку использовать...

Я придумал простой пример кода, который демонстрирует, что моя программа будет делать:

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? Если да, не могли бы вы показать мне, как преобразовать код примера в "асинхронный код"? Знаете ли вы какие-либо хорошие асинхронные уроки?

Ответ 1

Twisted лучше в любом случае. Он более портативный, более функциональный, более простой, более масштабируемый, лучше поддерживается, лучше документирован, и он может сделать вкусный омлет. Asyncore, по сути, устарел.

Трудно продемонстрировать все способы, с помощью которых Twisted превосходит короткий ответ (как я могу продемонстрировать http/dns/ssh/smtp/pop/imap/irc/xmpp/process-spawning/многопоточный сервер в кратком примере?), поэтому вместо этого я сосредоточусь на одном из самые распространенные заблуждения, которые люди, похоже, имеют о Twisted: это как-то более сложно или сложнее в использовании, чем asyncore.

Начнем с асинхронного примера. Чтобы избежать предвзятой презентации, я буду использовать пример от кого-то, кто все еще любит немного асинхронно. Вот простой пример асинхронного взятый из веб-журнала Ричарда Джонса (с комментариями для краткости).

Во-первых, здесь сервер:

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 на 25% короче, даже для чего-то такого тривиального. 40 строк для asyncore, всего 30 для Twisted. По мере того, как ваш протокол становится более сложным, эта разница будет увеличиваться и увеличиваться, так как вам нужно написать все больше и больше кода поддержки для асинхронизации, которые были бы предоставлены вам Twisted.

Во-вторых, Twisted обеспечивает полную абстракцию. С примером asyncore вы должны использовать модуль socket для создания реальной сети; asyncore обеспечивает только мультиплексирование. Это проблема, если вам нужно переносное поведение на таких платформах, как Windows. Это также означает, что в asyncore полностью отсутствуют возможности для асинхронной коммуникации подпроцесса на других платформах; вы не можете записывать произвольные дескрипторы файлов в вызов select() в Windows.

В-третьих, пример Twisted является нейтральным транспортом. Ни один из Echo и EchoFactory и EchoClient и EchoClientFactory не имеет никакого отношения к TCP. Вы можете сделать эти классы в библиотеку, которая может быть подключена через SSH, или SSL, или сокет UNIX, или канал, только путем изменения одного вызова connectTCP/listenTCP внизу. Это важно, так как поддержка чего-то типа TLS непосредственно в вашей логике протокола очень сложна. Например, "запись" в TLS вызовет "чтение" на более низком уровне. Таким образом, вам нужно отделить эти проблемы, чтобы их правильно исправить.

Наконец, для вашего прецедента, если вы имеете дело с MAC-адресами и сетями Ethernet напрямую, Twisted содержит Twisted Pair, a низкоуровневую библиотеку для работы с сетями IP и ethernet. Это не самая активно поддерживаемая часть Twisted; код довольно старый. Но он должен работать, и если это не так, мы будем серьезно относиться к любым ошибкам и (в конечном итоге) видеть, что они исправляются. Насколько мне известно, нет никакой сопоставимой библиотеки для asyncore, и она, безусловно, не содержит никакого такого кода.

Ответ 2

Asyncore приятный, но не очень богатый, поэтому вы можете столкнуться с проблемами позже, когда ваше приложение растет. Это, как говорится, отлично подходит для прототипов. Подход довольно прост. Вы определяете методы обработки определенных событий в классе (когда чтение возможно, когда запись возможна и т.д.), А затем подкласс, который из класса asyncore.dispatcher(я думаю).

официальные документы для модуля, а также Doug Hellmann отлично PyMOTW статья об этом - хорошие источники для проверки документов и примеров.

Если ваш протокол является разговорным (например, отправьте это, получите его), вы можете проверить, что модуль асинтакта также распространяется со стандартной библиотекой для идей.

Twisted - это гораздо более тяжелый подход. Я уверен, что он будет работать лучше для больших проектов, учитывая, насколько он используется, но я не могу сказать ничего больше, потому что у меня нет опыта в этом.

Ответ 3

Curl был спроектирован так, чтобы быть неблокирующим во всех перспективах и избегать использования select, который является дорогостоящей операцией во время асинхронного ввода-вывода. На низком уровне завиток использует наиболее оптимальные возможные решения, поэтому к дате нет рамки, которая была бы способна работать лучше, чем завиток, хотя могут быть структуры, которые могли бы дать схожую производительность.

Как говорится, как писать собственные сокеты? Это очень легко в Python и может дать вам потрясающую производительность, как только вы знаете, что делаете, и ясно из ваших целей.