[ Django使用Websocket ]


Channels官方文档

  1. 安装
# 安装channels
python -m pip install -U channels["daphne"]
# 插件redis用于实现组之间群发消息的
python -m pip install channels_redis
  1. 配置 settings.py
# 1. 添加app
INSTALLED_APPS = (
    "daphne",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.sites",
    ...
)

# 2. 添加redis插件, 用户存储用户组, 实现群聊功能
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("127.0.0.1", 6379)],
        },
    },
}

# 3. 添加asgi, 可以再项目文件里找到asgi.py文件
ASGI_APPLICATION = "myproject.asgi.application"

asgi.py

import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application

# 1. 添加websocket的app路由routing.py文件
from chat.routing import websocket_urlpatterns

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'websocket_test.settings')
django_asgi_app = get_asgi_application()

# 2. 改写asgi的路由, 让其可以支持http, 与 websocket协议
application = ProtocolTypeRouter(
    {
        "http": django_asgi_app,
        "websocket": AllowedHostsOriginValidator(
            AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
        ),
    }
)

新建App一个app叫 python manage.py chap chap/routing.py - 用于提供websocket接口

from django.urls import re_path
from . import views

websocket_urlpatterns = [
    re_path(r"ws/chat/(?P<room_name>\w+)/$", views.ChatConsumer.as_asgi()),
    re_path(r'room/(?P<room_name>\w+)/$', views.TestWebsocket.as_asgi()) # 设置组名字
]

chap/urls.py - 用于提供主页

from django.urls import path
from . import views
urlpatterns = [
    path("", views.index, name="index"),
    # path("<str:room_name>/", views.room, name="room"),
    path('test/', views.test)
]

chap/views.py

# chat/views.py
from django.shortcuts import render

def index(request):
    return render(request, "index.html")


def room(request, room_name):
    return render(request, "room.html", {"room_name": room_name})

# 1. 配置视图文件A
def test(request):
    return render(request, 'test.html')


import json

from channels.generic.websocket import WebsocketConsumer, StopConsumer


class ChatConsumer(WebsocketConsumer):
    def connect(self):
        print('有人来连接了')
        self.accept()

    def disconnect(self, close_code):
        pass

    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]
        print(message)
        self.send(text_data=json.dumps({"message": message}))


# chat/consumers.py
import json

from channels.generic.websocket import AsyncWebsocketConsumer


# chat/consumers.py
import json

from asgiref.sync import async_to_sync # 必须
from channels.generic.websocket import WebsocketConsumer # 继承视图


########################
##使用插件redis实现的群聊
########################
# 2. ASGI群聊视图
class TestWebsocket(WebsocketConsumer):
    def connect(self):
        # room/(?P<room_name>\w+)/
        # 1. 获取当前用户组 
        # ['url_route']固定获取路由 -> kwargs所有字段路由参数 -> ['room_name']房间名字
        self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
        # 2. 分组名字
        self.room_group_name = f"chat_{self.room_name}"

        # 3. 把当前用户添加到分组里(内存 or redis)
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name, self.channel_name
        )

        # 4. 连接成功
        self.accept()

    def disconnect(self, close_code):
        # 1. 再当前组中删除此用户
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name, self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data):
        # 1. 获取客户端上传的内容
        text_data_json = json.loads(text_data)
        # 2. 获取用户传过来的数据
        message = text_data_json["message"]

        # 3. 执行回调函数type:chat_message 给当前组的所有人发送消息
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name, {"type": "chat.message", "message": message}
        )

    # 给组内发送消息
    def chat_message(self, event):
        # 1. 获取消息
        message = event["message"]
        # 2. 发送消息
        self.send(text_data=json.dumps({"message": message}))

###########
## 这里是使用本地列表实现的群聊
###########

# CONN_LIST = []
# class TestWebsocket(WebsocketConsumer):
#     def websocket_connect(self, message):
#         print('有人来连接了')
#         self.accept()
#         self.send('来啦老弟!!')
#         CONN_LIST.append(self)

#     def websocket_disconnect(self, message):
#         print('有人断开连接了')
#         CONN_LIST.remove(self)
#         raise StopConsumer()
#         # self.close()

#     def websocket_receive(self, message):
#         print(f'有人来发送消息了:{message}')
#         msg = message['text']
#         if msg == '关闭':
#             # 服务端主动断开连接
#             self.close()
#             return 
#         for conn in CONN_LIST:
#             conn.send(msg)
#         # self.send(f'xxx发送了消息:{msg}')

前端 test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="message" id="msg"></div>
        <input type="text" placeholder="请输入" id="txt">
        <input type="button" value="发送" onclick="SendMessage()">
        <input type="button" value="关闭连接" onclick="CloseMessage()">


<script>
    socket = new WebSocket('ws://127.0.0.1:8000/room/chen/');

    // 连接好之后除法
    socket.onopen = function(event){
        let msg = document.createElement('div');
        msg.innerText = '[连接成功]';
        document.getElementById('msg').appendChild(msg);
    }

    // 接受消息
    socket.onmessage = function (event){
        let msg = document.createElement('div');
        msg.innerText = event.data;
        document.getElementById('msg').appendChild(msg);
    }

    // 发送消息
    function SendMessage() {
        let text = document.getElementById('txt');
        socket.send(text.value);
    }

    // 客户端主动断开
    function CloseMessage() {
        socket.close()
    }

    socket.onclose = function(event){
        let msg = document.createElement('div');
        msg.innerText = '[断开连接]';
        document.getElementById('msg').appendChild(msg);
    }


</script>
</body>
</html>