学习视频:
目录
[Websocket 连接不了?](#Websocket 连接不了?)
[settings 配置](#settings 配置)
[consumer 配置](#consumer 配置)
Websocket 连接不了?
基于这篇博客:
【全栈开发】---- 一文掌握 Websocket 原理,并用 Django 框架实现_django websocket-CSDN博客
之前这篇博客虽然大致原理都介绍了,但最终的代码并没有实现,这是因为博主当时遇见了一个问题,尽管我按照教程来的,但是 websocket 服务就是连不上,后面也参考了许多博客,也去官网看了,还去 github 上抄项目来对比,都解决不了,后来急得我转 SpringBoot 去了。但偶然间发现了这篇博客:
https://blog.csdn.net/qq_25218219/article/details/131752459Django的websocket
最终问题才得以解决,再次感谢这位博主!!!
++解决办法很简单,基于上面学习视频的配置后,需要在注册组件的 "channels" 前面添加一个组件 "daphne"++
python
INSTALLED_APPS = [
"daphne",
"channels",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"app01.apps.App01Config"
]
然后运行就能连上 asgi 了:

大致原因是 pip install channels 按照命令默认按照的是最新版的 channels ,可能与 Django 版本并不匹配。
收发数据
在 websocket 模式中,服务端和客户端都能主动收发数据:
在客户端发数据:
javascript
function sendMessage(){
var txt = document.getElementById("txt")
console.log(txt.value)
socket.send(txt.value)
}
在服务端收数据:
javascript
def websocket_receive(self, message):
# 收数据message
print("接收消息-->",message["text"])
在服务端发数据:
使用 send() 方法即可
javascript
def websocket_connect(self,message):
print("发送连接请求")
self.accept()
# 发数据
self.send("来了呀客官")
def websocket_receive(self, message):
# 收数据message
print("接收消息-->",message["text"])
self.send(message["text"])
在客户端收数据:
这里的服务端发数据在发送 websocket 连接函数和接收消息函数中都可,相对于,在客户端收数据也对应两种方法,一个是 socket.onopen , 创建好连接后自动触发(握手环节,服务端执行self.accept() );还有一个就是 socket.onmessage,用于正常接收数据。
javascript
socket.onopen = function(event){
console.log(event.value)
let lag = document.createElement("div")
lag.innerText = "[websocket连接成功]"
document.getElementById("message").appendChild(lag)
}
{#收数据#}
socket.onmessage = function (event){
var data = event.data
console.log("客户端接收到消息-->",data)
let lag = document.createElement("div")
lag.innerText = data
document.getElementById("message").appendChild(lag)
}
断开连接
在服务端断开连接一般是经过下面这个函数:
javascript
def websocket_disconnect(self, message):
# 浏览器关闭也会自动发送断开链接请求
print("断开连接")
# 服务端同意断开连接
raise StopConsumer()
这个函数不仅仅关闭浏览器的请求链接,还会关闭服务端链接,实现完全断连。在类中其他函数中可使用self.close() 来调用此关闭链接函数,实现完全断连;而如果用 raiseStopConsumer() , 则表示仅仅断开服务器连接,也不会执行 websocket_disconnect 函数。
服务器断开连接时,客户端也会触发一个函数:
javascript
socket.onclose = function (event){
}
并且客户端也可以设置按钮,主动断开连接:
javascript
function closeOnn(){
socket.close()
}
完整代码
index.html:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.message{
height: 300px;
width: 100%;
border: 1px solid #dddddd;
}
</style>
</head>
<body>
<div class="message" id="message"></div>
<div>
<input type="text" placeholder="请输入" id="txt">
<input type="button" value="发送" onclick="sendMessage()">
<input type="button" value="断开连接" onclick="closeOnn()">
</div>
<script>
socket = new WebSocket("ws://127.0.0.1:8080/room/123/")
{#创建好连接后自动触发(握手环节,服务端执行self.accept())#}
socket.onopen = function(event){
console.log(event.value)
let lag = document.createElement("div")
lag.innerText = "[websocket连接成功]"
document.getElementById("message").appendChild(lag)
}
{#发数据#}
function sendMessage(){
var txt = document.getElementById("txt")
console.log(txt.value)
socket.send(txt.value)
}
{#收数据#}
socket.onmessage = function (event){
var data = event.data
console.log("客户端接收到消息-->",data)
let lag = document.createElement("div")
lag.innerText = data
document.getElementById("message").appendChild(lag)
}
{#服务器主动断开连接,触发#}
socket.onclose = function (event){
}
function closeOnn(){
socket.close()
}
</script>
</body>
</html>
python
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
# socket = new WebSocket("ws://127.0.0.1:8000/room/123/")
class ChatConsumer(WebsocketConsumer):
print("进入消费者")
def websocket_connect(self,message):
print("发送连接请求")
self.accept()
# 发数据
self.send("来了呀客官")
def websocket_receive(self, message):
# 收数据message
print("接收消息-->",message["text"])
# 服务器主动断开连接
if message["text"] == "关闭":
self.close()
# 如果在这儿加上下面代码,执行StopConsumer异常,那么就不会执行websocket_disconnect
raise StopConsumer()
# return
self.send(message["text"])
# 调用self.close()方法默认都会调用下面这个函数
def websocket_disconnect(self, message):
# 浏览器关闭也会自动发送断开链接请求
print("断开连接")
# 服务端同意断开连接
raise StopConsumer()
聊天室的实现
当然,上面只是介绍 websocket 的一般使用,还并没有实际应用,下面将以聊天室场景进行应用。
聊天室一
前面的操作都是基于 self 来的。服务端仅仅关心自己与对应浏览器的连接通道,而不会联系到其它浏览器。可使用列表存储各个用户,某用户想断开连接或者主动退出浏览器时,再到列表中删除用户:
需要注意的是,用户添加到列表中后,后续的一系列操作需要在列表中循环操作每一个对象,以实现群聊
python
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
CONN_LIST = []
class ChatConsumer(WebsocketConsumer):
print("进入消费者")
def websocket_connect(self,message):
print("发送连接请求")
self.accept()
CONN_LIST.append(self)
def websocket_receive(self, message):
res = message["text"]
# 收数据message
print("接收消息-->",res)
for conn in CONN_LIST:
conn.send(res)
# 调用self.close()方法默认都会调用下面这个函数
def websocket_disconnect(self, message):
# 浏览器关闭也会自动发送断开链接请求
print("断开连接")
CONN_LIST.remove(self)
# 服务端同意断开连接
raise StopConsumer()
结果:

聊天室二
聊天室一虽然能实现简单的群聊功能,但是使用列表来储存各个用户,其实效率会很低,并且功能也不强大,Django 的 channels 组件中有一个更加厉害的东西叫 channel layers,可以帮助我们更加方便地去实现这种群聊。
settings 配置
layers 需要在 setting 中进行配置:
python
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer",
}
}
consumer 配置
再修改 Consumer 代码:
python
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync
class ChatConsumer(WebsocketConsumer):
print("进入消费者")
def websocket_connect(self,message):
print("发送连接请求")
self.accept()
# 将这个客户端的连接对象加入到某个地方(内存或redis),channel_name 随机给个名字
async_to_sync(self.channel_layer.group_add)("111",self.channel_name)
def websocket_receive(self, message):
res = message["text"]
# 收数据message
print("接收消息-->",res)
async_to_sync(self.channel_layer.group_send)("111",{"type":"send_to","message":message})
def send_to(self,event):
# 群中每一个连接对象都发送
text = event["message"]["text"]
self.send(text)
# 调用self.close()方法默认都会调用下面这个函数
def websocket_disconnect(self, message):
# 浏览器关闭也会自动发送断开链接请求
print("断开连接")
async_to_sync(self.channel_layer.group_discard)("111",self.channel_name)
# 服务端同意断开连接
raise StopConsumer()
部分解释:
需要注意的是,这里的 channel_layer 操作都是异步进行的,需要自己导入 async_to_sync进行异步转同步操作。
python
async_to_sync((self.channel_layer.group_add)("111",self.channel_name)
这里的作用是将本连接对象存入 channel_layer 中,并且 group 名为 "111" ,self.channel_name 的作用是连接对象存储时,随机给一个名字。
python
async_to_sync(self.channel_layer.group_send)("111",{"type":"send_to","message":message})
python
def send_to(self,event):
# 群中每一个连接对象都发送
text = event["message"]["text"]
self.send(text)
这里的作用是为 "111" 群聊中每个连接对象执行 type 对应的方法,并传入 message 给每个连接对象;下面的 send_to 方法就是为每一个连接对象发送 text消息。
python
async_to_sync(self.channel_layer.group_discard)("111",self.channel_name)
这里的作用是为群聊中的每一个连接对象关闭连接。
上诉代码已能实现聊天室功能,但还不够高级,因为群聊 id 是固定的。下面介绍在浏览器中打开多个聊天室,各个聊天室之间有不同的 id ,各个聊天室之前互不干扰。
多聊天室
实现思路是通过 http 的 get 传参将群号传给视图函数,视图函数给index.html 页面,在 index 页面构造 websocket url 并加入群号,在 consumer中获取群号,并替换群号为原先的固定群号。
实现:
视图函数传参:
python
def index(request):
QQ_number = request.GET.get('qq')
return render(request, 'index.html', {'QQ_number': QQ_number})
index 页面 websocket传参:
javascript
socket = new WebSocket("ws://127.0.0.1:8080/room/{{ QQ_number }}/")
routings中正则接收参数:
python
websocket_urlpatterns = [
re_path(r'^room/(?P<group>\w+)/$', consumers.ChatConsumer.as_asgi()),
]
consumer 中接收 group 并修改群号为 group:
python
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync
class ChatConsumer(WebsocketConsumer):
print("进入消费者")
def websocket_connect(self,message):
print("发送连接请求")
self.accept()
group = self.scope['url_route']['kwargs'].get('group')
# 将这个客户端的连接对象加入到某个地方(内存或redis),channel_name 随机给个名字
async_to_sync(self.channel_layer.group_add)(group,self.channel_name)
def websocket_receive(self, message):
group = self.scope['url_route']['kwargs'].get('group')
res = message["text"]
# 收数据message
print("接收消息-->",res)
async_to_sync(self.channel_layer.group_send)(group,{"type":"send_to","message":message})
def send_to(self,event):
# 群中每一个连接对象都发送
text = event["message"]["text"]
self.send(text)
# 调用self.close()方法默认都会调用下面这个函数
def websocket_disconnect(self, message):
group = self.scope['url_route']['kwargs'].get('group')
# 浏览器关闭也会自动发送断开链接请求
print("断开连接")
async_to_sync(self.channel_layer.group_discard)(group,self.channel_name)
# 服务端同意断开连接
raise StopConsumer()
结果:

这样即能实现多聊天室,各个聊天室互不打扰。