Содержание страницы
17.5. asyncore – Асинхронный обработчик сокетов¶asyncore – Asynchronous socket handler
Исходный код: Lib/asyncore.py
Этот модуль предоставляет базовую инфраструктуру для написания клиентов и серверов асинхронных сокетных служб.
Есть только два способа заставить программу на одном процессоре делать «более одного дела одновременно». Многопоточное программирование – самый простой и популярный способ, но есть и другой, совершенно иной метод, который даёт почти все преимущества многопоточности без использования нескольких потоков. Он действительно практичен только в том случае, если программа в основном связана с вводом-выводом. Если программа загружает процессор, то, вероятно, на самом деле требуются вытесняющие потоки с планированием. Однако сетевые серверы редко бывают привязаны к процессору.
Если ваша операционная система поддерживает системный вызов select() в библиотеке ввода-вывода (а поддерживают почти все), вы можете использовать его для одновременной работы с несколькими каналами связи, выполняя другую работу, пока ввод-вывод происходит в «фоновом режиме». Хотя такой подход может показаться странным и сложным, особенно на первых порах, во многих отношениях он проще для понимания и управления, чем многопоточное программирование. Модуль asyncore решает многие сложные проблемы, делая построение сложных высокопроизводительных сетевых серверов и клиентов простым делом. Для «диалоговых» приложений и протоколов незаменим сопутствующий модуль asynchat.
Основная идея обоих модулей – создать один или несколько сетевых каналов, экземпляров классов asyncore.dispatcher и asynchat.async_chat. Создание каналов добавляет их в глобальную карту, используемую функцией loop(), если вы не предоставите ей собственную карту.
После создания начального канала (или каналов) вызов функции loop() активирует обслуживание каналов, которое продолжается до тех пор, пока последний канал (включая те, что были добавлены в карту во время асинхронного обслуживания) не будет закрыт.
- asyncore.loop([timeout[, use_poll[, map[, count]]]])¶
Запускает цикл опроса, который завершается после count проходов или когда все открытые каналы будут закрыты. Все аргументы необязательны. Параметр count по умолчанию равен None, в результате цикл завершается только тогда, когда все каналы закрыты. Аргумент timeout задаёт параметр тайм-аута для соответствующего вызова select() или poll(), измеряется в секундах; значение по умолчанию – 30 секунд. Параметр use_poll, если равен true, указывает, что следует использовать poll() вместо select() (по умолчанию 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_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()¶
Вызывается при наличии внеполосных данных (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, 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()¶
Принимает соединение. Сокет должен быть привязан к адресу и ожидать соединений. Возвращаемое значение может быть либо 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.
17.5.1. Пример asyncore: базовый HTTP-клиент¶asyncore Example basic HTTP client
Вот очень простой HTTP-клиент, который использует класс dispatcher для реализации обработки сокетов:
import asyncore, socket
class HTTPClient(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 = 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()
17.5.2. asyncore Пример базового эхо-сервера¶asyncore Example basic echo server
Вот простой эхо-сервер, который использует класс dispatcher для приёма соединений и направляет входящие соединения обработчику:
import asyncore
import socket
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(socket.AF_INET, socket.SOCK_STREAM)
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()