Сообщить об ошибке.

Создание сетевых серверов низкоуровневым API asyncio

В разделе рассмотрены низкоуровневые методы цикла событий модуля asyncio, при помощи которых можно создавать асинхронные сетевые сервера следующих видов TCP-сервер, Unix-socket сервер.

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

Содержание:


loop.create_server(protocol_factory, host=None, port=None, *, family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None, start_serving=True):

Метод loop.create_server() создает TCP-сервер (сокет типа SOCK_STREAM), прослушивающий порт port адреса host.

Метод loop.create_server() представляет собой сопрограмму и возвращает объект Server.

Аргументы:

  • protocol_factory должен быть вызываемым объектом, который возвращает реализацию протокола модуля asyncio.

  • host может иметь несколько типов, которые определяют, где сервер будет прослушивать:

    • Если host является строкой, то TCP-сервер привязан к одному сетевому интерфейсу, указанному host.
    • Если host - это последовательность строк, то TCP-сервер привязан ко всем сетевым интерфейсам, указанным в последовательности.
    • Если host - пустая строка или None, то предполагаются все интерфейсы, и будет возвращен список из нескольких сокетов. Скорее всего, один для IPv4, а другой - для IPv6.
  • family - для семейства можно задать значение AF_INET или AF_INET6, чтобы заставить сокет использовать IPv4 или IPv6. Если не задан, то семейство будет определяться по имени хоста (по умолчанию AF_UNSPEC).

  • flags - это битовая маска для loop.getaddrinfo().

  • sock можно дополнительно указать для использования уже существующего объекта сокета. Если указан sock, то хост host и порт port указывать не нужно.

  • backlog - это максимальное количество подключений в очереди, переданных в метод сокета Socket.listen() (по умолчанию 100).

  • ssl может быть установлен на экземпляр SSLContext, чтобы включить TLS через принятые соединения.

  • reuse_address сообщает ядру повторно использовать локальный сокет в состоянии TIME_WAIT, не дожидаясь истечения его естественного тайм-аута. Если не указан, то в Unix будет автоматически установлено значение True.

  • reuse_port указывает ядру разрешить привязку этой конечной точки к тому же порту, к которому привязаны другие существующие конечные точки, если все они устанавливают этот флаг при создании. Эта опция не поддерживается в Windows.

ssl_handshake_timeout - это (для TLS-сервера) время в секундах, в течение которого необходимо дождаться завершения подтверждения TLS, прежде чем разорвать соединение. Если не установлен то будет 60,0 секунд (по умолчанию).

  • ssl_shutdown_timeout - это время в секундах, необходимое для ожидания завершения отключения SSL перед разрывом соединения. Если нет, то по умолчанию 30,0 секунд.

  • start_serving, установленный в True (по умолчанию), то заставляет созданный сервер немедленно начать принимать соединения. Если установлено значение False, то пользователь должен ждать на Server.start_serving() или Server.serve_forever(), чтобы сервер начал принимать соединения.

Новое в Python 3.7: Добавлены параметры ssl_handshake_timeout и start_serving.

Изменено в Python 3.6: Аргумент сокета TCP_NODELAY установлен по умолчанию для всех TCP-соединений.

Изменено в версии 3.11: Добавлен аргумент ssl_shutdown_timeout.

Смотрите также функцию asyncio.start_server() - это альтернативный API более высокого уровня, который возвращает пару (StreamReader, StreamWriter), которые можно использовать в коде async/await.

loop.create_unix_server(protocol_factory, path=None, *, sock=None, backlog=100, ssl=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None, start_serving=True):

Метод loop.create_unix_server() работает так же как loop.create_server(), но только с семейством сокетов AF_UNIX.

Представляет собой сопрограмму и возвращает объект Server.

path - это имя сокета домена Unix и является обязательным, если не указан аргумент sock. Поддерживаются абстрактные Unix сокеты, стоки str, bytes и объекты Path.

Смотрите документацию метода loop.create_server() для получения информации об остальных аргументах этого метода.

Доступность: Unix.

Новое в Python 3.7: добавлены параметры ssl_handshake_timeout и start_serving.

Изменено в Python 3.7: параметр path теперь может быть объектом Path.

Изменено в версии 3.11: Добавлен аргумент ssl_shutdown_timeout.

loop.connect_accepted_socket(protocol_factory, sock, *, ssl=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None):

Метод loop.connect_accepted_socket() оборачивает уже принятое соединение в пару (transport, protocol).

Представляет собой сопрограмму и возвращает пару (transport, protocol).

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

Аргументы:

  • protocol_factory должен быть вызываемым объектом, который возвращает реализацию протокола модуля asyncio.

  • sock - это уже существующий объект сокета, возвращаемый из метода Socket.accept().

  • ssl может быть установлен в SSLContext, чтобы включить SSL через принятые соединения.

    • ssl_handshake_timeout - это (для SSL-соединения) время в секундах, в течение которого необходимо дождаться завершения подтверждения SSL, прежде чем разорвать соединение. Если не установлен то будет 60,0 секунд (по умолчанию).
  • ssl_shutdown_timeout - это время в секундах, необходимое для ожидания завершения отключения SSL перед разрывом соединения. Если нет, то по умолчанию 30,0 секунд.

Изменено в Python 3.7: добавлен параметр ssl_handshake_timeout.

Изменено в версии 3.11: Добавлен аргумент ssl_shutdown_timeout.


