django channels(websocket)的使用,实现网页间实时聊天

目录

一、创建项目mysite和应用chat

1. 创建项目mysite

2. 创建应用chat并添加应用到settings.py的INSTALLED_APPS中

3. 添加模板文件

4. 添加视图及路由

5.配置根路由指定chat应用的路由

6. 集成channels

二、实现聊天服务器

1. 创建一个新文件chat/templates/chat/room.html,并添加以下内容

2. 创建视图room及配置路由

3. 配置消费者consumers.py

4. 配置routings.py

5. 再次配置asgi.py

6. 启用通道层CHANNEL_LAYERS

7. 再次配置consumers.py

三、改进:将消费者重写为异步


版本

python==3.7

django==3.2.18

channels==3.0.3
channels-redis==4.0.0

一、创建项目mysite和应用chat

1. 创建项目mysite

使用pycharm创建项目mysite

使用命令创建项目mysite

django-admin startproject mysite

2. 创建应用chat并添加应用到settings.py的INSTALLED_APPS中

 python manage.py startapp chat

# settings.py

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

 完成之后项目的目录结构:

3. 添加模板文件

在chat应用下新建templates文件夹,并右键选择Mark Direcory as >> Template Folder,templates的文件夹将变为紫色,并创建index.html文件

 index.html添加以下内容

<!-- chat/templates/chat/index.html -->
<!DOCTYPE html>
<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>

4. 添加视图及路由

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


def index(request):
    return render(request, "index.html")
# chat/urls.py
from django.urls import path

from . import views


urlpatterns = [
    path("", views.index, name="index"),
]

5.配置根路由指定chat应用的路由

# mysite/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path("chat/", include("chat.urls")),
    path("admin/", admin.site.urls),
]

此时启动django项目,浏览器访问http://127.0.0.1:8000/chat/

 这里在输入框中输入字符串后点击Enter会报404的错误,所以我们接下来继续配置

6. 集成channels

a.安装channels

pip3 install -i https://pypi.douban.com/simple channels==3.0.3

 指定安装channels==3.0.3,不要安装最新的4.0版本,否则websocket在连接时会报404错误

b. 调整mysite/asgi.py里的代码

import os

from channels.routing import ProtocolTypeRouter
from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")

application = ProtocolTypeRouter(
    {
        "http": get_asgi_application(),
    }
)

c.在settings.py中添加channels应用及配置ASGI_APPLICATION

# mysite/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'chat',
    'channels'
]

ASGI_APPLICATION = "mysite.asgi.application"

二、实现聊天服务器

1. 创建一个新文件chat/templates/chat/room.html,并添加以下内容

<!-- chat/templates/chat/room.html -->
<!DOCTYPE html>
<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>

2. 创建视图room及配置路由

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


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


# 新添加
def room(request, room_name):
    return render(request, "room.html", {"room_name": room_name})
# chat/urls.py
from django.urls import path

from . import views


urlpatterns = [
    path("", views.index, name="index"),
    path("<str:room_name>/", views.room, name="room"),  # 新添加
]

此时启动django项目,浏览器打开控制台输入地址http://127.0.0.1:8000/chat/

输入myroom,并Enter

 输入消息并点击Send没有任何事发生,所以接下来继续配置消费者consumers

3. 配置消费者consumers.py

在chat应用下添新建consumers.py文件,并添加以下内容

# chat/consumers.py
import json

from channels.generic.websocket import WebsocketConsumer


class ChatConsumer(WebsocketConsumer):
    def connect(self):
        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"]

        self.send(text_data=json.dumps({"message": message}))

目录结构

4. 配置routings.py

在chat应用下新建routing.py文件,并添加以下内容

# chat/routing.py
from django.urls import re_path

from . import consumers

websocket_urlpatterns = [
    re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()),
]

5. 再次配置asgi.py

# mysite/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

from chat.routing import websocket_urlpatterns

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()

import chat.routing

application = ProtocolTypeRouter(
    {
        "http": django_asgi_app,
        "websocket": AllowedHostsOriginValidator(
            AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
        ),
    }
)

