郭卫东 发表于 2025-3-21 13:54:37

【玩转全栈】---- Django 基于 Websocket 实现群聊(办理channel连接不了)

学习视频:
14-11 群聊(一)_哔哩哔哩_bilibili
   目次
Websocket 连接不了?
收发数据
断开连接
完备代码
聊天室的实现
聊天室一
聊天室二
settings 配置
consumer 配置
多聊天室

Websocket 连接不了?

基于这篇博客:
【全栈开发】---- 一文把握 Websocket 原理,并用 Django 框架实现_django websocket-CSDN博客
        之前这篇博客固然大致原理都先容了,但终极的代码并没有实现,这是因为博主当时遇见了一个题目,尽管我按照教程来的,但是 websocket 服务就是连不上,背面也参考了许多博客,也去官网看了,还去 github 上抄项目来对比,都办理不了,厥后急得我转 SpringBoot 去了。但偶尔间发现了这篇博客:
https://blog.csdn.net/qq_25218219/article/details/131752459Django的websocket
终极题目才得以办理,再次感谢这位博主!!!
办理办法很简单,基于上面学习视频的配置后,需要在注册组件的 “channels” 前面添加一个组件    "daphne"
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 了:
https://i-blog.csdnimg.cn/direct/59b4624eeef74848ad5ae059b1dd21ca.png
大致缘故原由是 pip install channels 按照命令默认按照的是最新版的 channels ,可能与 Django 版本并不匹配。
收发数据

在 websocket 模式中,服务端和客户端都能主动收发数据:
在客户端发数据:
function sendMessage(){
      var txt = document.getElementById("txt")
      console.log(txt.value)
      socket.send(txt.value)
    } 在服务端收数据:
    def websocket_receive(self, message):
      # 收数据message
      print("接收消息-->",message["text"]) 在服务端发数据:
利用 send() 方法即可
    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 ,用于正常吸收数据。
socket.onopen = function(event){
      console.log(event.value)
      let lag = document.createElement("div")
      lag.innerText = ""
      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)
    } 断开连接

在服务端断开连接一样平常是颠末下面这个函数:
    def websocket_disconnect(self, message):
      # 浏览器关闭也会自动发送断开链接请求
      print("断开连接")
      # 服务端同意断开连接
      raise StopConsumer()         这个函数不仅仅关闭欣赏器的哀求链接,还会关闭服务端链接,实现完全断连。在类中其他函数中可利用 self.close()  来调用此关闭链接函数,实现完全断连;而假如用 raiseStopConsumer() ,则表示仅仅断开服务器连接,也不会执行 websocket_disconnect 函数。
服务器断开连接时,客户端也会触发一个函数:
socket.onclose = function (event){

    } 而且客户端也可以设置按钮,主动断开连接:
function closeOnn(){
      socket.close()
    }
完备代码

index.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 = ""      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> consumers.py:
from channels.generic.websocket import WebsocketConsumerfrom 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 来的。服务端仅仅关心本身与对应欣赏器的连接通道,而不会接洽到其它欣赏器。可利用列表存储各个用户,某用户想断开连接或者主动退出欣赏器时,再到列表中删除用户:
需要注意的是,用户添加到列表中后,后续的一系列操作需要在列表中循环操作每一个对象,以实现群聊
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()

效果:
https://i-blog.csdnimg.cn/direct/d27d6d17dbfe475584372b373038d523.png
聊天室二

        聊天室一固然能实现简单的群聊功能,但是利用列表来储存各个用户,其实效率会很低,而且功能也不强盛,Django 的 channels 组件中有一个更加锋利的东西叫  channel layers,可以帮助我们更加方便地去实现这种群聊。
   参考文章:django channels - 武沛齐 - 博客园
settings 配置

layers 需要在 setting 中举行配置:
CHANNEL_LAYERS = {
    "default": {
      "BACKEND": "channels.layers.InMemoryChannelLayer",
    }
} consumer 配置

再修改 Consumer 代码:

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 举行异步转同步操作。
async_to_sync((self.channel_layer.group_add)("111",self.channel_name) 这里的作用是将本连接对象存入 channel_layer 中,而且 group 名为 "111" ,self.channel_name 的作用是连接对象存储时,随机给一个名字。

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) 这里的作用是为 "111" 群聊中每个连接对象执行 type 对应的方法,并传入 message 给每个连接对象;下面的 send_to 方法就是为每一个连接对象发送 text 消息。

async_to_sync(self.channel_layer.group_discard)("111",self.channel_name) 这里的作用是为群聊中的每一个连接对象关闭连接。

上诉代码已能实现聊天室功能,但还不敷高级,因为群聊 id 是固定的。下面先容在欣赏器中打开多个聊天室,各个聊天室之间有不同的 id ,各个聊天室之前互不干扰。
多聊天室

实现思路是通过 http 的 get 传参将群号传给视图函数,视图函数给 index.html 页面,在 index 页面构造 websocket url 并参加群号,在 consumer 中获取群号,并更换群号为原先的固定群号。
实现:
视图函数传参:
def index(request):
    QQ_number = request.GET.get('qq')
    return render(request, 'index.html', {'QQ_number': QQ_number}) index 页面 websocket 传参:
socket = new WebSocket("ws://127.0.0.1:8080/room/{{ QQ_number }}/") routings 中正则吸收参数:
websocket_urlpatterns = [
    re_path(r'^room/(?P<group>\w+)/$', consumers.ChatConsumer.as_asgi()),
] consumer 中吸收 group 并修改群号为 group:
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() 效果:
https://i-blog.csdnimg.cn/direct/3c50525473eb4a1a836940c2fd12353a.png
这样即能实现多聊天室,各个聊天室互不打搅。




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【玩转全栈】---- Django 基于 Websocket 实现群聊(办理channel连接不了)