在下面,我给出了实现基于 Websocket 和 Django 通道的解决方案所需操作的清单,正如之前的评论中所建议的那样。
最后给出了这样做的动机。
1) 连接Websocket,准备接收消息
在客户端,您需要执行以下 javascript 代码:
<script language="javascript">
var ws_url = 'ws://' + window.location.host + '/ws/ticks/';
var ticksSocket = new WebSocket(ws_url);
ticksSocket.onmessage = function(event) {
var data = JSON.parse(event.data);
console.log('data', data);
// do whatever required with received data ...
};
</script>
这里,我们打开Websocket,稍后在onmessage回调中细化服务器发送的通知。
可能的改进:
- 支持 SSL 连接
- 使用 ReconnectingWebSocket:WebSocket API 上的一个小型包装器,可自动重新连接
<script language="javascript">
var prefix = (window.location.protocol == 'https:') ? 'wss://' : 'ws://';
var ws_url = prefix + window.location.host + '/ws/ticks/';
var ticksSocket = new ReconnectingWebSocket(ws_url);
...
</script>
2) 安装和配置 Django Channels 和 Channel Layers
要配置 Django 频道,请按照以下说明操作:
https://channels.readthedocs.io/en/latest/installation.html
Channel Layers 是 Django Channels 的一个可选组件,它提供了我们稍后将使用的“组”抽象;您可以按照此处给出的说明进行操作:
https://channels.readthedocs.io/en/latest/topics/channel_layers.html#
3) 发布 Websocket 端点
路由为 Websocket(和其他协议)提供了已发布端点和相关服务器端代码之间的映射,就像 urlpattens 为传统 Django 项目中的 HTTP 所做的那样
文件routing.py
from django.urls import path
from channels.routing import ProtocolTypeRouter, URLRouter
from . import consumers
application = ProtocolTypeRouter({
"websocket": URLRouter([
path("ws/ticks/", consumers.TicksSyncConsumer),
]),
})
4) 写消费者
Consumer 是一个为 Websocket 标准(也可能是自定义)事件提供处理程序的类。从某种意义上说,它对 Websocket 的作用就像 Django 视图对 HTTP 的作用一样。
在我们的例子中:
- websocket_connect():我们接受连接并将传入客户端注册到“ticks”组
- websocket_disconnect():通过从组中删除 che 客户端进行清理
- new_ticks():我们的自定义处理程序,它将接收到的刻度广播到它的 Websocket 客户端
- 我假设 TICKS_GROUP_NAME 是项目设置中定义的常量字符串值
文件consumers.py:
from django.conf import settings
from asgiref.sync import async_to_sync
from channels.consumer import SyncConsumer
class TicksSyncConsumer(SyncConsumer):
def websocket_connect(self, event):
self.send({
'type': 'websocket.accept'
})
# Join ticks group
async_to_sync(self.channel_layer.group_add)(
settings.TICKS_GROUP_NAME,
self.channel_name
)
def websocket_disconnect(self, event):
# Leave ticks group
async_to_sync(self.channel_layer.group_discard)(
settings.TICKS_GROUP_NAME,
self.channel_name
)
def new_ticks(self, event):
self.send({
'type': 'websocket.send',
'text': event['content'],
})
5) 最后:广播新的报价
例如:
ticks = [
{'symbol': 'BTCUSDT', 'lastPrice': 1234, ...},
...
]
broadcast_ticks(ticks)
地点:
import json
from asgiref.sync import async_to_sync
import channels.layers
def broadcast_ticks(ticks):
channel_layer = channels.layers.get_channel_layer()
async_to_sync(channel_layer.group_send)(
settings.TICKS_GROUP_NAME, {
"type": 'new_ticks',
"content": json.dumps(ticks),
})
我们需要将group_send() 的调用封装在async_to_sync() 包装器中,因为channel.layers 仅提供异步实现,并且我们从同步上下文中调用它。 Django Channels 文档中提供了更多详细信息。
注意事项:
- 确保“type”属性与消费者处理程序的名称匹配(即:'new_ticks');这是必需的
- 每个客户都有自己的消费者;因此,当我们在消费者的处理程序中编写 self.send() 时,这意味着:将数据发送到单个客户端
- 在这里,我们将数据发送到“组”抽象,然后 Channel Layers 将其传递给每个注册的消费者
动机
在某些情况下,轮询仍然是最合适的选择,既简单又有效。
但是,在某些情况下,您可能会遇到一些限制:
- 即使没有可用的新数据,您也会继续查询服务器
- 您引入了一些延迟(在最坏的情况下,整个轮询周期)。权衡是:更少的延迟=更多的流量。
使用 Websocket,您可以改为仅在新数据可用时(并且尽快)通过向客户端发送特定消息来通知客户端。