asyncore — Асинхронный обработчик сокетов

Не рекомендуется, начиная с версии 3.6: Вместо него используйте asyncio.


Примечание

Этот модуль существует только для обратной совместимости. Для нового кода мы рекомендуем использовать asyncio.

Этот модуль обеспечивает базовую инфраструктуру для написания клиентов и серверов службы асинхронных сокетов.

Есть только два способа заставить программу на одном процессоре выполнять «более одной задачи одновременно». Многопоточное программирование — самый простой и самый популярный способ сделать это, но есть ещё один совершенно другой метод, который позволяет вам использовать почти все преимущества многопоточности, фактически не используя несколько потоков. Это действительно практично, только если ваша программа в значительной степени связана с вводом-выводом. Если ваша программа привязана к процессору, то, вероятно, вам действительно нужны упреждающие запланированные потоки. Однако сетевые серверы редко привязаны к процессору.

Если ваша операционная система поддерживает системный вызов select() в своей библиотеке ввода-вывода (и почти все поддерживают), то вы можете использовать его для одновременного управления несколькими каналами связи; выполняет другую работу, пока ваш ввод-вывод выполняется в «фоновом режиме». Хотя стратегия может показаться странной и сложной, особенно на первый взгляд, её во многих отношениях легче понять и контролировать, чем многопоточное программирование. Модуль asyncore решает для вас многие сложные проблемы, упрощая создание сложных высокопроизводительных сетевых серверов и клиентов. Для «разговорных» приложений и протоколов незаменим сопутствующий модуль asynchat.

Основная идея обоих модулей заключается в создании одной или нескольких сетей каналов, экземпляров классов asyncore.dispatcher и asynchat.async_chat. Создание каналов добавляет их на глобальное отображение, используемую функцией loop(), если вы не предоставили ей свой собственное отображение.

После того как начальный канал (каналы) создан (созданы), вызов функции loop() активирует службу канала, которая продолжается до тех пор, пока последний канал (включая добавленные на отображение во время асинхронного обслуживания) не будет закрыт.

asyncore.loop([timeout[, use_poll[, map[, count]]]])

Войти в цикл опроса, завершаемый после прохождения счётчика или закрытия всех открытых каналов. Все аргументы необязательны. У параметра count по умолчанию значение None, в результате чего цикл завершается только после закрытия всех каналов. Аргумент timeout устанавливает параметр тайм-аута для соответствующего вызова select() или poll(), измеряемый в секундах; по умолчанию 30 секунд. Параметр use_poll, если он истинен, указывает, что poll() следует использовать вместо select() (по умолчанию False).

Параметр map — это словарь, элементами которого являются просматриваемые каналы. Когда каналы закрываются, они удаляются со своего отображения. Если map пропущен, используется глобальное отображение. Каналы (экземпляры asyncore.dispatcher, asynchat.async_chat и их подклассы) можно свободно смешивать на отображении.

class asyncore.dispatcher

Класс dispatcher — это тонкая оболочка для низкоуровневого объекта сокета. Чтобы сделать его более полезным, в нём есть несколько методов обработки событий, которые вызываются из асинхронного цикла. В противном случае его можно рассматривать как обычный неблокирующий объект сокета.

Запуск низкоуровневых событий в определённое время или в определённых состояниях соединения сообщает асинхронному циклу, что произошли определенные высокоуровневые события. Например, если попросить сокет подключиться к другому хосту, то становится известно, что соединение было выполнено, когда сокет впервые становится доступным для записи (на этом этапе известно, что можете писать в него с ожиданием успеха). Подразумеваемые события более высокого уровня следующие:

Событие Описание
handle_connect() Подразумевается первым событием чтения или записи
handle_close() Подразумевается событие чтения без доступных данных
handle_accepted() Подразумевается событием чтения в прослушивающем сокете

Во время асинхронной обработки методы readable() и writable() каждого отображаемого канала используются для определения того, следует ли добавить сокет канала в список каналов select() или poll() для событий чтения и записи.

Таким образом, множество событий канала больше, чем базовых событий сокета. Полное множество методов, которые можно переопределить в вашем подклассе, приведён ниже:

handle_read()

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

handle_write()

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

def handle_write(self):
    sent = self.send(self.buffer)
    self.buffer = self.buffer[sent:]
handle_expt()

Вызывается при наличии внеполосных данных (out of band, OOB) для сокет-соединения. Этого почти никогда не произойдёт, поскольку OOB мало поддерживается и используется редко.

handle_connect()

Вызывается, когда сокет активного открывателя фактически устанавливает соединение. Например, может отправить приветственный баннер или инициировать согласование протокола с удаленной конечной точкой.

handle_close()

Вызывается, когда сокет закрыт.

handle_error()

Вызывается при возникновении исключения и не обрабатывается иным образом. Версия по умолчанию печатает сжатую трассировку.

handle_accept()

Вызывается на прослушивающих каналах (пассивные открывающие устройства), когда может быть установлено соединение с новой удалённой конечной точкой, которая отправила вызов connect() для локальной конечной точки. Не рекомендуется в версии 3.2; используйте вместо него handle_accepted().

Не рекомендуется, начиная с версии 3.2.

handle_accepted(sock, addr)

