Python 如何跨多个服务器/工作者管理WebSocket

Python 如何跨多个服务器/工作者管理WebSocket,python,websocket,python-3.5,channels,aiohttp,Python,Websocket,Python 3.5,Channels,Aiohttp,具有对的内置支持。它非常简单,效果也很好 文档中示例的简化版本为: async def handler(request): ws = web.WebSocketResponse() await ws.prepare(request) # Async iterate the messages the client sends async for message in ws: ws.send_str('You sent: %s' % (message

具有对的内置支持。它非常简单,效果也很好

文档中示例的简化版本为:

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

    # Async iterate the messages the client sends
    async for message in ws:
        ws.send_str('You sent: %s' % (message.data,))

    print('websocket connection closed')
在本例中,
ws
是对与客户端的websocket连接的引用。我可以很容易地将这些引用放入
request.app
,比如(即全局状态),但不能放在生产应用中,因为每个应用服务器(甚至每个工作人员)都有自己的
app
实例

这有一个公认的模式吗?还有别的办法吗


注意:我指的不是会话。我指的是关系。当服务器B中的应用程序代码中发生事件时,我想向连接到服务器a的客户端发送一条消息。

因此我只熟悉节点中的Socket.IO,但使用Socket.IO水平扩展WebSocket是相当容易的

套接字可以与会话一起提供,因此每个会话都由特定的服务器管理。这使得保存每个打开的套接字的状态以及所有服务器的负载平衡变得很容易

下面是用于Python的SocketIO:

这是一本非常好的书,介绍了如何将会话附加到redis商店,使其更快,并使服务器间的负载平衡更易于管理

我知道这并不能回答您关于aiohttp的问题,但希望这能让您更好地了解套接字是如何工作的

编辑: 写入节点-

在Socket.IO中,这真的很简单,它有大量的功能以各种不同的方式广播消息

例如,如果您希望向每个聊天室中的每个人发送消息。示例:打开套接字的每个人都可以轻松地编写

socket.broadcast.emit('WARNING', "this is a test");
假设您有开放的房间,您可以通过一个名为
.to()
的简单函数仅向该房间中的人广播消息。例如,我有一间名为“BBQ”的房间:

socket.broadcast.to('BBQ').emit('invitation', 'Come get some food!');
这将向BBQ频道的每个人传达信息-来吃点东西吧

编辑:编辑:

这是一篇关于Socket.IO工作原理的精彩文章,请务必阅读更新版本函数的第二个答案。它比他们的文档更容易阅读

据我所知,这也是它在python实现中的工作方式。为了便于使用,我当然会将其用于WebSocket。aiohttp似乎非常强大,但要么没有此功能,要么隐藏在文档中,要么只在代码中编写,而没有任何文档。

更新(2017年2月) 幸运的是,频道没有合并到Django中。它可能仍然是一个伟大的项目,但它并不真正属于Django

另外,我强烈建议您看看Postgres相对较新的内置pub/sub支持。它会的,在aiohttp之上构建一个定制解决方案,使用Postgres作为支持服务,可能是您最好的选择

起初的 虽然不是aiohttp,但它很可能被合并到中,以一种非常直观的方式解决了这个问题,它是由Django的作者写的

Django通道通过在Django应用程序前面创建一个路由层来抽象“多服务器上的多进程”的概念。此路由层与后端(例如Redis)通信,以在进程之间保持可共享状态,并使用新协议来方便处理HTTP请求和WebSocket,同时将它们各自委托给各自的“”(例如,附带用于HTTP请求的内置处理程序,您可以为WebSocket编写自己的处理程序)

Django频道有一个称为的概念,它处理问题的“广播”性质;也就是说,它允许服务器上发生的事件触发发送给该组中的客户端的消息,而不管它们是否连接到相同或不同的进程或服务器


总之,Django通道很可能被抽象为一个更通用的Python库。有很多方法可以实现,但在撰写本文时,没有任何值得注意的方法可以提供网络透明度;通道在进程和服务器之间通信的能力。

如果我理解正确,您希望有多个websocket服务器,每个服务器都连接有多个客户端,但您希望能够与所有连接的客户端进行潜在通信

下面是一个示例,它创建了三个简单的服务器——一个大写回音、一个随机引用和一天中的时间——然后向所有连接的客户端发送广播消息。也许这里面有一些有用的想法

巴斯德宾:


这不是我要问的。会话可以很容易地管理-我指的是与客户端的实际连接。换句话说,如果服务器A中发生事件,我如何向已连接到服务器B的客户端发送消息。即使您有服务器c和服务器d,您是否希望专门为连接到服务器B的用户发送消息,或者你想向所有可能登录到你的服务器的客户端发送事件吗?想想像聊天服务器这样的东西-如果连接到服务器a的用户发送消息,那么我需要将消息发送给同一聊天室中的用户,即使其中一些用户连接到服务器B、C等。这有意义吗?啊,好的,我可以用Node SocketIO实现来编辑这个问题的答案,但我不知道这对aiohttp是否有帮助。我从未仅在节点中使用过针对python的aiohttp或WebSocket。我道歉。使用SocketIO,如果你想添加另一个库来制作websockets,这很容易。啊,实际上这会很有帮助,因为我也知道Node。嗨@rharder,我希望我的100多个客户端将base64映像发送到jetson nano板上运行的单个服务器。所以我希望异步发生。那么你能告诉我上面的代码片段是否适合我的项目吗?@saddambinsed是的,我想你可以用这个。
#!/usr/bin/env python3
"""
Illustrates how to have multiple websocket servers running and send
messages to all their various clients at once.

In response to stackoverflow question:
https://stackoverflow.com/questions/35820782/how-to-manage-websockets-across-multiple-servers-workers

Pastebin: https://pastebin.com/xDSACmdV
"""
import asyncio
import datetime
import random
import time
import webbrowser

