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

channels介绍

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

准备工作

创建python虚拟环境

mkdir instance
cd install
python -m venv venv

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

安装channels

pip install channels

安装django

pip install django

安装daphne

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

pip install daphne

创建django项目

django-admin startproject instance 

创建chat应用

django-admin startapp chat

配置instance项目

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

建议最好用pycharm打开

# 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

{% 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代码

{% 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

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

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

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

启动同步服务器

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

python manage.py runserver

启动后,在浏览器中打开网站http://127.0.0.1:8000/chat/room/1/ 可以看到页面
我这里有样式设置,所以可能你们电脑上看到的样式不一样,但是基本结构时一样的
在这里插入图片描述

搭建websocket服务

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

所以可以直接配置channels

创建channels路由

新增协议使用者

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

chat目录下新建consumers.py

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

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

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协议

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中新增配置项

...
ASGI_APPLICATION = 'instance.routing.application'
...

修改项目的asgi文件

修改instance/instance/asgi.py

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中配置

配置下面的项,则自动启动通道层
不配置或者保留为空,则不启动

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels.layers.InMemoryChannelLayer",
    }
}

daphne部署

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

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

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

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

就是daphne

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

daphne instance.asgi:application
# 或者
daphne instance.routing:application

此时应该就可以了!

验证

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

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

之后优化一下页面结构,那就是一个简易聊天室了
在这里插入图片描述
在这里插入图片描述

总结

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

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

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的 Django-channels 实现 WebSocket 的示例: 首先,安装 Django-channels: ``` pip install channels ``` 然后,在 Django 的 settings.py 中添加以下配置: ```python INSTALLED_APPS = [ ... 'channels', ] ASGI_APPLICATION = 'myproject.routing.application' CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels.layers.InMemoryChannelLayer', }, } ``` 在 myproject 目录下创建一个 routing.py 文件,添加以下内容: ```python from channels.routing import ProtocolTypeRouter, URLRouter from django.urls import path from myapp.consumers import MyConsumer application = ProtocolTypeRouter({ 'websocket': URLRouter([ path('ws/', MyConsumer.as_asgi()), ]), }) ``` 在 myapp 目录下创建一个 consumers.py 文件,添加以下内容: ```python from channels.generic.websocket import AsyncWebsocketConsumer import json class MyConsumer(AsyncWebsocketConsumer): async def connect(self): await self.accept() async def disconnect(self, close_code): pass async def receive(self, text_data): text_data_json = json.loads(text_data) message = text_data_json['message'] await self.send(text_data=json.dumps({ 'message': message })) ``` 在你的模板中添加以下 JavaScript 代码,用于连接 WebSocket 和发送消息: ```javascript const socket = new WebSocket('ws://' + window.location.host + '/ws/'); socket.onmessage = function(e) { const data = JSON.parse(e.data); console.log(data); }; socket.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; socket.send(JSON.stringify({ 'message': message })); messageInputDom.value = ''; }; ``` 最后,启动 Django 服务器,打开浏览器访问你的应用程序,进入调试器,打开控制台,你应该可以看到从服务器发送的消息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值