Bootstrap

django+vue3实现websocket聊天室(群聊)

1.如何实现聊天功能

2.什么是websocket

2.1解释什么叫全双工,半双工,单工

3.websocker的概念

4.websocket的优点

5.django ,vue如何实现websocket 

6.django配置

6.1安装channels,安装channels_redis

6.2 配置channels

6.3在settings.py 中配置asgi

6.4 在settings.py 中创建routing.py

6.5创建consumer.py文件,处理websocket通信逻辑

7.前端进行websoket链接(群聊1 vue3,vue2也可以用)

8.群聊2 如图片所示(只能vue2)

8.1创建consumer.py文件,处理websocket通信逻辑

8.2前端chat_ui插件配置

1.如何实现聊天室功能

通过websocket进行长链接通信

2.什么是websocket

django中channel模块之websocket_10973441的技术博客_51CTO博客

WebSocket是一种在单个TCP连接上进行全双工通信的协议

在WebSocket协议中,没有Request和Response的概念,连接一旦建立,就建立了持久连接,双方可以随时向对方发送数据

2.1 解释下什么叫全双工,半双工,单工

全双工:
通信允许数据在两个方向上同时传输。
全双工可以同时进行信号的双向传输,即A-->B的同时B-->A,是瞬间同步的。
日常生活中的电话聊天就是全双工

半双工:
可分时进行信号的双向传输。
指A-->B时,不能B-->A。
数据可以双向传输,但双向传输不是同时进行的。
例如对讲机,一方讲话的同时,另一方不能讲话。

单工:
单向的数据传输。
只允许A-->B传送消息,不能B-->A传送。
例如日常生活中的广播。

3.websocket的概念

在WebSocket概念出来之前,如果页面要不停地显示最新的价格,那么必须不停地刷新页面,或者用一段js代码每隔几秒钟发消息询问服务器数据。

而使用WebSocket技术之后,当服务器有了新的数据,会主动通知浏览器。 如当服务端有新的比特币价格之后,浏览器立马接收到消息。

4.websocket的优点

HTTP协议是浏览器客户端发出请求,服务端进行响应,服务端不能主动发送信息到客户端
WebSocket协议区别于HTTP协议的最大特点,即WebSocket可以由服务端主动发送消息到浏览器端
最普遍的应用就是tail -f查看服务器端的日志,可实现动态刷新最新日志。
WebSocket的另外一个应用场景就是聊天室,一个浏览器端发送的消息,其他浏览器端可同时接受。这在HTTP协议下很难实现的。

5.django ,vue 如何实现websoket

django可以通过channels或者dwebsocket去实现,但是dwebsocket不支持django3.x,所以使用channels来实现前后端的通信

Django-channels2.0笔记--2、Channel Layers - 简书

6.django配置

6.1安装channels,channels_redis

pip install channels==3.0.4

pip install channels_redis

6.2配置channels

注册channels

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'corsheaders',
    'rest_framework',
    'account',
    'rest_framework.authtoken',
    'drf_yasg2',
    'channels', # <-----这里
]

6.3在setting中配置asgi

这里为什么用asgi不用wsgi,因为wsgi不支持websocket通信。

# Django项目默认的WSGI配置,可以注释掉,也可以放着不管,因为之后我们不会使用WSGI作为网关,而是使用下面的ASGI配置。

# 增加ASGI配置

注意:提前查看下自己redis的版本     

连接redis-cli   

输入info   

有个server 下面显示版本5.0以上,可以使用

WSGI_APPLICATION = 'dyz_account.wsgi.application'
ASGI_APPLICATION = 'dyz_account.asgi.application'

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            # 连接自己的redis
            "hosts": [('101.42.224.35', 6379)],
        },
    },
}

然后,在项目的settings.py同级目录下的asgi.py文件中加入下面的内容:

Django2.2是默认没有asgi.py文件,手动创建一个即可。

Django3之后,项目默认会生成一个asgi.py文件, 默认的asgi虽然支持了异步服务,但是仍然不支持websocket.

django3修改asgi.py文件

import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter

from . import routing
from channels.auth import AuthMiddlewareStack
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dyz_account.settings')


application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)),
})

Django2.2不支持asgi,因此无法从django.core.asgi导入get_asgi_application,需要使用如下配置。

import os
import django
from channels.http import AsgiHandler
from channels.routing import ProtocolTypeRouter,URLRouter
from . import routing      # 这个文件后续会说,你先写上。

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dayuanzhong.settings')
django.setup()

application = ProtocolTypeRouter({
  "http": AsgiHandler(),
  "websocket": URLRouter(routing.websocket_urlpatterns),
})

6.4在settings同级目录中创建routing.py,注册路由

from django.urls import path
from dyz_account.consumers import ChatConsumer

websocket_urlpatterns = [
    path('ws/chat', ChatConsumer.as_asgi()),
]

6.5创建consumer.py文件,处理websocket通信逻辑

import json

from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer

CONN_LIST = []


