Django+websocket实现多人聊天室的功能

2022年5月31日 21:13 ry 920

最近在尝试给我的网站增加多人聊天室的功能,学习了下websocket,发现django可以结合websocket实现,再结合官网文档进行尝试搭建个多人聊天的功能,官网文档链接https://channels.readthedocs.io/en/latest/index.html 其channels库可以实现websocket请求,本次使用的环境是django2.2.4,channels,channesl-redis,以及redis。安装时直接pip install xxx即可。我们先创建一个django项目,命令行python django-admin startproject  django_discuss 创建了一个名为django_discuss的django项目了,接下来切进django_discuss目录,使用命令python manage.py startapp inde创建一个名为index的app,具体结构如下

这里注意的是,django2项目创建时没有asgi.py这个文件,这个django3才会有,这个不急,后面会讲,static和templates文件夹分别存储静态资源和前端代码的,在settings.py可以配置下,对应代码

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR,'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

以及静态文件路由

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR,'static')
]

将创建的app加入到settings.py中,如图所示

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'index',

]

进入django_discuss/urls.py中,设置路由

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include('index.urls')),
]

进入index/views.py中,后端视图

from django.shortcuts import render

# Create your views here.
def index(request):
    return render(request,'index.html')

在templates/index.html中

{% load static %}
<html>
<head>
    <meta charset="utf-8"/>
    <title>Chat Rooms</title>
</head>
<body>
    What chat room would you like to enter?<br>
    <input id="room-name-input" type="text" size="100"><br>
    <input id="room-name-submit" type="button" value="Enter">

    <script>
        document.querySelector('#room-name-input').focus();
        document.querySelector('#room-name-input').onkeyup = function(e) {
            if (e.keyCode === 13) {  // enter, return
                document.querySelector('#room-name-submit').click();
            }
        };

        document.querySelector('#room-name-submit').onclick = function(e) {
            var roomName = document.querySelector('#room-name-input').value;
            window.location.pathname = '/chat/' + roomName + '/';
        };
    </script>
</body>
</html>

 

新建index/urls.py

from django.urls import path
from . import views
urlpatterns = [
    path('chat/',views.index,name='index'),
   
]

现在可以正常运行项目了,python manage.py runserver运行,目前功能只是一个房间号的输入框,还没用到channels,具体功能是用户输入房间号立马跳转到房间号实现对话效果,加一个对话框视图,将一下代码放入templates/room.html中

{% load static %}
<html>
<head>
    <meta charset="utf-8"/>
    <title>Chat Room</title>
</head>
<body>
    <textarea id="chat-log" cols="100" rows="20"></textarea><br>
    <input id="chat-message-input" type="text" size="100"><br>
    <input id="chat-message-submit" type="button" value="Send">
    {{ room_name|json_script:"room-name" }}
    <script>
        const roomName = JSON.parse(document.getElementById('room-name').textContent);

        const chatSocket = new WebSocket(
            'ws://'
            + window.location.host
            + '/ws/chat/'
            + roomName
            + '/'
        );

        chatSocket.onmessage = function(e) {
            const data = JSON.parse(e.data);
            document.querySelector('#chat-log').value += (data.message + '\n');
        };

        chatSocket.onclose = function(e) {
            console.error('Chat socket closed unexpectedly');
        };

        document.querySelector('#chat-message-input').focus();
        document.querySelector('#chat-message-input').onkeyup = function(e) {
            if (e.keyCode === 13) {  // enter, return
                document.querySelector('#chat-message-submit').click();
            }
        };

        document.querySelector('#chat-message-submit').onclick = function(e) {
            const messageInputDom = document.querySelector('#chat-message-input');
            const message = messageInputDom.value;
            chatSocket.send(JSON.stringify({
                'message': message
            }));
            messageInputDom.value = '';
        };
    </script>
</body>
</html>

 

这里ws表示是websocket请求,因此后端根据websocket的链接配置对应路由,在index.urls.py中如下

from django.urls import path
from . import views
urlpatterns = [
    path('chat/',views.index,name='index'),
    path('chat//',views.room,name='room')
]

传递前端输入的房间名,对应后端视图代码即index/views.py中

from django.shortcuts import render

