通过 Django websockets 将更新数据传输到客户端的正确方法是什么?

Whats the proper way to transmit updating data to a client via Django websockets?

目前,我有一个元素,单击该元素会设置一个全局冷却计时器,该计时器会影响使用 Django websockets 的所有客户端。我的问题是,虽然最初 websocket 值在我的 React 客户端中通过 componentDidMount 转换为 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 模型值流式传输到前端。

当您想将由其他代码触发的更新发送到 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 -> async def websocket_connect。所以 channels 层有点“发送”给你一个“websocket 消息”,然后你响应(但对实际的客户端)。

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