我想在django中使用websocket的主要目的是我们项目中有一个关于log日志的实时推送,这个时候就需要使用websocket把我们数据库的日志推送到前端去
这里我使用的模块是channels,这里有个坑需要注意,如果直接pip install的话下载的是最新版本的,会可能与你的django版本不匹配,所以如果你的django是3版本的,那么就需要下载对应3版本的channels,否则asgi可能会启动不起来
pip install channels==3.0.3
配置
1.首先需要有一个websocket的app,我们需要在项目中创建一个app(app的名字随意)
python .\manage.py startapp web_socket
2.修改settings.py(app里面要新增)
INSTALLED_APPS = [
'channels',
'web_socket'
]
添加asgi
WSGI_APPLICATION = 'account_book.wsgi.application'
# websocket
ASGI_APPLICATION = 'account_book.asgi.application'
3.修改asgi.py
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'account_book.settings')
# websocket新加的
from channels.routing import ProtocolTypeRouter, URLRouter
from . import routing
# application = get_asgi_application()
# websocket
application = ProtocolTypeRouter({
# 处理http请求
'http': get_asgi_application(),
# 处理websocket请求
'websocket':URLRouter(routing.websocket_urlpatterns)
})
4.在项目settings同级创建routing.py,用来存放websocket路由
from django.urls import re_path
from web_socket import consumers
# 示例路由
websocket_urlpatterns = [
re_path(r'ws/(?P<group>\w+)/$', consumers.ChatConsumer.as_asgi())
]
5.我们这里还需要使用在web_socket app中有一个路由匹配的函数,新建consumers.py,这是基于同步方式,但是会出现堵塞http的进程
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from .models import Book
import json
import asyncio
class ChatConsumer(WebsocketConsumer):
def websocket_connect(self, message):
# 接受客户端连接
# 服务端允许和客户端创建连接
print('有连接进入')
self.accept()
def websocket_receive(self, message):
# 如果浏览器基于websocket向后端发送数据,接收客户端传过来的消息
# 发送消息
print('接受到消息:', message)
# 接受到消息: {'type': 'websocket.receive', 'text': '你好'} 字典可以直接取出来
# 向后端发送消息
# self.send('Hello ' + message['username'])
# 如果服务端向主动断开连接
if message['text'] == '关闭':
self.close()
# 如果服务端断开连接不想执行websocket_disconnect,那么可以直接执行raise StopConsumer(),不return
return
def websocket_disconnect(self, close_code):
# 这里是停止
raise StopConsumer()
这样我们就直接可以启动我们的django,他会变成一个这样的形式
System check identified no issues (0 silenced).
February 24, 2024 - 17:47:12
Django version 3.2.2, using settings 'account_book.settings'
Starting ASGI/Channels version 3.0.3 development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
前端
前端可以通过产生websocket对象来进行websocket连接,这样可以通过websocket进行数据的交互,简单的示例如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<p>index页面</p>
<style>
.messsage {
height: 80px;
border: 1px solid #dddd;
width: 100%;
}
</style>
</head>
<body>
<div class="messsage" id="message"></div>
<div>
<input type="text" pattern="请输入">
<input type="button" onclick="sendMessage()" value="发送">
</div>
<script>
socket = new WebSocket('ws://127.0.0.1:8000/ws/123/')
// 创建好连接之后自动触发(服务端执行self.accept(),会触发)
socket.onopen = function () {
}
// 当websocket接收到服务端发送的消息时,自动会触发这个函数
socket.onmessage = function (event) {
console.log(event.data)
}
// 断开连接,服务端接收到会自动执行websocket_disconnect
// socket.close()
// 服务端主动断开连接时,这个方法也会被处罚
socket.onclose = function (){}
function sendMessage() {
socket.send('你好')
}
</script>
</body>
</html>
这样就可以写一个简单的websocket连接了,我们可以通过前端连接websocket,后端会打印有有连接进入,然后也可以触发前端的回调进行数据的回去
目的功能的视线:
我想实现的不停给前端推送某个数据库,之前的代码一直会堵塞住htpp,这样就会产生一个问题,http没办法通过,所以采用异步的方式
后端代码:
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from .models import Book
# 这里是将数据库的查询操作变为异步的操作
@sync_to_async
def get_book_list():
return list(Book.objects.all())
class ChatConsumer(AsyncWebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.connected = False # 增加一个标志来表示是否有连接进入
async def connect(self):
# 接受客户端连接
await self.accept()
# 启动异步任务
asyncio.create_task(self.send_book_list_periodically())
print('有连接进入')
# await self.channel_layer.group_add("book_update", self.channel_name)
self.connected = True # 设置连接标志为True
async def send_book_list_periodically(self):
while self.connected:
book_list = await get_book_list() # 使用异步方式获取书籍列表
send_list = [{'name': book.name, 'price': book.price} for book in book_list]
send_list_json = json.dumps(send_list)
await self.send(text_data=send_list_json)
# 休眠5秒
await asyncio.sleep(5)
async def disconnect(self, close_code):
# 连接断开时的处理
self.connected = False # 设置连接标志为False
print('这里关闭了页面')
raise StopConsumer()
上面就可以实现不停的推送数据库的消息,但是这里要注意,如果前端页面在关闭的时候并没有关闭我们的websocket,他进入其他页面还是会继续推送,所以需要我们在前端结束的时候进行断开连接,我是在vue里面写的,如下在mounted里面进行连接的建立,在beforeDestroy 进行websocket连接的关闭
mounted() {
this.socket = new WebSocket('ws://127.0.0.1:8000/ws/123/')
this.socket.onmessage = function (event) {
console.log(event.data)
}
},
beforeDestroy () { // 在组件销毁前关闭 WebSocket 连接
if (this.socket) {
this.socket.close()
}
},
这样可以基本实现一个这样方式
同步堵塞大概的猜测,具体又看了一下同步的请求,发现前端不变的情况下,同步关闭了页面但是并没有触发到后端websocket的关闭,导致这个websocket一直在连接状态,所以会将其他的http请求进行堵塞,暂时想不到解决办法