python异步编程-channels使用,创建websocket服务

目录

channels介绍

channels是一个用来构建实时web应用的强大工具,比如聊天室、在线游戏,多人协作、实时通知等。今天简单介绍一下,用channels搭建一个简易的websocket服务。

准备工作

创建python虚拟环境

shell 复制代码
mkdir instance
cd install
python -m venv venv

# 激活虚拟环境
source venv/bin/activate # linux
./venv/bin/activate.bat # window

安装channels

shell 复制代码
pip install channels

安装django

shell 复制代码
pip install django

安装daphne

这个稍后会用到,用来启动异步服务器使用

shell 复制代码
pip install daphne

创建django项目

shell 复制代码
django-admin startproject instance 

创建chat应用

shell 复制代码
django-admin startapp chat

配置instance项目

在instance/instance目录下,修改settings.py文件

建议最好用pycharm打开

python 复制代码
# instance/instance/settings.py 

...
INSTALLED_APPS = [
	'daphne', # 新增
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',
    'django.contrib.sitemaps',
    'chat', # 新增
    'channels', # 新增
]
...
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates']
        ,
        'APP_DIRS': True,
        'OPTIONS': { # 此处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',
            ],
        },
    },
]
...

简单聊天室页面

在chat应用中创建模板

需要创建两个模板文件,其中room.html 继承base.html

base.html

主要定义了页面的基本结构,具体内容在子模板中填充

instance/chat/template/chat/base.html

html 复制代码
{% load static %}
<!DOCTYPE html>
<html>
<head>
    <meta charset=""utf-8>
    <title>{% block title %}Education{% endblock %}</title>
    <link href="{% static "css/base.css" %}" rel="stylesheet">
</head>
<body>
<div id="header">
    <a href="/" class="logo">Education</a>
    <ul class="menu">
        {% if request.user.is_authenticated %}
        <li><a href="{% url "logout" %}">Sign out</a> </li>
        {% else %}
        <li><a href="{% url "login" %}">Sign in</a> </li>
        {% endif %}
    </ul>
</div>
<div id="content">
    {% block content %}
    {% endblock %}
</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>
    $(document).ready(function(){
        {% block domready %}
        {% endblock %}
    });
</script>
</body>
</html>

room.html

主要实现了一个输入框一个发送按钮,以及可以展示消息的div元素,页面简陋,能看即可

除了页面结构,还加了websocket链接的js代码

html 复制代码
{% extends "chat/base.html" %}

{% block title %} Chat room for "{{ course.title }}" {% endblock %}

{% block content %}
<div id="chat">

</div>
<div id="chat-input">
    <input id="chat-message-input" type="text">
    <input id="chat-message-submit" type="submit" value="Send">
</div>
{% endblock %}

{% block domready %}
        var url = 'ws://' + window.location.host + '/ws/chat/room/' + '{{ course }}' + '/';
        console.log(url);
        var chatSocket = new WebSocket(url);
        console.log(chatSocket);

        chatSocket.onmessage = function(e) {
            var data = JSON.parse(e.data);
            var message = data.message;

            var $chat = $('#chat');
            $chat.append('<div class="message">' + message + '</div>');
            $chat.scrollTop($chat[0].scrollHeight);
        };

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

        var $input = $('#chat-message-input');
        var $submit = $('#chat-message-submit');

        $submit.click(function(){
            var message = $input.val();
            if(message) {
                chatSocket.send(JSON.stringify({'message': message}));

                $input.val('');

                $input.focus();
            }
        });

        $input.focus();
        $input.keyup(function (e){
            if(e.which === 13) {
                $submit.click();
            }
        });
{% endblock %}

添加视图

编辑chat目录下views.py

python 复制代码
from django.shortcuts import render

def course_chat_room(request, course_id):
    return render(request, 'chat/room.html', {'course': 1})

添加路由

添加urls.py

chat目录下新增urls.py

python 复制代码
from django.urls import path

from chat import views


app_name = 'chat'

urlpatterns = [
    path('room/<int:course_id>/', views.course_chat_room, name='course_chat_room'),
]

项目路由添加chat转发路由

编辑instance/instance/urls.py

python 复制代码
urlpatterns = [
    path('admin/', admin.site.urls),
    path('chat/', include('chat.urls', namespace='chat')), # 新增
    ...,
]

启动同步服务器

到这一步就可以启动wsgi服务器,查看页面

shell 复制代码
python manage.py runserver

启动后,在浏览器中打开网站http://127.0.0.1:8000/chat/room/1/ 可以看到页面

我这里有样式设置,所以可能你们电脑上看到的样式不一样,但是基本结构时一样的

搭建websocket服务

我们前面已经将channels 应用添加到settings中的INSTALLED_APPS中

所以可以直接配置channels

创建channels路由