class ChatConsumer(WebsocketConsumer):
    def websocket_connect(self, message):
        print("开始链接...")
        # 有客户端来向后端发送websocket连接的请求时,自动触发。
        # 服务端允许和客户端创建连接(握手)。
        self.accept()

        CONN_LIST.append(self)

    def websocket_receive(self, message):
        # 浏览器基于websocket向后端发送数据,自动触发接收消息。
        print('接受的消息', message)
        text = message['text']  # {'type': 'websocket.receive', 'text': '阿斯蒂芬'}
        print("接收到消息-->", text)
        res = {'result': 'ok'}
        for conn in CONN_LIST:
            conn.send(json.dumps(res))

    def websocket_disconnect(self, message):
        CONN_LIST.remove(self)
        raise StopConsumer()

7、前端进行websocket链接(群聊1)

1.这是个普通(群聊)的写法

<template>
    <div style="    width: 60%;
    margin-left: 20%;">

        <div class="message" id="message"></div>
        <div>
            <input type="text" placeholder="请输入" id="txt" v-model="send_data">
            <input type="button" value="发送" @click="sendMessage(send_data)">
            <input type="button" value="关闭连接" @click="closeConn()">

        </div>
    </div>

</template>

<script>
import Axios from 'axios'
// import { } from "@/components/axios_api/api.js"; // @符号指的是src路径
export default {
    data() {
        return {
            send_data: ''
        };
    },
    components: {
    },
    methods: {
        initWebSocket() {
            //初始化websocket      
            var wsuri = "ws://127.0.0.1:8000";
            // var ws_scheme = window.location.protocol === "https:" ? "wss" : "ws";
            var ws_on_line = wsuri + '/ws/chat'
            // 本地走代理/vue.config.js,线上也代理nginx代理
            console.log('111111111111', ws_on_line)

            // var ws_scheme = window.location.protocol==="https:"?"wss":"ws"      
            this.websock = new WebSocket(ws_on_line);
            this.websock.onopen = this.websocketonopen;
            this.websock.onmessage = this.websocketonmessage;
            this.websock.onerror = this.websocketonerror;
            // this.websock.onclose = this.websocketclose;   
        },
        websocketonopen() {
            //连接建立之后执行send方法发送数据     
            console.log("建立连接");
            var actions = { message: "连接测试" };
            this.sendMessage(actions);
        },

        websocketonerror(e) {
            //连接建立失败重连      
            console.log("连接error", e);
            // this.initWebSocket();
        },

        websocketonmessage(e) {
            //数据接收      
            this.websocket_data = JSON.parse(e.data);
            console.log("websocket-res", JSON.stringify(this.websocket_data))
            console.log("接收后端数据type", typeof (this.websocket_data))
            // 将websocket消息展示在消息提示框     
            var h = this.$createElement;
            // 创建html元素      
            this.hrender = h("p", null, [
                h(
                    "div", [
                    h("div",
                        JSON.stringify(this.websocket_data.message)),
                    // 传变量           
                    //   },         
                    // }),           
                ],
                    null
                ),
            ]);

        },
        sendMessage(Data) {
            //数据发送     
            this.websock.send(JSON.stringify(Data));
        },
        websocketclose(e) {  //关闭      
            console.log('断开连接', e);
        },
        closeConn(){
            socket.close(); // 向服务端发送断开连接的请求
        }


    },
    mounted() {
        this.initWebSocket()

    },
};
</script>

<style>
</style>

 8.主要实现图片(别人发的消息在左边,自己发送的消息在右边)

群聊2 ----只基于vue2

这需要什么配置都写在图片下面

 第二种实现方式是基于channels中提供channel layers来实现

1.先建立连接

2.浏览器websocket向后端发送数据

如果是add_chat  添加所输入的群号

如果是不add_chat  则获取前端传给后端的信息去发送

3.要么不发送消息的时候断开连接 把群号从channel_layer 中移除

8.1创建consumer.py文件,处理websocket通信逻辑


from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync
import json


class ChatConsumer(WebsocketConsumer):
    def websocket_connect(self, message):
        # 接收这个客户端的连接
        self.accept()
        # 将这个客户端的连接对象加入到某个地方(内存 or redis)1314 是群号这里写死了
        # async_to_sync(self.channel_layer.group_add)(1314, self.channel_name)

    def websocket_receive(self, message):
       # 浏览器基于websocket向后端发送数据,自动触发接收消息
        print('222', message) 
        data = json.loads(message['text'])
        chat_type = data.get('chat_type')
        chat_id = data.get('chat_id')
        chat_content = data.get('message')
        print('chat_type', chat_type)
        if chat_type == 'add_chat':
            async_to_sync(self.channel_layer.group_add)(chat_id, self.channel_name)
        # 通知组内的所有客户端,执行 chat_message 方法,在此方法中自己可以去定义任意的功能。
        async_to_sync(self.channel_layer.group_send)(chat_id, {"type": 'chat.message', 'message': message})

    # 这个方法对应上面的type,意为向1314组中的所有对象发送信息
    # 回调的方法
    def chat_message(self, event):
        text = event['message']['text']
        self.send(text)

    def websocket_disconnect(self, message):
        print('3333', message)
        data = json.loads(message['text','{}'])
        chat_id = data.get('chat_id')
        # 断开链接要将这个对象从 channel_layer 中移除
        async_to_sync(self.channel_layer.group_discard)(chat_id, self.channel_name)
        raise StopConsumer()

