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

打印 上一主题 下一主题

主题 1002|帖子 1002|积分 3006

学习视频:
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"
  1. INSTALLED_APPS = [
  2.     "daphne",
  3.     "channels",
  4.     "django.contrib.admin",
  5.     "django.contrib.auth",
  6.     "django.contrib.contenttypes",
  7.     "django.contrib.sessions",
  8.     "django.contrib.messages",
  9.     "django.contrib.staticfiles",
  10.     "app01.apps.App01Config"
  11. ]
复制代码
然后运行就能连上 asgi 了:

大致缘故原由是 pip install channels 按照命令默认按照的是最新版的 channels ,可能与 Django 版本并不匹配。
收发数据

websocket 模式中,服务端和客户端都能主动收发数据:
在客户端发数据:
  1. function sendMessage(){
  2.         var txt = document.getElementById("txt")
  3.         console.log(txt.value)
  4.         socket.send(txt.value)
  5.     }
复制代码
在服务端收数据:
  1.     def websocket_receive(self, message):
  2.         # 收数据message
  3.         print("接收消息-->",message["text"])
复制代码
在服务端发数据:
利用 send() 方法即可
  1.     def websocket_connect(self,message):        print("发送连接哀求")        self.accept()        # 发数据        self.send("来了呀客官")    def websocket_receive(self, message):
  2.         # 收数据message
  3.         print("接收消息-->",message["text"])        self.send(message["text"])
复制代码
在客户端收数据:
这里的服务端发数据在发送 websocket 连接函数和吸收消息函数中都可,相对于,在客户端收数据也对应两种方法,一个是 socket.onopen ,创建好连接后自动触发(握手环节,服务端执行self.accept());另有一个就是 socket.onmessage ,用于正常吸收数据。
  1. socket.onopen = function(event){
  2.         console.log(event.value)
  3.         let lag = document.createElement("div")
  4.         lag.innerText = "[websocket连接成功]"
  5.         document.getElementById("message").appendChild(lag)
  6.     }
  7. {#收数据#}
  8. socket.onmessage = function (event){
  9.         var data = event.data
  10.         console.log("客户端接收到消息-->",data)
  11.         let lag = document.createElement("div")
  12.         lag.innerText = data
  13.         document.getElementById("message").appendChild(lag)
  14.     }
复制代码
断开连接

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

完备代码

index.html:
  1. <!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(){
  2.         var txt = document.getElementById("txt")
  3.         console.log(txt.value)
  4.         socket.send(txt.value)
  5.     }    {#收数据#}    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){
  6.     }    function closeOnn(){
  7.         socket.close()
  8.     }</script></body></html>
复制代码
consumers.py:
  1. 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):
  2.         # 收数据message
  3.         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):
  4.         # 浏览器关闭也会自动发送断开链接请求
  5.         print("断开连接")
  6.         # 服务端同意断开连接
  7.         raise StopConsumer()
复制代码
聊天室的实现

固然,上面只是先容 websocket 的一样平常利用,还并没有现实应用,下面将以聊天室场景举行应用。
聊天室一

前面的操作都是基于 self 来的。服务端仅仅关心本身与对应欣赏器的连接通道,而不会接洽到其它欣赏器。可利用列表存储各个用户,某用户想断开连接或者主动退出欣赏器时,再到列表中删除用户:
需要注意的是,用户添加到列表中后,后续的一系列操作需要在列表中循环操作每一个对象,以实现群聊
  1. from channels.generic.websocket import WebsocketConsumer
  2. from channels.exceptions import StopConsumer
  3. CONN_LIST = []
  4. class ChatConsumer(WebsocketConsumer):
  5.     print("进入消费者")
  6.     def websocket_connect(self,message):
  7.         print("发送连接请求")
  8.         self.accept()
  9.         CONN_LIST.append(self)
  10.     def websocket_receive(self, message):
  11.         res = message["text"]
  12.         # 收数据message
  13.         print("接收消息-->",res)
  14.         for conn in CONN_LIST:
  15.             conn.send(res)
  16.     # 调用self.close()方法默认都会调用下面这个函数
  17.     def websocket_disconnect(self, message):
  18.         # 浏览器关闭也会自动发送断开链接请求
  19.         print("断开连接")
  20.         CONN_LIST.remove(self)
  21.         # 服务端同意断开连接
  22.         raise StopConsumer()
复制代码
效果:

聊天室二

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

layers 需要在 setting 中举行配置:
  1. CHANNEL_LAYERS = {
  2.     "default": {
  3.         "BACKEND": "channels.layers.InMemoryChannelLayer",
  4.     }
  5. }
复制代码
consumer 配置

