Django Channel实时推送与聊天的示例代码


Posted in Python onApril 30, 2020

先来看一下最终的效果吧

Django Channel实时推送与聊天的示例代码

开始聊天,输入消息并点击发送消息就可以开始聊天了

Django Channel实时推送与聊天的示例代码

点击 “获取后端数据”开启实时推送

Django Channel实时推送与聊天的示例代码

先来简单了解一下 Django Channel

Channels是一个采用Django并将其功能扩展到HTTP以外的项目,以处理WebSocket,聊天协议,IoT协议等。它基于称为ASGI的Python规范构建。

它以Django的核心为基础,并在其下面分层了一个完全异步的层,以同步模式运行Django本身,但异步处理了连接和套接字,并提供了以两种方式编写的选择,从而实现了这一点。

详情请参考官方文档:https://channels.readthedocs.io/en/latest/introduction.html

再简单说下ASGI是什么东东吧

ASGI 由 Django 团队提出,为了解决在一个网络框架里(如 Django)同时处理 HTTP、HTTP2、WebSocket 协议。为此,Django 团队开发了 Django Channels 插件,为 Django 带来了 ASGI 能力。
在 ASGI 中,将一个网络请求划分成三个处理层面,最前面的一层,interface server(协议处理服务器),负责对请求协议进行解析,并将不同的协议分发到不同的 Channel(频道);频道属于第二层,通常可以是一个队列系统。频道绑定了第三层的 Consumer(消费者)。

详情请参考官方文档:https://channels.readthedocs.io/en/latest/asgi.html

下边来说一下具体的实现步骤

一、安装channel

pip3 install channels 
pip3 install channels_redis

二、新建Django项目

1.新建项目

django-admin startproject mysite

2.新建应用

python3 manage.py startapp chat

3.编辑mysite/settings.py文件

#注册应用
INSTALLED_APPS = [
  ....
  'chat.apps.ChatConfig',
  "channels",
]

# 在文件尾部新增如下配置

#将ASGI_APPLICATION设置设置为指向该路由对象作为您的根应用程序:
ASGI_APPLICATION = 'mysite.routing.application'

#配置Redis
CHANNEL_LAYERS = {
  'default': {
    'BACKEND': 'channels_redis.core.RedisChannelLayer',
    'CONFIG': {
      "hosts": [('10.0.6.29', 6379)],
    },
  },
}

三、详细代码与配置

1. 添加索引视图的模板

chat目录中创建一个templates目录。在您刚刚创建的templates目录中,创建另一个名为的目录chat,并在其中创建一个名为的文件index.html以保存索引视图的模板

将以下代码放入chat/templates/chat/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>

2.创建聊天与消息推送模板

chat/templates/chat/room.html

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.0/jquery.min.js" type="text/javascript"></script>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="external nofollow" >
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
  <meta charset="utf-8"/>
  <title>Chat Room</title>
</head>
<body>
<textarea id="chat-log" cols="150" rows="30" class="text"></textarea><br>
<input id="chat-message-input" type="text" size="150"><br>
<input id="chat-message-submit" type="button" value="发送消息" class="input-sm">
<button id="get_data" class="btn btn-success">获取后端数据</button>
{{ room_name|json_script:"room-name" }}

<script>

  $("#get_data").click(function () {
    $.ajax({
      url: "{% url 'push' %}",
      type: "GET",
      data: {
        "room": "{{ room_name }}",
        "csrfmiddlewaretoken": "{{ csrf_token }}"
      },
    })
  });

  const roomName = JSON.parse(document.getElementById('room-name').textContent);
  const chatSocket = new WebSocket(
    'ws://' + window.location.host
    + '/ws/chat/'
    + roomName + '/'
  );
  let chatSocketa = new WebSocket(
    "ws://" + window.location.host + "/ws/push/" + roomName
  );
  chatSocket.onmessage = function (e) {
    const data = JSON.parse(e.data);
    // data 为收到后端发来的数据
    //console.log(data);
    document.querySelector('#chat-log').value += (data.message + '\n');
  };
  chatSocketa.onmessage = function (e) {
    let data = JSON.parse(e.data);
    //let message = data["message"];
    document.querySelector("#chat-log").value += (data.message + "\n");
  };


  chatSocket.onclose = function (e) {
    console.error('Chat socket closed unexpectedly');
  };
  chatSocketa.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>

