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

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

Исходный код: Lib/asyncore.py


Примечание

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

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

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

Если ваша операционная система поддерживает системный вызов 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).

Параметр map – это словарь, элементами которого являются отслеживаемые каналы. При закрытии каналов они удаляются из своей карты. Если map опущен, используется глобальная карта. Каналы (экземпляры 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()

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

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=socket.AF_INET, type=socket.SOCK_STREAM)

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

Изменено в версии 3.3: family и type аргументы могут быть опущены.

connect(address)

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

send(data)

Отправляет 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

A file_dispatcher takes a file descriptor or файловый объект along with an optional map argument and wraps it for use with the poll() or loop() functions. If provided a file object or anything with a fileno() method, that method will be called and passed to the file_wrapper constructor. Доступность: UNIX.

class asyncore.file_wrapper

A file_wrapper takes an integer file descriptor and calls os.dup() to duplicate the handle so that the original handle may be closed independently of the file_wrapper. This class implements sufficient methods to emulate a socket for use by the file_dispatcher class. Доступность: UNIX.

18.6.1. asyncore: пример базового HTTP-клиентаasyncore Example basic HTTP client

Вот очень простой 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: пример базового эхо-сервераasyncore Example basic echo server

Вот простой эхо-сервер, который использует класс 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()