import aiohttp
from aiohttp import web

__author__ = "Robert Harder"
__email__ = "rob@iharder.net"
__license__ = "Public Domain"


def main():
    # Create servers
    cap_srv = CapitalizeEchoServer(port=9990)
    rnd_srv = RandomQuoteServer(port=9991)
    tim_srv = TimeOfDayServer(port=9992)

    # Queue their start operation
    loop = asyncio.get_event_loop()
    loop.create_task(cap_srv.start())
    loop.create_task(rnd_srv.start())
    loop.create_task(tim_srv.start())

    # Open web pages to test them
    webtests = [9990, 9991, 9991, 9992, 9992]
    for port in webtests:
        url = "http://www.websocket.org/echo.html?location=ws://localhost:{}".format(port)
        webbrowser.open(url)
    print("Be sure to click 'Connect' on the webpages that just opened.")

    # Queue a simulated broadcast-to-all message
    def _alert_all(msg):
        print("Sending alert:", msg)
        msg_dict = {"alert": msg}
        cap_srv.broadcast_message(msg_dict)
        rnd_srv.broadcast_message(msg_dict)
        tim_srv.broadcast_message(msg_dict)

    loop.call_later(17, _alert_all, "ALL YOUR BASE ARE BELONG TO US")

    # Run event loop
    loop.run_forever()


class MyServer:
    def __init__(self, port):
        self.port = port  # type: int
        self.loop = None  # type: asyncio.AbstractEventLoop
        self.app = None  # type: web.Application
        self.srv = None  # type: asyncio.base_events.Server

    async def start(self):
        self.loop = asyncio.get_event_loop()
        self.app = web.Application()
        self.app["websockets"] = []  # type: [web.WebSocketResponse]
        self.app.router.add_get("/", self._websocket_handler)
        await self.app.startup()
        handler = self.app.make_handler()
        self.srv = await asyncio.get_event_loop().create_server(handler, port=self.port)
        print("{} listening on port {}".format(self.__class__.__name__, self.port))

    async def close(self):
        assert self.loop is asyncio.get_event_loop()
        self.srv.close()
        await self.srv.wait_closed()

        for ws in self.app["websockets"]:  # type: web.WebSocketResponse
            await ws.close(code=aiohttp.WSCloseCode.GOING_AWAY, message='Server shutdown')

        await self.app.shutdown()
        await self.app.cleanup()

    async def _websocket_handler(self, request):
        assert self.loop is asyncio.get_event_loop()
        ws = web.WebSocketResponse()
        await ws.prepare(request)
        self.app["websockets"].append(ws)

        await self.do_websocket(ws)

        self.app["websockets"].remove(ws)
        return ws

    async def do_websocket(self, ws: web.WebSocketResponse):
        async for ws_msg in ws:  # type: aiohttp.WSMessage
            pass

    def broadcast_message(self, msg: dict):
        for ws in self.app["websockets"]:  # type: web.WebSocketResponse
            ws.send_json(msg)


class CapitalizeEchoServer(MyServer):
    """ Echoes back to client whatever they sent, but capitalized. """

    async def do_websocket(self, ws: web.WebSocketResponse):
        async for ws_msg in ws:  # type: aiohttp.WSMessage
            cap = ws_msg.data.upper()
            ws.send_str(cap)


class RandomQuoteServer(MyServer):
    """ Sends a random quote to the client every so many seconds. """
    QUOTES = ["Wherever you go, there you are.",
              "80% of all statistics are made up.",
              "If a tree falls in the woods, and no one is around to hear it, does it make a noise?"]

    def __init__(self, interval: float = 10, *kargs, **kwargs):
        super().__init__(*kargs, **kwargs)
        self.interval = interval

    async def do_websocket(self, ws: web.WebSocketResponse):
        async def _regular_interval():
            while self.srv.sockets is not None:
                quote = random.choice(RandomQuoteServer.QUOTES)
                ws.send_json({"quote": quote})
                await asyncio.sleep(self.interval)

        self.loop.create_task(_regular_interval())

        await super().do_websocket(ws)  # leave client connected here indefinitely


class TimeOfDayServer(MyServer):
    """ Sends a message to all clients simultaneously about time of day. """

    async def start(self):
        await super().start()

        async def _regular_interval():
            while self.srv.sockets is not None:
                if int(time.time()) % 10 == 0:  # Only on the 10 second mark
                    timestamp = "{:%Y-%m-%d %H:%M:%S}".format(datetime.datetime.now())
                    self.broadcast_message({"timestamp": timestamp})
                await asyncio.sleep(1)

        self.loop.create_task(_regular_interval())


if __name__ == "__main__":
    main()