【问题标题】:Whats the proper way to transmit updating data to a client via Django websockets?通过 Django websockets 将更新数据传输到客户端的正确方法是什么?
【发布时间】:2021-06-17 00:48:12
【问题描述】:

目前,我有一个元素,当单击该元素时,它会设置一个全局冷却计时器,该计时器会影响所有使用 Django websockets 的客户端。我的问题是,虽然最初 websocket 值通过componentDidMount 在我的 React 客户端中转换为 state,但当其值实时更改时,websocket 不会再次运行。

这是它的详细工作原理。

计时器通过 django 模型更新,我通过 websocket 将其广播到我的 React 前端:

consumer.py

class TestConsumer(AsyncConsumer):
    async def websocket_connect(self, event):
        print("connected", event)
        await self.send({
            "type":"websocket.accept",
            
        })

        #correct way to grab the value btw, just work on outputting it so its streaming
        @database_sync_to_async
        def get_timer_val():
                val = Timer.objects.order_by('-pk')[0]
                return val.time

        await self.send({
            "type": "websocket.send",
            "text": json.dumps({
                'timer':await get_timer_val(),
        })
        })

    async def websocket_receive(self, event):
        print("received", event)
    
    async def websocket_disconnect(self, event):
        print("disconnected", event)

这最初有效,因为我的 React 客户端启动并将值转换为 state,其中:

component.jsx

//handles connecting to the webclient
componentDidMount() {
    client.onopen = () => {
      console.log("WebSocket Client Connected");
    };
    client.onmessage = (message) => {
      const myObj = JSON.parse(message.data);
      console.log(myObj.timer);
      this.setState({ timestamp: myObj.timer });
    };
  }

//handles submitting the new timer upon clicking on element
handleTimer = () => {
    // handles making PUT request with updated cooldown timer upon submission,
    const timestamp = moment().add(30, "minutes");
    const curr_time = { time: timestamp };

    axios
      .put(URL, curr_time, {
        auth: {
          username: USR,
          password: PWD,
        },
      })
      .then((res) => {
        console.log(res);
      });
  };

//button that prompts the PUT request
<button
 type="submit"
 onClick={(e) => {
 this.handleTimer();
 //unrelated submit function
 this.handleSubmit(e);
 }}
>
Button
</button>

但是,当用户单击被操纵的元素并且数据库模型发生更改时,Web 套接字值直到我刷新页面后才会更改。我认为问题在于我只在连接期间发送 websocket 数据,但我不知道如何保持该“连接”打开,因此任何更改都会自动发送到客户端服务器。我浏览了大量链接以找到实现实时的最佳方式,但其中大多数是关于 socket.io 或实现聊天应用程序的。我要做的就是将 django 模型值实时流式传输到前端。

【问题讨论】:

  • 您好。这会有点复杂 =) 你能否附上整个格式正确的 Consumer 类,以及在元素点击时处理模型更新的代码。如果您已经有一个 websocket 连接工作,它已经“保留”到客户端断开连接,您只需要在模型更新时将数据发送到此连接,我们将在您发布代码后尝试解决这个问题。
  • @AlexandrTatarinov 非常感谢您回复我,我尽可能多地修改了原始帖子的相关细节。你的权利,客户端一直打开,直到它断开连接,但我不知道如何在不刷新的情况下同时发送数据。如果您有任何指示,我将不胜感激。
  • 我终于来了。好的,所以我真的很想看看处理 axios.put(URL, curr_time, 部分的支持代码,但我会尝试在没有它的情况下回答。不过,如果可能,请添加它

标签: reactjs django rest websocket django-rest-framework


【解决方案1】:

当您想将由其他代码触发的更新发送到 websocket 连接时,django-channelschannels 部分会发挥作用。它的工作原理是这样的:

  1. 在连接时,您将 websocket 添加到某个命名组
  2. 当 Timer 的值发生变化时,您通过触发更改的代码将带有特定 type 的事件(通过通道层)发送到该组。
  3. Django-channels 然后为组中的每个 websocket 调用以事件类型命名的 Consumer 方法
  4. 最后,在此方法中,您的代码将消息发送到客户端

您需要使用 Redis 配置通道层。 https://channels.readthedocs.io/en/stable/topics/channel_layers.html

现在,一步一步来。我将省略不相关的部分。

1

async def websocket_connect(self, event):
    await self.send({
        "type":"websocket.accept"
    })
    await self.channel_layer.group_add('timer_observers', self.channel_name)
    

2 这里我在模型中发送事件,但是您可以在视图中执行此操作,或者通过 django 信号执行此操作,但是您想要它。另外我没有检查值是否实际改变,我假设数据库中只有一个 Timer 实例。

from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer

class Timer(models.Model):
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        async_to_sync(get_channel_layer().send)(
            'timer_observers', {"type": "timer.changed"}
        )

3+4 我已经提取了时间发送代码以重复使用它

class TestConsumer(AsyncConsumer):
    async def websocket_connect(self, event):
        print("connected", event)
        await self.send({
            "type": "websocket.accept",
        })
        await self.channel_layer.group_add('timer_observers', self.channel_name)
        await self.send_current_timer()

    async def timer_changed(self, event):
        await self.send_current_timer()

    async def send_current_timer(self):
        @database_sync_to_async
        def get_timer_val():
            val = Timer.objects.order_by('-pk')[0]
            return val.time
        
        await self.send({
            "type": "websocket.send",
            "text": json.dumps({
                'timer': await get_timer_val(),
            })
        })

这里的想法是您处理应用程序生成的内部事件的方式与来自客户端的外部事件相同,即websocket.connect -&gt; async def websocket_connect。所以channels 层有点“发送”给您一个“websocket 消息”,然后您做出响应(但响应实际的客户端)。

我希望这有助于理解这些概念。可能你正在做的是矫枉过正,但我​​认为这只是一个学习练习=) 我不能 100% 确定这会奏效,所以请随时提出其他问题。

【讨论】:

  • 非常感谢您的解释,以及沿途的步骤。我花了几天的时间来实现这个和它的工作。感谢 Alexandr 的帮助。
  • 唷,有那么一刻我以为你已经消失了,永远不会读到这篇文章 =) 很高兴我能提供帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多