async socket python как асинхронно дождаться возможности писать/читать сокет

Играюсь с socket. С обычной версией я вроде разобрался и теперь хочу написать асинхронную. Например простой эхо сервер. Есть код:

    async def _run(self) -> None:
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.bind((settings.HOST, settings.PORT))
        self.server_socket.listen(8)
        self.server_socket.setblocking(False)

        loop = asyncio.get_event_loop()

        while True:
            client, address = await loop.sock_accept(self.server_socket)
            print(f"Connected {address}")
            loop.create_task(self.handle_client(client, address))

    async def handle_client(self, client_socket: socket.socket, address: tuple) -> None:
        while True:
            data = client_socket.recv(1024).decode(encoding=settings.ENCODING)  # BlockingIOError: [WinError 10035] Операция на незаблокированном сокете не может быть завершена немедленно
            if not data:
                break
            client_socket.send(data.upper().encode(encoding=settings.ENCODING))
            print(data)

Он ломается с ошибкой:

BlockingIOError: [WinError 10035] Операция на незаблокированном сокете не может быть завершена немедленно

Я примерно понимаю почему: стоит self.server_socket.setblocking(False) для реализации асинхронности и клиент ещё не прочитал прошлые данные или просто ещё не готов. Если перед client_socket.recv поставить time.sleep, то всё начинает работать, но это явно не решение.

Как мне асинхронно (чтобы другие клиенты в этот момент могли обрабатываться) дождаться момента, когда я смогу прочитать/написать что-либо?

P.S. Я знаю про asyncore, но хочу написать всё с нуля и понять как всё работает изнутри


Ответы (1 шт):

Автор решения: Trizalio

Можно сделать пример рабочим, если заменить
client_socket.recv(1024)
на
(await asyncio.get_event_loop().sock_recv(client_socket, 1024)).
(получение loop'а лучше вынести)
Что происходит внутри - можно посмотриеть в исходниках CPython
Если коротко, то операционная система предоставляет возможность для файлового дескриптора(в сокете он тоже присутствует) получать события, в том числе событие возможности чтения.
Делается это с помощью селектора(Poll, Epoll, ...). При этом селектору передаётся за какими файловыми дескрипторами и событиями надо следить.

→ Ссылка