私は現在 aiohttp で遊んでおり、WebSocket接続を備えたモバイルアプリのサーバーアプリケーションとしてどのように機能するかを確認しています。
これは単純な「Helloworld」の例です( ここでは要点として ):
import asyncio
import aiohttp
from aiohttp import web
class WebsocketEchoHandler:
@asyncio.coroutine
def __call__(self, request):
ws = web.WebSocketResponse()
ws.start(request)
print('Connection opened')
try:
while True:
msg = yield from ws.receive()
ws.send_str(msg.data + '/answer')
except:
pass
finally:
print('Connection closed')
return ws
if __name__ == "__main__":
app = aiohttp.web.Application()
app.router.add_route('GET', '/ws', WebsocketEchoHandler())
loop = asyncio.get_event_loop()
handler = app.make_handler()
f = loop.create_server(
handler,
'127.0.0.1',
8080,
)
srv = loop.run_until_complete(f)
print("Server started at {sock[0]}:{sock[1]}".format(
sock=srv.sockets[0].getsockname()
))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
loop.run_until_complete(handler.finish_connections(1.0))
srv.close()
loop.run_until_complete(srv.wait_closed())
loop.run_until_complete(app.finish())
loop.close()
ここで、以下に説明する構造を使用したいと思います(node server = python aiohttp)。より具体的には、 Redis Pub/Sub メカニズムを asyncio -redisWebsocketEchoHandlerでWebSocket接続とRedisの両方の読み取りと書き込みを行います。
WebsocketEchoHandlerは完全に単純なループであるため、これをどのように行うべきかわかりません。 Tornado と brükva を使用するコールバックを使用します。
Redis をすでに使用しているので、次の2つのアプローチのどちらを使用する必要がありますか。
http://goldfirestudios.com/blog/136/Horizontally-Scaling-Node.js-and-WebSockets-with-Redis からの画像
明確にする必要があるようです。
Redis Pub/Subハンドラーは次のようになります:
class WebsocketEchoHandler:
@asyncio.coroutine
def __call__(self, request):
ws = web.WebSocketResponse()
ws.start(request)
connection = yield from asyncio_redis.Connection.create(Host='127.0.0.1', port=6379)
subscriber = yield from connection.start_subscribe()
yield from subscriber.subscribe(['ch1', 'ch2'])
print('Connection opened')
try:
while True:
msg = yield from subscriber.next_published()
ws.send_str(msg.value + '/answer')
except:
pass
finally:
print('Connection closed')
return ws
このハンドラーは、Redisチャネルch1およびch2にサブスクライブし、これらのチャネルから受信したすべてのメッセージをWebSocketに送信します。
このハンドラーが欲しい:
class WebsocketEchoHandler:
@asyncio.coroutine
def __call__(self, request):
ws = web.WebSocketResponse()
ws.start(request)
connection = yield from asyncio_redis.Connection.create(Host='127.0.0.1', port=6379)
subscriber = yield from connection.start_subscribe()
yield from subscriber.subscribe(['ch1', 'ch2'])
print('Connection opened')
try:
while True:
# If message recived from redis OR from websocket
msg_ws = yield from ws.receive()
msg_redis = yield from subscriber.next_published()
if msg_ws:
# Push to redis / do something else
self.on_msg_from_ws(msg_ws)
if msg_redis:
self.on_msg_from_redis(msg_redis)
except:
pass
finally:
print('Connection closed')
return ws
ただし、次のコードは常に順番に呼び出されるため、websocketからの読み取りはRedisからの読み取りをブロックします。
msg_ws = yield from ws.receive()
msg_redis = yield from subscriber.next_published()
イベントで読み取りを実行したい。ここでイベントは2つのソースのいずれかから受信したメッセージです。
2つのwhile
ループを使用する必要があります。1つはWebSocketからのメッセージを処理し、もう1つはredisからのメッセージを処理します。メインハンドラーは、2つのコルーチンを開始します。1つは各ループを処理し、次にそれらの両方を待機します。
class WebsocketEchoHandler:
@asyncio.coroutine
def __call__(self, request):
ws = web.WebSocketResponse()
ws.start(request)
connection = yield from asyncio_redis.Connection.create(Host='127.0.0.1', port=6379)
subscriber = yield from connection.start_subscribe()
yield from subscriber.subscribe(['ch1', 'ch2'])
print('Connection opened')
try:
# Kick off both coroutines in parallel, and then block
# until both are completed.
yield from asyncio.gather(self.handle_ws(ws), self.handle_redis(subscriber))
except Exception as e: # Don't do except: pass
import traceback
traceback.print_exc()
finally:
print('Connection closed')
return ws
@asyncio.coroutine
def handle_ws(self, ws):
while True:
msg_ws = yield from ws.receive()
if msg_ws:
self.on_msg_from_ws(msg_ws)
@asyncio.coroutine
def handle_redis(self, subscriber):
while True:
msg_redis = yield from subscriber.next_published()
if msg_redis:
self.on_msg_from_redis(msg_redis)
このようにして、他のソースを気にすることなく、2つの潜在的なソースのいずれかから読み取ることができます。
最近、python 3.5以降でasyncawaitを使用できます。
async def task1(ws):
async for msg in ws:
if msg.type == WSMsgType.TEXT:
data = msg.data
print(data)
if data:
await ws.send_str('pong')
## ch is a redis channel
async def task2(ch):
async for msg in ch1.iter(encoding="utf-8", decoder=json.loads):
print("receving", msg)
user_token = msg['token']
if user_token in r_cons.keys():
_ws = r_cons[user_token]
await _ws.send_json(msg)
coroutines = list()
coroutines.append(task1(ws))
coroutines.append(task2(ch1))
await asyncio.gather(*coroutines)
これは私がしていることです。WebSocketが複数のソースからのメッセージを待つ必要がある場合。
ここでの主なポイントは、asyncio.gatherを使用して、前述の@danoのように2つのコルーチンを一緒に実行することです。