Вызывается на прослушивающих каналах (пассивных средствах открытия), когда установлено соединение с новой удаленной конечной точкой, которая отправила вызов connect() для локальной конечной точки. sock — это новый объект сокета, используемый для отправки и получения данных в соединении, а addr — это адрес, привязанный к сокету на другом конце соединения.

Добавлено в версии 3.2.

readable()

Вызывается каждый раз в асинхронном цикле, чтобы определить, следует ли добавить сокет канала в список, в котором могут происходить события чтения. Метод по умолчанию просто возвращает True, указывая, что по умолчанию все каналы будут заинтересованы в событиях чтения.

writable()

Вызывается каждый раз в асинхронном цикле, чтобы определить, следует ли добавить сокет канала в список, в котором могут происходить события записи. Метод по умолчанию просто возвращает True, указывая, что по умолчанию все каналы будут заинтересованы в событиях записи.

Кроме того, каждый канал делегирует или расширяет многие методы сокетов. Большинство из них почти идентичны своим партнерам по сокетам.

create_socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

Идентичен созданию обычного сокета и будет использовать те же параметры для создания. Обратитесь к документации socket за информацией о создании сокетов.

Изменено в версии 3.3: Аргументы family и type можно не указывать.

connect(address)

Как и в случае с обычным объектом сокета, address представляет собой кортеж с первым элементом, к которому нужно подключиться, а вторым — номером порта.

send(data)

Отправляет data на удаленную конечную точку сокета.

recv(buffer_size)

Читает не более buffer_size байт из удаленной конечной точки сокета. Пустой объект байтов означает, что канал был закрыт с другого конца.

Обратите внимание, что recv() может вызвать BlockingIOError, даже если select.select() или select.poll() сообщили, что сокет готов к чтению.

listen(backlog)

Слушает подключения к сокету. Аргумент backlog указывает максимальное количество подключений в очереди и должен быть не менее 1; максимальное значение зависит от системы (обычно 5).

bind(address)

Привязывает сокет к address. Сокет ещё не должен быть привязан. (Формат address зависит от семейства адресов. Для получения дополнительной информации см. документацию socket.) чтобы пометить сокет как повторно используемый (установка параметра SO_REUSEADDR), вызвать метод set_reuse_addr() объекта dispatcher.

accept()

Принимает соединение. Сокет должен быть привязан к адресу и прослушивать соединения. Возвращаемое значение может быть либо None, либо парой (conn, address), где conn — объект нового сокета, используемый для отправки и получения данных в соединении, а address — это адрес, привязанный к сокету на другом конце соединения. Когда возвращается None, это означает, что соединение не было установлено, и в этом случае сервер должен просто игнорировать это событие и продолжать прослушивать дальнейшие входящие соединения.

close()

Закрывает сокет. Все будущие операции с объектом сокета завершатся ошибкой. Удаленная конечная точка больше не будет получать данные (после того, как данные из очереди будут сброшены). Сокеты автоматически закрываются при сборке мусора.

class asyncore.dispatcher_with_send

Подкласс dispatcher, который добавляет возможность простого буферизованного вывода, полезную для простых клиентов. Для более сложного использования используйте asynchat.async_chat.

class asyncore.file_dispatcher

File_dispatcher принимает дескриптор файла или файловый объект вместе с необязательным аргументом отображения и обёртывает его для использования с функциями poll() или loop(). Если предоставлен файловый объект или что-либо ещё с методом fileno(), этот метод будет вызван и передан конструктору file_wrapper.

Доступность: Unix.

class asyncore.file_wrapper

file_wrapper принимает целочисленный дескриптор файла и вызывает os.dup() для дублирования дескриптора, чтобы исходный дескриптор мог быть закрыт независимо от file_wrapper. Этот класс реализует достаточные методы для эмуляции сокета для использования классом file_dispatcher.

Доступность: Unix.

Аsyncore пример базового HTTP-клиента

Вот очень простой HTTP-клиент, который использует класс dispatcher для реализации своей обработки сокетов:

import asyncore

class HTTPClient(asyncore.dispatcher):

    def __init__(self, host, path):
        asyncore.dispatcher.__init__(self)
        self.create_socket()
        self.connect( (host, 80) )
        self.buffer = bytes('GET %s HTTP/1.0\r\nHost: %s\r\n\r\n' %
                            (path, host), 'ascii')

    def handle_connect(self):
        pass

    def handle_close(self):
        self.close()

    def handle_read(self):
        print(self.recv(8192))

    def writable(self):
        return (len(self.buffer) > 0)

    def handle_write(self):
        sent = self.send(self.buffer)
        self.buffer = self.buffer[sent:]


client = HTTPClient('digitology.tech', '/')
asyncore.loop()

Пример asyncore базового эхо-сервера

Вот базовый эхо-сервер, который использует класс dispatcher для приёма соединений и отправляет входящие соединения обработчику:

import asyncore

class EchoHandler(asyncore.dispatcher_with_send):

    def handle_read(self):
        data = self.recv(8192)
        if data:
            self.send(data)

class EchoServer(asyncore.dispatcher):

    def __init__(self, host, port):
        asyncore.dispatcher.__init__(self)
        self.create_socket()
        self.set_reuse_addr()
        self.bind((host, port))
        self.listen(5)

    def handle_accepted(self, sock, addr):
        print('Incoming connection from %s' % repr(addr))
        handler = EchoHandler(sock)

server = EchoServer('localhost', 8080)
asyncore.loop()