LINUX.ORG.RU

Нубский вопрос по python/asyncio/aiohttp/websockets

 , ,


0

1

Всем привет!) Пока разобрался с простейшим эхо сервером:

import asyncio
from aiohttp import web

async def ws_handler(request):
   ws_resp = web.WebSocketResponse()
   await ws_resp.prepare(request)

   async for msg in ws_resp:
       await ws_resp.send_str(msg.data)

   return ws_resp

app = web.Application()
app.router.add_get('/ws', ws_handler)

Как в вышеприведенный код грамотно добавить цикл, в котором сервер будет каждые 10 секунд отправлять сообщение клиенту?

# примерный набросок цикла
while True:
    await ws_resp.send_str('message')
    await asyncio.sleep(10)

Тут интересуют два варианта:
1) Для каждого клиента свой частный цикл, и если соединение с этим клиентом по какой-либо причине закрывается, то частный цикл прекращает свою работу;
2) Один независимый цикл для всех клиентов, прекращает работу вместе с завершением приложения

Как в вышеприведенный код грамотно добавить цикл, в котором сервер будет каждые 10 секунд отправлять сообщение клиенту?

Грамотно, никак.

vvn_black ★★★★ ()
def on_startup(app):
   ...
   app['websockets'] = []

...
app = web.Application()
on_startup(app)
async def ws_handler(request):
    ws = web.WebSocketResponse()
    await ws.prepare(request)

    request.app['websockets'].append(ws)

    try:
        async for msg in ws:
            await ...
    except Exception as e:
        pass
    finally:
        request.app['websockets'].remove(ws)

    return ws

И в другой корутине пробегаешься по списку соединений

while True:
    for ws in request.app['websockets']:
        await ws.send_str('ping')
    await asyncio.sleep(10)

vvn_black ★★★★ ()
Последнее исправление: vvn_black (всего исправлений: 3)

Я бы взял второй вариант. В торнадо удобней там есть tornado.ioloop.PeriodicCallback. Но смысл тот же. Я бы сделал так же, как vvn_black выше написал.

См. мою статью на быдлохабре, там это используется.

pawnhearts ★★★★ ()
Последнее исправление: pawnhearts (всего исправлений: 1)
Ответ на: комментарий от vvn_black

А чтобы другая корутина заработала, надо завернуть ее в таску? Вот так будет правильно?:

from aiohttp import web
import asyncio

def on_startup(app):
	app['websockets'] = []

async def send_ping(request):
	while True:
		for ws in request.app['websockets']:
			await ws.send_str('ping')
		await asyncio.sleep(10)

async def ws_handler(request):
	ws = web.WebSocketResponse()
	await ws.prepare(request)

	request.app['websockets'].append(ws)

	loop = asyncio.get_event_loop()
	task = loop.create_task(send_ping(ws))
	loop.run_until_complete(task)

	try:
		async for msg in ws:
			await ws.send_str(msg.data)
	except Exception as e:
			pass
	finally:
		request.app['websockets'].remove(ws)

	return ws

app = web.Application()
app.router.add_get('/ws', ws_handler)
on_startup(app)

a-lexx ()
Ответ на: комментарий от a-lexx

Я бы вот так сделал:

async def send_ping(request):
    while True:
        for ws in request.app['websockets']:
	    await ws.send_str('ping')
        await asyncio.sleep(10)


async def start_workers(app):
    asyncio.ensure_future(send_ping())


def on_startup(app):
    app['websockets'] = []
    app.on_startup.append(start_workers)

P.S. Отступы табами - фуууу таким быть.

vvn_black ★★★★ ()
Ответ на: комментарий от a-lexx

Угу только у task есть метод cancel, тебе его надо дергать наверное когда ws разрывается(там будет исключение, вроде).

Отлаживать это всё не очень удобно.

Ты там в send_ping передаешь одно, а ожидаешь другое вроде как.

P.S. обмажь всё typehints. pycharm будет подсвечивать такую фигню. Я сегодня на работе почти весь код обмазал с помощью monkeytype(ручками, конечно, кое что пришлось править)

pawnhearts ★★★★ ()
Последнее исправление: pawnhearts (всего исправлений: 2)
Ответ на: комментарий от vvn_black
asyncio.ensure_future(send_ping())

ругань на эту строчку идет: TypeError: send_ping() missing 1 required positional argument: 'request'

P.S. Отступы табами - фуууу таким быть.

Что такого в табах?)

зы: как код подсвечивать?

a-lexx ()
Ответ на: комментарий от pawnhearts

обмажь всё typehints

это как? Если что, я с винды на форуме сижу, а код запускается в линукс (чистая консоль, без графики)

Ты там в send_ping передаешь одно, а ожидаешь другое вроде как.

я вообще не понимаю, что там передавать нужно. И почему request.app['websockets'] вместо просто app['websockets'] (которое вроде как из любой функции видно)

a-lexx ()
Ответ на: комментарий от a-lexx

ругань на эту строчку идет:

async def send_ping(app):
    while True:
        for ws in app['websockets']:
	    await ws.send_str('ping')
        await asyncio.sleep(10)


async def start_workers(app):
    asyncio.ensure_future(send_ping(app))


def on_startup(app):
    app['websockets'] = []
    app.on_startup.append(start_workers)
vvn_black ★★★★ ()
Ответ на: комментарий от a-lexx

пашет!

Как пример, быть может. По хорошему, надо закрытие сокетов при остановке сервера добавить и класс типа PeriodicTask написать, с соответствующими методами run, stop etc, и переопределяемым действием.

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

vvn_black ★★★★ ()
Ответ на: комментарий от vvn_black

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

async def start_workers(app):
    asyncio.ensure_future(send_ping(app))
    asyncio.ensure_future(send_ping2(app))
и соответственно добавилась корутина send_ping2.

А задумка вообще такая: в целом будет браузерная игра, но запрос может прийти от админа. Запрос админа обслуживается пока так: идет периодический опрос операционки по разным параметрам, например, запрашивается список установленных tcp-соединений, форматируется и отправляется клиенту(админу) в браузер. Тут еще фишка в том, что эта инфа рассчитана на одного пользователя - на админа, авторизацию я потом добавлю. Поэтому мне нужны циклы, которые привязаны конкретно к обработчику запроса от админа. Как только этот обработчик завершает свою работу, циклы тоже прекращаются.

a-lexx ()