Объект Server.

Серверные объекты создаются функциями loop.create_server(), loop.create_unix_server(), start_server() и start_unix_server().

Не создавайте экземпляр класса напрямую.

asyncio.Server:

Серверные объекты asyncio.Server - это асинхронные диспетчеры контекста. При использовании в инструкции async with гарантируется, что когда инструкция async with завершена, то объект Server закрыт и не принимает новые соединения:

srv = await loop.create_server(...)

async with srv:
    # Какой-то код

# На этом этапе `srv` закрыт и больше не принимает новые подключения.

Изменено в версии 3.7: Серверный объект является асинхронным диспетчером контекста, начиная с Python 3.7.

Методы и атрибуты объекта Server:


Server.close():

Метод Server.close() останавливает обслуживание сервера: закрывает прослушивающие сокеты и устанавливает для атрибута сокетов значение None.

Сокеты, которые представляют существующие входящие клиентские подключения, остаются открытыми.

Сервер закрывается асинхронно и чтобы дождаться закрытия сервера - используйте сопрограмму Server.wait_closed().

Server.get_loop():

Метод Server.get_loop() возвращает цикл событий, связанный с серверным объектом.

Новое в Python 3.7.

Server.start_serving():

Метод Server.start_serving() начинает принимать подключения.

Представляет собой сопрограмму.

Этот метод идемпотентен, поэтому его можно вызывать, когда сервер уже обслуживает соединения.

Ключевой аргумент start_serving для методов loop.create_server() и asyncio.start_server() позволяет создать объект Server, который изначально не принимает соединения. В этом случае можно использовать методы Server.start_serving() или Server.serve_forever(), чтобы сервер начал принимать соединения.

Новое в Python 3.7.

Server.serve_forever():

Метод Server.serve_forever() начинает принимать подключения, пока сопрограмма не будет отменена. Отмена задачи serve_forever приводит к закрытию сервера.

Представляет собой сопрограмму.

Этот метод можно вызвать, если сервер уже принимает соединения. На один объект Server может существовать только одна задача Server.serve_forever().

async def client_connected(reader, writer):
    # Communicate with the client with
    # reader/writer streams.  For example:
    await reader.readline()

async def main(host, port):
    srv = await asyncio.start_server(
        client_connected, host, port)
    await srv.serve_forever()

asyncio.run(main('127.0.0.1', 0))

Новое в Python 3.7.

Server.is_serving():

Метод Server.is_serving() возвращает True, если сервер принимает новые соединения.

Новое в Python 3.7.

Server.wait_closed():

Метод Server.wait_closed() ждет завершения метода Server.close().

Представляет собой сопрограмму.

Server.sockets:

Атрибут Server.sockets возвращает список объектов Socket, которые прослушивает сервер.

Изменено в Python 3.7: до Python 3.7 метод Server.sockets использовался для непосредственного возврата внутреннего списка серверных сокетов. В Python 3.7 возвращается копия этого списка.

Пример TCP сервера и клиента .


Пример TCP эхо-сервера.

Создадим TCP эхо-сервер с помощью метода loop.create_server(), отправим обратно полученные данные и закроем соединение:

import asyncio

class EchoServerProtocol(asyncio.Protocol):
    def connection_made(self, transport):
        peername = transport.get_extra_info('peername')
        print('Connection from {}'.format(peername))
        self.transport = transport

    def data_received(self, data):
        message = data.decode()
        print('Data received: {!r}'.format(message))

        print('Send: {!r}'.format(message))
        self.transport.write(data)

        print('Close the client socket')
        self.transport.close()

async def main():
    # Получаем ссылку на цикл событий, т.к. планируем
    # использовать низкоуровневый API.
    loop = asyncio.get_running_loop()

    server = await loop.create_server(
        lambda: EchoServerProtocol(),
        '127.0.0.1', 8888)

    async with server:
        await server.serve_forever()

asyncio.run(main())

Смотрите пример TCP эхо-сервера с использованием потоков stream, который использует высокоуровневую функцию asyncio.start_server().


Пример TCP эхо-клиента.

Пример TCP эхо-клиента использует метод loop.create_connection(), отправляет данные и ждет, пока соединение не будет закрыто:

import asyncio

class EchoClientProtocol(asyncio.Protocol):
    def __init__(self, message, on_con_lost):
        self.message = message
        self.on_con_lost = on_con_lost

    def connection_made(self, transport):
        transport.write(self.message.encode())
        print('Data sent: {!r}'.format(self.message))

    def data_received(self, data):
        print('Data received: {!r}'.format(data.decode()))

    def connection_lost(self, exc):
        print('The server closed the connection')
        self.on_con_lost.set_result(True)

async def main():
    # Получаем ссылку на цикл событий, т.к. планируем
    # использовать низкоуровневый API.
    loop = asyncio.get_running_loop()

    on_con_lost = loop.create_future()
    message = 'Hello World!'

    transport, protocol = await loop.create_connection(
        lambda: EchoClientProtocol(message, on_con_lost),
        '127.0.0.1', 8888)

    # Ждем, пока протокол не подаст сигнал о том, 
    # что соединение потеряно, далее закроем транспорт.
    try:
        await on_con_lost
    finally:
        transport.close()

asyncio.run(main())

Смотрите также пример TCP эхо-клиента с использованием потоков stream, который использует высокоуровневую функцию loop.open_connection().