再修改 Consumer 代码:

  1. from channels.generic.websocket import WebsocketConsumer
  2. from channels.exceptions import StopConsumer
  3. from asgiref.sync import async_to_sync
  4. class ChatConsumer(WebsocketConsumer):
  5.     print("进入消费者")
  6.     def websocket_connect(self,message):
  7.         print("发送连接请求")
  8.         self.accept()
  9.         # 将这个客户端的连接对象加入到某个地方(内存或redis),channel_name 随机给个名字
  10.         async_to_sync(self.channel_layer.group_add)("111",self.channel_name)
  11.     def websocket_receive(self, message):
  12.         res = message["text"]
  13.         # 收数据message
  14.         print("接收消息-->",res)
  15.         async_to_sync(self.channel_layer.group_send)("111",{"type":"send_to","message":message})
  16.     def send_to(self,event):
  17.         # 群中每一个连接对象都发送
  18.         text = event["message"]["text"]
  19.         self.send(text)
  20.     # 调用self.close()方法默认都会调用下面这个函数
  21.     def websocket_disconnect(self, message):
  22.         # 浏览器关闭也会自动发送断开链接请求
  23.         print("断开连接")
  24.         async_to_sync(self.channel_layer.group_discard)("111",self.channel_name)
  25.         # 服务端同意断开连接
  26.         raise StopConsumer()
复制代码
部门解释:
需要注意的是,这里的 channel_layer 操作都是异步举行的,需要本身导入 async_to_sync 举行异步转同步操作。
  1. async_to_sync((self.channel_layer.group_add)("111",self.channel_name)
复制代码
这里的作用是将本连接对象存入 channel_layer 中,而且 group 名为 "111" ,self.channel_name 的作用是连接对象存储时,随机给一个名字。

  1. async_to_sync(self.channel_layer.group_send)("111",{"type":"send_to","message":message})
复制代码
  1. def send_to(self,event):
  2.     # 群中每一个连接对象都发送
  3.     text = event["message"]["text"]
  4.     self.send(text)
复制代码
这里的作用是为 "111" 群聊中每个连接对象执行 type 对应的方法,并传入 message 给每个连接对象;下面的 send_to 方法就是为每一个连接对象发送 text 消息。

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

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

实现思路是通过 http get 传参将群号传给视图函数,视图函数给 index.html 页面,在 index 页面构造 websocket url 并参加群号,在 consumer 中获取群号,并更换群号为原先的固定群号。
实现:
视图函数传参:
  1. def index(request):
  2.     QQ_number = request.GET.get('qq')
  3.     return render(request, 'index.html', {'QQ_number': QQ_number})
复制代码
index 页面 websocket 传参:
  1. socket = new WebSocket("ws://127.0.0.1:8080/room/{{ QQ_number }}/")
复制代码
routings 中正则吸收参数:
  1. websocket_urlpatterns = [
  2.     re_path(r'^room/(?P<group>\w+)/$', consumers.ChatConsumer.as_asgi()),
  3. ]
复制代码
consumer 中吸收 group 并修改群号为 group:
  1. from channels.generic.websocket import WebsocketConsumer
  2. from channels.exceptions import StopConsumer
  3. from asgiref.sync import async_to_sync
  4. class ChatConsumer(WebsocketConsumer):
  5.     print("进入消费者")
  6.     def websocket_connect(self,message):
  7.         print("发送连接请求")
  8.         self.accept()
  9.         group = self.scope['url_route']['kwargs'].get('group')
  10.         # 将这个客户端的连接对象加入到某个地方(内存或redis),channel_name 随机给个名字
  11.         async_to_sync(self.channel_layer.group_add)(group,self.channel_name)
  12.     def websocket_receive(self, message):
  13.         group = self.scope['url_route']['kwargs'].get('group')
  14.         res = message["text"]
  15.         # 收数据message
  16.         print("接收消息-->",res)
  17.         async_to_sync(self.channel_layer.group_send)(group,{"type":"send_to","message":message})
  18.     def send_to(self,event):
  19.         # 群中每一个连接对象都发送
  20.         text = event["message"]["text"]
  21.         self.send(text)
  22.     # 调用self.close()方法默认都会调用下面这个函数
  23.     def websocket_disconnect(self, message):
  24.         group = self.scope['url_route']['kwargs'].get('group')
  25.         # 浏览器关闭也会自动发送断开链接请求
  26.         print("断开连接")
  27.         async_to_sync(self.channel_layer.group_discard)(group,self.channel_name)
  28.         # 服务端同意断开连接
  29.         raise StopConsumer()
复制代码
效果:

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




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

郭卫东

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表