运行迁移命令

python manage.py migrate

 此时启动django项目,浏览器打开控制台访问 http://127.0.0.1:8000/,输入myroom并Enter后,不再报ws连接500的错误了

 此时输入消息并点击Send,消息将出现在聊天框中,但是打开新标签页输入同一网址(http://127.0.0.1:8000/chat/myroom/)时,再发消息并不会出现在新标签页的网址消息聊天框中

 要做到能接收到另一个标签页发送的消息,还需要继续配置启动通道层CHANNEL_LAYERS

6. 启用通道层CHANNEL_LAYERS

  • 先安装 channels-redis

pip3 install -i https://pypi.douban.com/simple channels-redis==4.0.0

  • settings.py中添加CHANNEL_LAYERS的配置(请确认在这之前已经安装了redis)
# settings.py
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("127.0.0.1", 6379)],
        },
    },
}
  • 确保通道层可以与Redis通信。打开Django shell并运行以下命令:  
$ python3 manage.py shell
>>> import channels.layers
>>> channel_layer = channels.layers.get_channel_layer()
>>> from asgiref.sync import async_to_sync
>>> async_to_sync(channel_layer.send)('test_channel', {'type': 'hello'})
>>> async_to_sync(channel_layer.receive)('test_channel')
{'type': 'hello'}

7. 再次配置consumers.py

将以下代码替换之前chat/consumers.py里的代码

# chat/consumers.py
import json

from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer


class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
        self.room_group_name = f"chat_{self.room_name}"

        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name, self.channel_name
        )

        self.accept()

    def disconnect(self, close_code):
        # Leave room group
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name, self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]

        # Send message to room group
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name, {"type": "chat.message", "message": message}
        )

    # Receive message from room group
    def chat_message(self, event):
        message = event["message"]

        # Send message to WebSocket
        self.send(text_data=json.dumps({"message": message}))

此时启动django项目,浏览器打开两个标签页,都进入到http://127.0.0.1:8000/chat/myroom/下,再次发送消息,另一个标签页就能接收到啦~

 到目前为止,一个基本的全功能聊天服务器就完成了

三、改进:将消费者重写为异步

我们之前写的consumers.py里的代码都是同步的。同步使用很方便,因为他们可以调用常规的同步I/O函数 例如那些不需要编写特殊代码就可以访问Django模型。然而异步消费者可以提供更高级别的性能,因为它们在处理请求时不需要创建额外的线程。

所以我们再次重写chat/consumers.py里的代码,用以下代码替换之前写的同步代码

# chat/consumers.py
import json

from channels.generic.websocket import AsyncWebsocketConsumer


class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
        self.room_group_name = f"chat_{self.room_name}"

        # Join room group
        await self.channel_layer.group_add(self.room_group_name, self.channel_name)

        await self.accept()

    async def disconnect(self, close_code):
        # Leave room group
        await self.channel_layer.group_discard(self.room_group_name, self.channel_name)

    # Receive message from WebSocket
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]

        # Send message to room group
        await self.channel_layer.group_send(
            self.room_group_name, {"type": "chat.message", "message": message}
        )

    # Receive message from room group
    async def chat_message(self, event):
        message = event["message"]

        # Send message to WebSocket
        await self.send(text_data=json.dumps({"message": message}))

这个新的代码是ChatConsumer非常类似于原来的代码,有以下区别:

  • ChatConsumer 现在继承自 AsyncWebsocketConsumer ,而不是 WebsocketConsumer
  • 所有方法都是 async def 而不仅仅是 def
  • await 用于调用执行I/O的异步函数。
  • 在通道层调用方法时,不再需要async_to_sync。

此时再次启动django项目,浏览器打开两个标签页,都进入到http://127.0.0.1:8000/chat/myroom/下,再次发送消息,这时聊天服务器是完全异步的

参考: Tutorial — Channels 4.0.0 documentation

猜你喜欢

转载自blog.csdn.net/qq_37140721/article/details/130219933