8.2 前端chat_ui插件配置

1.安装

npm install @maybecode/m-chat

2.vue中main.js添加

import Vue from 'vue'
import MChat from '@maybecode/m-chat'
 
Vue.use(MChat)

3.局部使用

import MChat from '@maybecode/m-chat'

export default {
  components: {
    MChat
  }
}

4.具体的解释看官方 :m-chat: 基于vue 聊天(IM) UI组件

5.前端vue2写法

<template>
  <div style="width: 60%; margin-left: 20%">
    <div class="message" id="message"></div>
    <div v-show="add_chat">
      <input
        type="text"
        placeholder="请输入群聊id"
        id="txt"
        v-model="chat_id"
      />
      <input type="button" value="加入群聊" @click="addChat()" />
    </div>
    <div v-show="send_chat">
      <m-chat
        ref="mChat"
        :messages="messages"
        @submit="submit"
        :loadMore="loadMore"
        height="100vh"
        :openExtends="open_extends"
      >
      </m-chat>
    </div>
  </div>
</template>

<script>
// import { } from "@/components/axios_api/api.js"; // @符号指的是src路径
import MChat from "@maybecode/m-chat";
export default {
  data() {
    return {
      user_id: "",
      send_data: "",
      chat_id: null,
      send_chat: false,
      add_chat: true,
      messages: [],
      open_extends: ["image"],
    };
  },
  components: {
    MChat,
  },
  methods: {
    submit(content) {
      this.sendMessage(content["content"]);
    },

    loadMore() {},
    // 加入群聊,如果有对应的id,进入群聊,没有则创建一个新的群聊
    addChat() {
      //获取存入的user_id
      // 如果没有就跳转到登录页面
      var userid = localStorage.getItem("user_id");
      if (userid) {
        if (this.chat_id) {
          this.initWebSocket();
          this.send_chat = true;
          this.add_chat = false;
        } else {
          alert("请输入群聊号");
        }
      }else{
        alert('拜拜')
        this.$router.push('/login')
      }
    },
    initWebSocket() {
      //初始化websocket----连接后端
      var wsuri = "ws://192.168.10.20:8000";
      // var ws_scheme = window.location.protocol === "https:" ? "wss" : "ws";
      var ws_on_line = wsuri + "/ws/chat";
      // 本地走代理/vue.config.js,线上也代理nginx代理
      console.log("111111111111", ws_on_line);

      // var ws_scheme = window.location.protocol==="https:"?"wss":"ws"
      this.websock = new WebSocket(ws_on_line);
      this.websock.onopen = this.websocketonopen;
      this.websock.onmessage = this.websocketonmessage;
      this.websock.onerror = this.websocketonerror;
      // this.websock.onclose = this.websocketclose;
    },
    websocketonopen() {
      //连接建立之后执行send方法发送数据
      console.log("建立连接");
      var actions = { message: "连接测试" };
      var type = "add_chat";
      this.sendMessage(actions, type);
    },

    websocketonerror(e) {
      //连接建立失败重连
      console.log("连接error", e);
      // this.initWebSocket();
    },

    websocketonmessage(e) {
      //数据接收
      //json.parse()服务器一般接受的是js对象  
      this.websocket_data = JSON.parse(e.data);
      //json.stringfy 是将接js对象转换为json数据
      console.log("websocket-res", JSON.stringify(this.websocket_data));
      console.log("接收后端数据type", typeof this.websocket_data);

      //获取信息里面的user_id,然后把消息放入左边
      let message_user_id = this.websocket_data["user_id"];
      let message_self = false;
      // 判断当前消息是否自己发出
      if (message_user_id == localStorage.getItem("user_id")) {
        message_self = true;
      }else{
          message_self=false
      }

      // 获取信息里面的usrname
      let name = this.websocket_data.username;
      // 将websocket消息展示在消息提示框
      var count = this.messages.length;
      var push_message = {
        type: "text",
        content: { text: JSON.stringify(this.websocket_data.message) },
        id: count + 1,
        self: message_self,
        name: name,
      };
      this.messages.push(push_message);
      var h = this.$createElement;
      // 创建html元素
      this.hrender = h("p", null, [
        h(
          "div",
          [
            h("div", JSON.stringify(this.websocket_data.message)),
            // 传变量
            //   },
            // }),
          ],
          null
        ),
      ]);
    },
    sendMessage(Data, type = "send_data") {
      //数据发送
      console.log("222222222222");
      this.websock.send(
        JSON.stringify({
          chat_id: this.chat_id,
          message: Data,
          chat_type: type,
          // 获取里面存的username,user_id
          username: localStorage.getItem("username"),
          user_id: localStorage.getItem("user_id"),
        })
      );
    },

    websocketclose(e) {
      //关闭
      console.log("断开连接", e);
    },
    userinfo() {},
  },
  mounted() {},
};
</script>

<style>
</style>

9.写到这个里就结束的了,上面的比较专业我复制了链接了,需要的去看看哟

;