【问题标题】:Bi-directional communication with sanic websockets without awaiting forever与 sanic websockets 进行双向通信,无需永远等待
【发布时间】:2019-08-13 03:21:13
【问题描述】:

我向these questions 询问了有关 sanic 中的 websockets 的问题。这是后续行动。

下面让我向我的 websocket 客户端广播消息:

from sanic import Sanic, response
from sanic.websocket import WebSocketProtocol
import asyncio
import time

app = Sanic()

@app.websocket('/feed')
async def feed(request, ws):
    while True:
        now = time.time()
        data = f'message: {now}'
        print(data)
        await ws.send(data)
        await asyncio.sleep(1)

@app.route('/')
async def handle_request(request):
    return response.html("""
    <html><head>
    <script>
    const ws = new WebSocket("ws://" + location.host + '/feed');
    ws.onmessage = event => {
      let e = event;
      console.log(e.data);
      let out = document.getElementById('out');
      out.innerHTML += `<li><p>${e.data}</p></li>`;
    }
    document.querySelector('form').addEventListener('submit', (event) => {
      event.preventDefault();
      let message = document.querySelector("#in").value;
      ws.send(message);
      document.querySelector("#in").value = "";
    })
    </script>
    </head>
      <body><h1>Main</h1>
        <div id="in"><form><input type="text" method="post"></form></div>
        <div><ul id="out"></ul></div>
     </body>
    </html>
    """)

app.run(host="0.0.0.0", port=8000)

我可以确认服务器会定期向客户端发送消息,并且能够接收某种类型的消息返回。

在服务器上:

[2019-08-12 22:06:27 -0500] - (sanic.access)[INFO][127.0.0.1:49028]: GET http://localhost:8000/  200 714
message: 1565665587.4367297
message: 1565665588.4373734
message: 1565665589.4389973
message: 1565665590.440603
message: 1565665591.4414358
message: 1565665592.441888
message: 1565665593.443465
[2019-08-12 22:06:34 -0500] - (sanic.access)[INFO][127.0.0.1:49036]: GET http://localhost:8000/  200 714
message: 1565665594.362771
message: 1565665595.3643198
message: 1565665596.3655813
message: 1565665597.3671694

在客户端:

但是,由于以下几个原因,这对我来说没有意义:

  1. 当我提交表单时,我实际上看不到表单的内容 提交,只是有一个GET请求。
  2. 我看到一个 GET 请求,但我的表单明确显示 POST。
  3. event.preventDefault() 已使用,但我仍然看到表单提交时整页刷新。

而且,我真正想要的是双向通信。所以,除了刚才的ws.send(data),我还要去方法中的new = await ws.recv(),捕获输入。

@app.websocket('/feed')
async def feed(request, ws):
    while True:
        now = time.time()
        data = f'message: {now}'
        print(data)
        await ws.send(data)
        new = await ws.recv()  # this is the only change
        print(new)
        await asyncio.sleep(1)

但现在,我不再连续向客户端发送数据。相反,进程挂起,等待我的ws.recv(),即使客户端没有发送任何内容。

[2019-08-12 22:13:52 -0500] [12920] [INFO] Starting worker [12920]
[2019-08-12 22:13:56 -0500] - (sanic.access)[INFO][127.0.0.1:49086]: GET http://localhost:8000/  200 714
message: 1565666037.0688074
[2019-08-12 22:14:03 -0500] - (sanic.access)[INFO][127.0.0.1:49090]: GET http://localhost:8000/  200 714

在我再次提交表单之前,不会再发送message

如何进行双向通信,将数据传回/feed 端点,但无需等待即可连续进行?

【问题讨论】:

    标签: javascript python websocket sanic python-asyncio


    【解决方案1】:

    你需要分离消费者和生产者,有一个例子如何做到这一点:

    from sanic import Sanic, response
    import asyncio
    import time
    
    app = Sanic()
    
    
    async def _consumer_handler(ws):
        print('consumer_handler')
        while True:
            message = await ws.recv()
            print('message arrived', message)
    
    
    async def _producer_handler(ws):
        print('producer_handler')
        while True:
            now = time.time()
            data = f'message sent: {now}'
            print(data)
            await ws.send(data)
            await asyncio.sleep(1)
    
    
    @app.websocket('/feed')
    async def feed(request, ws):
        consumer_task = asyncio.ensure_future(
            _consumer_handler(ws))
        producer_task = asyncio.ensure_future(
            _producer_handler(ws))
        done, pending = await asyncio.wait(
            [consumer_task, producer_task],
            return_when=asyncio.FIRST_COMPLETED,
        )
        for task in pending:
            task.cancel()
    
    
    @app.route('/')
    async def handle_request(request):
        return response.html("""
        <html><head>
        </head>
          <body><h1>Main</h1>
            <div><form><input id="in" type="text" method="post"></form></div>
            <div><ul id="out"></ul></div>
         </body>
         <script>
        const ws = new WebSocket("ws://" + location.host + '/feed');
        ws.onmessage = event => {
          let e = event;
          console.log(e.data);
          let out = document.getElementById('out');
          out.innerHTML += `<li><p>${e.data}</p></li>`;
        }
        document.querySelector('form').addEventListener('submit', (event) => {
          event.preventDefault();
          let message = document.querySelector("#in").value;
          ws.send(message);
          document.querySelector("#in").value = "";
        })
        </script>
        </html>
        """)
    
    app.run(host="0.0.0.0", port=8000)
    

    要在消费者和生产者之间进行通信,您需要使用 pubsub,或者定期检查新消息(并且您需要将消息存储在某个地方,例如可以使用 redis)。

    一些有用的链接:

    【讨论】:

    • 当我提交表单时,后端收到此错误:future: &lt;Task finished coro=&lt;_consumer_handler() done, defined at main.py:15&gt; exception=ConnectionClosed('WebSocket connection is closed: code = 1001 (going away), no reason')&gt; Traceback (most recent call last) ... in _consumer_handler 并且不检索数据。
    • 似乎页面在表单提交时重新加载,这会导致错误。我的电脑上没有这个问题,您是否在没有任何修改的情况下运行了我的代码?请注意,我将脚本标签移到了 html 的末尾(这可能是错误的原因)
    • async def feed(request, ws): await asyncio.gather(_consumer_handler(ws), _producer_handler(ws)) 也可以。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-19
    • 2013-11-24
    • 2012-02-24
    • 2013-05-11
    • 1970-01-01
    • 1970-01-01
    • 2019-08-13
    相关资源
    最近更新 更多