新增协议使用者

channels中一个很重要的概念就是消费者,也叫使用者,本质上就是协议请求接收之后的处理者

chat目录下新建consumers.py

这里使用了异步实现,将注释掉的代码恢复,就是同步代码

python 复制代码
import json

from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer
from asgiref.sync import async_to_sync
from django.utils import timezone


# class ChatConsumer(WebsocketConsumer):
class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.user = self.scope['user']
        self.id = self.scope['url_route']['kwargs']['course_id']
        self.room_group_name = 'chat_%s' % self.id
        # async_to_sync(self.channel_layer.group_add)(self.room_group_name, self.channel_name)
        await self.channel_layer.group_add(self.room_group_name, self.channel_name)
        # accept connection
        await self.accept()

    async def disconnect(self, code):
        # async_to_sync(self.channel_layer.group_dicard)(self.room_group_name, self.channel_name)
        await self.channel_layer.group_dicard(self.room_group_name, self.channel_name)

    # receive message from WebSocket
    async def receive(self, text_data=None, bytes_data=None):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]
        # send message to WebSocket
        # self.send(text_data=json.dumps({'message': message}))
        now = timezone.now()

        # send message to room group
        await self.channel_layer.group_send(self.room_group_name, {
            "type": "chat_message",
            "message": message,
            "user": self.user.username,
            "datetime": now.isoformat(),
        })

    async def chat_message(self, event):
        await self.send(text_data=json.dumps(event))

新增websocket路由

chat下新增routing.py

python 复制代码
from django.urls import re_path
from chat import consumers


websocket_urlpatterns = [
    re_path(r'ws/chat/room/(?P<course_id>\d+)/$', consumers.ChatConsumer.as_asgi()),
]

创建asgi的application

在instance/instance下新建routing.py

这里我们使用的ProtocolTypeRouter 协议路由,区分不同的协议使用不同的路由方式

http一定要配置,否则正常的http请求无法处理,会报错

其次就是新增了websocket协议

如此创建之后,才能应用websocket协议

python 复制代码
import os

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

import chat.routing


os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'instance.settings')


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

配置asgi应用

instance/instance/settings.py中新增配置项

python 复制代码
...
ASGI_APPLICATION = 'instance.routing.application'
...

修改项目的asgi文件

修改instance/instance/asgi.py

python 复制代码
import os
import django
from django.core.asgi import get_asgi_application
from channels.routing import get_default_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'instance.settings')
django.setup()
# application = get_asgi_application()
application = get_default_application()

OK,走到这一步,就可以在聊天室内发送消息了,只不过我们为了模拟而已,所以没有两个用户,只是把发送过来的消息再转发回client端,并显示在页面上

你是不是用了python manage.py runserver 启动,发现没有任何变化,然后打开F12 发现websocket链接失败

别急,往下看

启动通道层

通道层类似于传话筒,能够实现不同应用之间的通信,也可以支持使用者实例之间的通信

一般常用的通道层后台是redis

这里我们为了演示,就直接本地内存作为通道层缓存

settings中配置

配置下面的项,则自动启动通道层

不配置或者保留为空,则不启动

python 复制代码
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels.layers.InMemoryChannelLayer",
    }
}

daphne部署

如果你使用了python manage.py runserver 发现没有效果,可以尝试daphne部署

django项目的测试服务器,默认启动的都是wsgi程序,不支持异步处理

而websocket的应用一般都是异步(同步的话就没有了意义)

所以我们要使用能够部署异步服务器的工具

就是daphne

daphne 支持asgi应用,基于我们上面写好的代码和创建的项目结构,启动方式如下

shell 复制代码
daphne instance.asgi:application
# 或者
daphne instance.routing:application

此时应该就可以了!

验证

启动之后,可以浏览器中多打开几个页面,因为我们没有做鉴权,所以每个页面都会创建一个websocket链接,相当于多个client

在其中一个页面中发送消息,那么其他页面也会同步刷新

之后优化一下页面结构,那就是一个简易聊天室了

总结

channels是一个能够处理实时协议的强大工具,这个协议可以是http,websocket,也可以是其他的需要实时通讯的情况。

daphne命令可以启动验证,也可以直接使用在生产环境,是安全的

相关推荐
数据智能老司机2 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机3 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机3 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机3 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i3 小时前
drf初步梳理
python·django
每日AI新事件3 小时前
python的异步函数
python
Raymond运维3 小时前
MariaDB源码编译安装(二)
运维·数据库·mariadb
沢田纲吉4 小时前
🗄️ MySQL 表操作全面指南
数据库·后端·mysql
这里有鱼汤4 小时前
miniQMT下载历史行情数据太慢怎么办?一招提速10倍!
前端·python
databook13 小时前
Manim实现脉冲闪烁特效
后端·python·动效