3.创建房间的视图

将以下代码放入chat/views.py

# chat/views.py
from django.shortcuts import render
from django.http import JsonResponse
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync


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


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


def pushRedis(request):
  room = request.GET.get("room")
  print(room)

  def push(msg):
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(
      room,
      {"type": "push.message", "message": msg, "room_name": room}
    )

  push("推送测试", )
  return JsonResponse({"1": 1})

4. 创建项目二级路由

在chat目录下创建一个名为的文件urls.py

# mysite/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'),
]

5. 修改根路由

# mysite/urls.py

from django.contrib import admin
from django.urls import path, include
from chat.views import pushRedis

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

6.创建一个消费者

文件chat/consumers.py

当Django接受HTTP请求时,它会查询根URLconf来查找视图函数,然后调用该视图函数来处理该请求。同样,当Channels接受WebSocket连接时,它会查询根路由配置以查找使用者,然后在使用者上调用各种功能来处理来自连接的事件。

import time
import json
from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer
from asgiref.sync import async_to_sync
import redis

pool = redis.ConnectionPool(
  host="10.0.6.29",
  port=6379,
  max_connections=10,
  decode_response=True,
)
conn = redis.Redis(connection_pool=pool, decode_responses=True)


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

    await self.channel_layer.group_add(
      self.room_group_name,
      self.channel_name,
    )
    await self.accept()

  async def disconnect(self, close_code):
    print("close_code: ", close_code)
    await self.channel_layer.group_discard(
      self.room_group_name,
      self.channel_name
    )

  async def receive(self, text_data=None, bytes_data=None):
    text_data_json = json.loads(text_data)
    message = text_data_json["message"]
    print("receive_message:", message)
    await self.channel_layer.group_send(
      self.room_group_name,
      {
        "type": "chat_message",
        "message": message
      }
    )

  async def chat_message(self, event):
    receive_message = event["message"]
    response_message = "You message is :" + receive_message
    await self.send(text_data=json.dumps({
      "message": response_message
    }))


class PushMessage(WebsocketConsumer):

  def connect(self):
    self.room_group_name = self.scope["url_route"]["kwargs"]["room_name"]
    async_to_sync(self.channel_layer.group_add)(
      self.room_group_name,
      self.channel_name
    )
    self.accept()

  def disconnect(self, code):
    async_to_sync(self.channel_layer.group_discard)(
      self.room_group_name,
      self.channel_name
    )

  def push_message(self, event):
    """
    主动推送
    :param event:
    :return:
    """
    print(event, type(event))
    while True:
      time.sleep(2)
      msg = time.strftime("%Y-%m-%d %H:%M:%S") + "--- room_name: %s" % event["room_name"]
      self.send(text_data=json.dumps(
        {"message": msg}
      ))

7.为项目添加websocket的路由配置

在chat目录下创建一个名为的文件routing.py

# mysite/chat/routing.py

from django.urls import re_path, path
from . import consumers

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

8.配置websocket根路由

与setting同级目录新建ws根路由文件 routing.py

from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import chat.routing

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

9.最终的文件关系如下图

Django Channel实时推送与聊天的示例代码

10.启动服务

python3 manage.py runserver 10.0.6.2:80

注意看,这和django是不一样的

Django Channel实时推送与聊天的示例代码

还有另一种更稳健的启动方式

和setting同级新增文件 asgi.py

import os
import django
from channels.routing import get_default_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
django.setup()
application = get_default_application()

启动方式为:

daphne -b 10.0.6.2 -p 80 mysite.asgi:application

daphne 在安装channel时已经自动安装好了

Django Channel实时推送与聊天的示例代码

参考:

https://channels.readthedocs.io/en/latest/tutorial/index.html

https://blog.ernest.me/post/asgi-demonstration-realtime-blogging

