18.6. asyncore – Асинхронный обработчик сокетов
Исходный код: Lib/asyncore.py
Устарело с версии 3.6: используйте asyncio вместо этого.
Примечание
Этот модуль существует только для обратной совместимости. Для нового кода рекомендуем использовать asyncio.
Этот модуль предоставляет базовую инфраструктуру для написания асинхронных клиентов и серверов сокетных служб.
Есть только два способа заставить программу на одном процессоре делать «более одного дела за раз». Многопоточное программирование – самый простой и популярный способ, но есть и другой, совершенно иной метод, который даёт почти все преимущества многопоточности без фактического использования нескольких потоков. Он действительно практичен только в том случае, если ваша программа в значительной степени ограничена вводом-выводом. Если программа ограничена вычислительными ресурсами, то, вероятно, вам нужны вытесняющие потоки с планированием. Однако сетевые серверы редко бывают ограничены процессором.
Если ваша операционная система поддерживает системный вызов select() в своей библиотеке ввода-вывода (а почти все поддерживают), вы можете использовать его, чтобы одновременно управлять несколькими каналами связи, выполняя другую работу, пока ваш ввод-вывод происходит в «фоне». Хотя эта стратегия может показаться странной и сложной, особенно поначалу, она во многих отношениях проще для понимания и контроля, чем многопоточное программирование. Модуль asyncore решает многие сложные проблемы за вас, делая создание сложных высокопроизводительных сетевых серверов и клиентов простым делом. Для «диалоговых» приложений и протоколов бесценен сопутствующий модуль asynchat.
Основная идея обоих модулей – создать один или несколько сетевых каналов, экземпляров класса asyncore.dispatcher и asynchat.async_chat. Создание каналов добавляет их в глобальную карту, используемую функцией loop(), если вы не предоставите ей собственную карту.
Как только начальный канал (или каналы) создан, вызов функции loop() активирует обслуживание каналов, которое продолжается до закрытия последнего канала (включая любые, добавленные в карту во время асинхронного обслуживания).
-
asyncore.loop([тайм-аут[, use_poll[, карта[, count]]]])¶ Запускает цикл опроса, который завершается после count проходов или после закрытия всех открытых каналов. Все аргументы необязательны. Параметр count по умолчанию равен
None, в результате цикл завершается только при закрытии всех каналов. Аргумент тайм-аут задаёт параметр тайм-аута для соответствующего вызоваselect()илиpoll(), измеряется в секундах; по умолчанию 30 секунд. Параметр use_poll, если равен true, указывает, что следует использоватьpoll()вместоselect()(по умолчаниюFalse).Параметр карта – это словарь, элементами которого являются каналы для наблюдения. При закрытии каналы удаляются из своей карты. Если карта опущена, используется глобальная карта. Каналы (экземпляры
asyncore.dispatcher,asynchat.async_chatи их подклассов) можно свободно смешивать в карте.
-
class
asyncore.dispatcher¶ Класс
dispatcher– это тонкая обёртка вокруг низкоуровневого объекта сокета. Чтобы сделать его более полезным, он имеет несколько методов для обработки событий, которые вызываются из асинхронного цикла. В остальном с ним можно обращаться как с обычным неблокирующим объектом сокета.Возникновение низкоуровневых событий в определённые моменты или в определённых состояниях соединения сообщает асинхронному циклу о том, что произошли некоторые высокоуровневые события. Например, если мы запросили у сокета подключение к другому хосту, мы знаем, что соединение установлено, когда сокет впервые становится доступным для записи (в этот момент вы знаете, что можете писать в него с ожиданием успеха). Подразумеваемые высокоуровневые события:
Событие
Описание
handle_connect()Подразумевается первым событием чтения или записи
handle_close()Подразумевается событием чтения, когда данные недоступны
handle_accepted()Подразумевается событием чтения на прослушивающем сокете
Во время асинхронной обработки методы
readable()иwritable()каждого отображаемого канала используются для определения того, должен ли сокет канала быть добавлен в список каналов, для которыхselect()ed илиpoll()ed проверяются события чтения и записи.Таким образом, набор событий канала шире, чем базовые события сокета. Далее приведён полный набор методов, которые можно переопределить в вашем подклассе:
-
handle_read()¶ Вызывается, когда асинхронный цикл обнаруживает, что вызов
read()на сокете канала будет успешным.
-
handle_write()¶ Вызывается, когда асинхронный цикл обнаруживает, что в сокет, доступный для записи, можно записать. Часто этот метод реализует необходимую буферизацию для производительности. Например:
pythondef 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()для локальной конечной точки. Устарело в версии 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 на удалённую конечную точку сокета.
-
recv(buffer_size)¶ Читает не более buffer_size байт из удалённой конечной точки сокета. Пустой объект bytes означает, что канал был закрыт с другого конца.
Обратите внимание, что
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 принимает файловый дескриптор или файловый объект вместе с необязательным аргументом map и оборачивает его для использования с функциями
poll()илиloop(). Если передан файловый объект или что-либо с методомfileno(), этот метод будет вызван и передан конструкторуfile_wrapper. Доступность: UNIX.
-
class
asyncore.file_wrapper¶ file_wrapper принимает целочисленный файловый дескриптор и вызывает
os.dup()для дублирования дескриптора, чтобы исходный дескриптор можно было закрыть независимо от file_wrapper. Этот класс реализует достаточное количество методов для эмуляции сокета для использования классомfile_dispatcher. Доступность: UNIX.
18.6.1. asyncore Пример базового 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('www.python.org', '/')
asyncore.loop()
18.6.2. 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()