asyncore — обробник асинхронного сокета

Вихідний код: Lib/asyncore.py

Застаріло з версії 3.6, буде видалено у версії 3.12: The asyncore module is deprecated (see PEP 594 for details). Please use asyncio instead.


Примітка

Цей модуль існує лише для зворотної сумісності. Для нового коду ми рекомендуємо використовувати asyncio.

Цей модуль надає базову інфраструктуру для написання клієнтів і серверів служби асинхронних сокетів.

Availability: not Emscripten, not WASI.

This module does not work or is not available on WebAssembly platforms wasm32-emscripten and wasm32-wasi. See WebAssembly platforms for more information.

Є лише два способи змусити програму на одному процесорі виконувати «більш ніж одну дію одночасно». Багатопотокове програмування є найпростішим і найпопулярнішим способом зробити це, але існує ще одна зовсім інша техніка, яка дає вам змогу отримати майже всі переваги багатопоточності без фактичного використання кількох потоків. Це дійсно практично, лише якщо ваша програма значною мірою пов’язана з вводом-виводом. Якщо ваша програма пов’язана з процесором, то вам справді потрібні потоки з випереджальним плануванням. Проте мережеві сервери рідко пов’язані з процесором.

Якщо ваша операційна система підтримує системний виклик 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()ed або 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=socket.AF_INET, type=socket.SOCK_STREAM)

Це ідентично створенню звичайного сокета та використовуватиме ті самі параметри для створення. Зверніться до документації socket для отримання інформації про створення сокетів.

Змінено в версії 3.3: Аргументи сімейство і тип можна опустити.

connect(address)

Як і у випадку зі звичайним об’єктом сокета, адреса – це кортеж із першим елементом, до якого потрібно підключитися, а другим – номер порту.

send(data)

Надіслати дані до віддаленої кінцевої точки сокета.

recv(buffer_size)

Читати щонайбільше buffer_size байтів із віддаленої кінцевої точки сокета. Порожній об’єкт bytes означає, що канал закрито з іншого боку.

Зверніть увагу, що recv() може викликати BlockingIOError, навіть якщо select.select() або select.poll() повідомили, що сокет готовий для читання.

listen(backlog)

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

bind(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 приймає дескриптор файлу або file object разом із необов’язковим аргументом карти та обгортає його для використання з функціями poll() або loop(). Якщо надати файловий об’єкт або щось із методом fileno(), цей метод буде викликано та передано конструктору file_wrapper.

class asyncore.file_wrapper

File_wrapper приймає цілочисельний файловий дескриптор і викликає os.dup(), щоб дублювати дескриптор, щоб вихідний дескриптор можна було закрити незалежно від file_wrapper. Цей клас реалізує достатньо методів для емуляції сокета для використання класом file_dispatcher.

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()

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()