到此这篇关于Django Channel实时推送与聊天的示例代码的文章就介绍到这了,更多相关Django Channel实时推送与聊天内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python开发之快速搭建自动回复微信公众号功能
Apr 22 Python
Python基于list的append和pop方法实现堆栈与队列功能示例
Jul 24 Python
python实现合并多个list及合并多个django QuerySet的方法示例
Jun 11 Python
python点击鼠标获取坐标(Graphics)
Aug 10 Python
python爬虫 urllib模块url编码处理详解
Aug 20 Python
关于Numpy数据类型对象(dtype)使用详解
Nov 27 Python
MxNet预训练模型到Pytorch模型的转换方式
May 25 Python
解决pyinstaller 打包exe文件太大,用pipenv 缩小exe的问题
Jul 13 Python
Python如何截图保存的三种方法(小结)
Sep 01 Python
Python关于拓扑排序知识点讲解
Jan 04 Python
Python趣味挑战之用pygame实现简单的金币旋转效果
May 31 Python
用Python将GIF动图分解成多张静态图片
Jun 11 Python
Django ORM 查询表中某列字段值的方法
Apr 30 #Python
Python pip install如何修改默认下载路径
Apr 29 #Python
Django 5种类型Session使用方法解析
Apr 29 #Python
PyQt5连接MySQL及QMYSQL driver not loaded错误解决
Apr 29 #Python
Python退出时强制运行一段代码的实现方法
Apr 29 #Python
pyinstaller将含有多个py文件的python程序做成exe
Apr 29 #Python
如何使用python切换hosts文件
Apr 29 #Python
You might like
php中文验证码实现方法
2015/06/18 PHP
PHP性能优化大全(php.ini)
2016/05/20 PHP
php根据用户名和手机号查询是否存在手机号码
2017/02/16 PHP
深入理解Javascript闭包 新手版
2010/12/28 Javascript
javascript中的Function.prototye.bind
2015/06/25 Javascript
jQuery插件formValidator自定义函数扩展功能实例详解
2015/11/25 Javascript
自动化测试读写64位操作系统的注册表
2016/08/15 Javascript
老生常谈JavaScript 正则表达式语法
2016/08/20 Javascript
jQuery实现两列等高并自适应高度
2016/12/22 Javascript
Bootstrap 过渡效果Transition 模态框(Modal)
2017/03/17 Javascript
AngularJS 教程及实例代码
2017/10/23 Javascript
vue里面父组件修改子组件样式的方法
2018/02/03 Javascript
Es6 Generator函数详细解析
2018/02/24 Javascript
vue使用技巧及vue项目中遇到的问题
2018/06/04 Javascript
JS跨域请求的问题解析
2018/12/03 Javascript
聊聊Vue 中 title 的动态修改问题
2019/06/11 Javascript
深入探索VueJS Scoped CSS 实现原理
2019/09/23 Javascript
高效jQuery选择器的5个技巧实例分析
2019/11/26 jQuery
JS变量提升原理与用法实例浅析
2020/05/22 Javascript
Antd中单个DatePicker限定时间输入范围操作
2020/10/29 Javascript
[44:15]国士无双DOTA2 6.82版本详解(上)
2014/09/28 DOTA
[59:26]DOTA2上海特级锦标赛D组资格赛#1 EG VS VP第二局
2016/02/28 DOTA
python 正则表达式 概述及常用字符
2009/05/04 Python
基于Python Shell获取hostname和fqdn释疑
2016/01/25 Python
Python探索之URL Dispatcher实例详解
2017/10/28 Python
Python实现银行账户资金交易管理系统
2020/01/03 Python
vue学习笔记之动态组件和v-once指令简单示例
2020/02/29 Python
python字典和json.dumps()的遇到的坑分析
2020/03/11 Python
Python开发入门——迭代的基本使用
2020/09/03 Python
澳大利高级泳装品牌:Bondi Born
2018/05/23 全球购物
施华洛世奇加拿大官网:SWAROVSKI加拿大
2018/06/03 全球购物
英国排名第一的LED灯泡网站:LED Bulbs
2019/09/03 全球购物
运动会表扬稿大全
2014/01/16 职场文书
小学一年级学生评语
2014/04/22 职场文书
Python 恐龙跑跑小游戏实现流程
2022/02/15 Python
python中字符串String及其常见操作指南(方法、函数)
2022/04/06 Python