本文在 django 中实现 websocket 协议。
在 django 中使用 websocket
纯净的 django 是不支持 websocket 的,要想实现 websocket 协议,我们需要更改一些配置。
Step1: 安装第三方包
第三方包channels
提供了在 django 中实现 websocket 通信协议的方式。我们安装这个包:
然后,我们前往settings.py
下注册这个 app:
1 | INSTALLED_APPS = [ |
一定要注意这里的逗号,千万不要遗漏,否则 django 会将没有被逗号分隔开的两个应用视作一个应用。
Step2: 配置 asgi
前往项目的settings.py
下配置 asgi:
1 | WSGI_APPLICATION = '{ProjectName}.wsgi.application' |
然后前往asgi.py
下更改默认配置,删除全部内容改写为:
1 | """ |
Step3: 配置路由和视图类
在settings.py
同级目录下新建routings.py
,其功能相当于urls.py
,专门负责 websocket 协议的路由:
1 | """ |
然后,前往需要使用到 websocket 的应用下新建consumers.py
,其功能相当于views.py
:
1 | from channels.generic.websocket import WebsocketConsumer |
以上是默认的结构,当然,你可以在routings.py
中配置多个路由,然后在consumers.py
中声明多个类。
运行程序并检查
此时我们运行程序,发现控制台输出变成了:
1 | August 02, 2022 - 16:12:48 |
说明此时 django 项目同时支持 http 协议和 websocket 协议了。
前后端数据交互
后端
后端对于 websocket 的操作相对较少,大多是对于数据的处理。主要用到的是self.send()
和self.close()
。
self.send()
,向建立连接的客户端发送数据,参数即为准备发送的数据。
self.close()
,服务端主动与客户端关闭连接。该函数被执行后,需要追加return
停止函数继续运行。客户端接收到断开连接的请求后,客户端断开连接,同时,由于断开连接,服务端会自动执行websocket_disconnect()
函数,然后抛出异常StopConsumer()
,终止 websocket 连接。
前端
创建 websocket 连接
前端可以主动与目标 url 创建 websocket 连接。首先需要新建一个WebSocket
对象,初始化参数是目标 url。
1 | ws = new WebSocket("ws://127.0.0.1:8000/room/123/"); |
该请求匹配到后端的路由:
1 | path("room/<int>/",consumers.ChatRoom.as_asgi()) |
于是执行
1 | """ |
此时,前端可以使用ws.send()
发送数据了。
回调函数
当发生连接请求、传递数据、断开连接请求时,后端都有对应的函数会自动执行,前端也如此。这被称为回调函数。回调函数在满足条件后自动触发,JavaScript 中可以为 websocket 绑定三种回调函数。
如果ws
是 websocket 的一个实例化对象,那么可以使用ws.onopen
、ws.onmessage
、ws.onclose
绑定函数。例如:
1 | ws.onmessage = function(event){ |
上述函数在后端主动发送数据后被自动执行,结果是向 id 为msg
的块级内容写入新的元素。其中,event
封装了后端发送的所有数据,利用event.data
可以提取出数据。
更多地,onopen
发生在建立连接后,onclose
发生在收到后端关闭连接请求后。
多客户端的管理:聊天室为例
更改配置文件
使用 channel_layers,可以对同时连接的多个客户端进行管理。相关的数据需要被写入内存当中,因此需要在配置文件中添加:
1 | """ |
将新连接的客户端写入内存中
我们需要将新建立的连接写入内存。对实例对象调用channel_layer.group_add()
方法。例如:
1 | from asgiref.sync import async_to_sync |
其中,由于上述方法只支持异步,然而我们并没有编写异步代码,因此需要使用同步的方式完成该操作。asnyc_to_sync
是将方法由异步转变成为同步。
获得新消息时,对同一组内所有连接群发消息,可以使用group_send()
完成。例如:
1 | def websocket_message(self, message): # 对组名中所有连接对象调用方法名所对应的方法,字典作为参数传入 |
如果方法定义为:
1 | def {方法名}(self, event): |
也就意味着对群组内所有连接都发送message['text']
。
断开连接时,需要从组内同时剔除该连接。可以使用:
1 | def websocket_disconnect(self, message): |
这样,当连接终止时,自动触发websocket_disconnect
,该连接就从 group 中被剔除了。