# Create your views here.
def index(request):
    return render(request,'index.html')
def room(request,room_name):
    return render(request,'room.html',context={'room_name':room_name})

先说下基本知识,websocket中有4个事件,open,message,error,close.open是服务器一旦响应了websocket请求,立马触发open事件,open事件对应的回调函数为onopen,而message为websocket消息,来自其对应服务器的数据,其回调函数为onmessage,error为处理以为故障触发,close为websocket连接关闭时触发。而websocket对象有2个方法send和close,这个在django后端视图消费者部分体现明显。聊天室大概原来我们说下,首先用户前端输入房间号回车后,进入聊天室的界面,通过前端new websocket路由连接后端视图,前端中的ws.onmessage监听后端服务器传来的消息,一有消息则将聊天内容显示在对话框上,对于前端用户输入的消息,则前端绑定输入框,使用send方法传给django后端,这里先将输入内容通过SON.stringify转化为字符串再传给后端,这里重要的就是后端接受消息和发送消息部分了。当 Django 接受一个 HTTP 请求时,它会参考根 URLconf 来查找视图函数,然后调用视图函数来处理请求。同样,当 Channels 接受 WebSocket 连接时,它会参考根路由配置来查找消费者,然后调用消费者上的各种函数来处理来自连接的事件。而这个消费者即后端接受消息和发送消息的重要部分了。

在settings.py中加入channels,

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'index',
    'channels'
]

新建django_discuss/asgi.py(django2得新建,django3自带),代码如下

import os
import django
import index.routing
from channels.http import AsgiHandler
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_discuss.settings')
application = ProtocolTypeRouter({
    "http": AsgiHandler,
    # Just HTTP for now. (We can add other protocols later.)
    "websocket": AllowedHostsOriginValidator(
            AuthMiddlewareStack(
                URLRouter(
                    index.routing.websocket_urlpatterns
                )
            )
        ),
})

类似于普通的路由配置,只不过这里是websocket根路由,很好理解,和django_discuss中的urls的根路由一个道理,接着在settings.py将channels指向配置好了的根路由

WSGI_APPLICATION = 'django_discuss.wsgi.application'
#新增
ASGI_APPLICATION = 'django_discuss.asgi.application'

接下来开始写消费者后端了,创建index/consumers.py,代码如下

import json
from channels.generic.websocket import WebsocketConsumer
from channels.generic.websocket import AsyncWebsocketConsumer
from asgiref.sync import async_to_sync
class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = 'chat_%s'% self.room_name
        #加入room group
        await self.channel_layer.group_add(
            self.room_group_name,self.channel_name
        )
        await self.accept()
    
    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(
            self.room_group_name, self.channel_name
        )

    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type':'chat_message',
                'message':message
            }
        )
    async def chat_message(self,event):
        message = event['message']
        await self.send(text_data=json.dumps({
            'message': message
        }))

这是一个异步的websocket消费者,为了确保同一房间多个实例相互通信,需要使用redis作为后备存储的通道层,在settings.py中增加

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('127.0.0.1', 6379)],
        },
    },
}

上面消费者中connect首先接受前端的websocket连接,其接受一个房间号参数,获取房间号参数,将其房间名称作为channels组名称,实现了对于同一组的可以相互通信,对于关闭连接,只需移除房间号即可,receive函数接受前端send发送过来的数据包,通过chat_message函数将数据包发送给前端对话框中,这样就实现了通信循环。

然后创建index/routing.py,设置该app下的websocket路由,代码如下,

from django.urls import re_path

from . import consumers

websocket_urlpatterns = [
    re_path(r'ws/chat/(?P\w+)/$', consumers.ChatConsumer),
]

先启动redis服务,然后运行代码,2个浏览器实现同一房间号进行通信,如图1,2

实现了2个浏览器之间的简单通信,当然,你可以在对话框加个用户标识,设置用户登录后对话,界面优化等功能都可以,这里只是粗糙的简易的实现方式而已。

如果上述代码帮助您很多,可以打赏下以减少服务器的开支吗,万分感谢!

欢迎发表评论~

点击此处登录后即可评论


评论列表
暂时还没有任何评论哦...

赣ICP备2021001574号-1

赣公网安备 36092402000079号