Документация Python неофициальный перевод
Содержание страницы

17.6. asyncore – асинхронный обработчик сокетовasyncore – Asynchronous socket handler

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

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

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

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

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

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

Enter a polling loop that terminates after count passes or all open channels have been closed. All arguments are optional. The count parameter defaults to None, resulting in the loop terminating only when all channels have been closed. The timeout argument sets the timeout parameter for the appropriate select() or poll() call, measured in seconds; the default is 30 seconds. The use_poll parameter, if true, indicates that poll() should be used in preference to select() (the default is False).

The map parameter is a dictionary whose items are the channels to watch. As channels are closed they are deleted from their map. If map is omitted, a global map is used. Channels (instances of asyncore.dispatcher, asynchat.async_chat and subclasses thereof) can freely be mixed in the map.

class asyncore.dispatcher

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

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

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

Во время асинхронной обработки методы 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()
Вызывается при наличии внеполосных данных (OOB) для сокетного соединения. Это почти никогда не происходит, так как OOB поддерживается слабо и редко используется.
handle_connect()
Вызывается, когда сокет активного инициатора действительно устанавливает соединение. Может, например, отправить приветственный баннер или инициировать согласование протокола с удалённой конечной точкой.
handle_close()
Вызывается при закрытии сокета.
handle_error()
Вызывается, когда возникает исключение, которое иначе не обрабатывается. Версия по умолчанию выводит сокращённую трассировку.
handle_accept()
Вызывается на прослушивающих каналах (пассивных открывателях), когда можно установить соединение с новой удалённой конечной точкой, которая выполнила вызов connect() для данной локальной конечной точки.
readable()
Вызывается на каждом проходе асинхронного цикла, чтобы определить, нужно ли добавить сокет канала в список, на котором могут происходить события чтения. Метод по умолчанию просто возвращает True, указывая, что по умолчанию все каналы заинтересованы в событиях чтения.
writable()
Вызывается на каждом проходе асинхронного цикла, чтобы определить, нужно ли добавить сокет канала в список, на котором могут происходить события записи. Метод по умолчанию просто возвращает True, указывая, что по умолчанию все каналы заинтересованы в событиях записи.

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

create_socket(family, type)
Это идентично созданию обычного сокета и использует те же параметры создания. Обратитесь к документации socket за информацией о создании сокетов.
connect(address)
Как и для обычного объекта сокета, address – это кортеж, первый элемент которого – хост для подключения, а второй – номер порта.
send(data)
Отправляет data на удалённый узел сокета.
recv(buffer_size)
Читает не более buffer_size байт из удалённой конечной точки сокета. Пустая строка означает, что канал был закрыт с другого конца.
listen(backlog)
Ожидает входящие соединения на сокете. Аргумент backlog задаёт максимальное количество соединений в очереди и должен быть не менее 1; максимальное значение зависит от системы (обычно 5).
bind(address)
Привязывает сокет к адресу address. Сокет не должен быть уже привязан. (Формат address зависит от семейства адресов – обратитесь к документации socket для получения дополнительной информации.) Чтобы пометить сокет как повторно используемый (установив параметр SO_REUSEADDR), вызовите метод объекта dispatcher set_reuse_addr().
accept()
Принимает соединение. Сокет должен быть привязан к адресу и находиться в состоянии ожидания соединений. Возвращаемое значение – пара (conn, address), где conn – это новый объект сокета, который можно использовать для отправки и получения данных по соединению, а address – это адрес, привязанный к сокету на другом конце соединения.
close()
Закрывает сокет. Все последующие операции с объектом сокета будут завершаться ошибкой. Удалённый узел больше не будет получать данные (после отправки данных из очереди). Сокеты автоматически закрываются при сборке мусора.
class asyncore.file_dispatcher
A file_dispatcher принимает файловый дескриптор или файловый объект вместе с необязательным аргументом map и оборачивает его для использования с функциями poll() или loop(). Если передан файловый объект или что-либо, имеющее метод fileno(), этот метод будет вызван, а результат передан конструктору file_wrapper. Доступность: UNIX.
class asyncore.file_wrapper
file_wrapper принимает целочисленный файловый дескриптор и вызывает os.dup() для дублирования дескриптора, чтобы исходный дескриптор можно было закрыть независимо от file_wrapper. Этот класс реализует достаточное количество методов для эмуляции сокета для использования классом file_dispatcher. Доступно: UNIX.

17.6.1. Пример asyncore: простой HTTP-клиентasyncore Example basic HTTP client

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

import asyncore, socket

class http_client(asyncore.dispatcher):

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

    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:]

c = http_client('www.python.org', '/')

asyncore.loop()