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 冒泡,选择,插入排序使用实例
Feb 05 Python
Python中的random()方法的使用介绍
May 15 Python
使用Python下载歌词并嵌入歌曲文件中的实现代码
Nov 13 Python
python 回调函数和回调方法的实现分析
Mar 23 Python
python使用正则表达式的search()函数实现指定位置搜索功能
Nov 10 Python
TensorFlow实现创建分类器
Feb 06 Python
神经网络(BP)算法Python实现及应用
Apr 16 Python
Pycharm 设置默认头的图文教程
Jan 17 Python
Python利用PyExecJS库执行JS函数的案例分析
Dec 18 Python
解决django xadmin主题不显示和只显示bootstrap2的问题
Mar 30 Python
Python selenium自动化测试模型图解
Apr 15 Python
python中pivot()函数基础知识点
Jan 03 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实现的获取网页中的图片并保存到本地的代码
2010/01/05 PHP
解析PHP中VC6 X86和VC9 X86的区别及 Non Thread Safe的意思
2013/06/28 PHP
解析php5配置使用pdo
2013/07/03 PHP
使用PHP生成二维码的两种方法(带logo图像)
2014/03/14 PHP
从盛大通行证上摘下来的身份证验证js代码
2011/01/11 Javascript
一个简单的动态加载js和css的jquery代码
2014/09/01 Javascript
jquery移动节点实例
2015/01/14 Javascript
AngularJS实现元素显示和隐藏的几个案例
2015/12/09 Javascript
Node做中转服务器转发接口
2017/10/18 Javascript
JavaScript伪数组用法实例分析
2017/12/22 Javascript
webpack中的热刷新与热加载的区别
2018/04/09 Javascript
js中split()方法得到的数组长度问题
2018/07/19 Javascript
vue监听对象及对象属性问题
2018/08/20 Javascript
记React connect的几种写法(小结)
2018/09/18 Javascript
实例介绍JavaScript中多种组合继承
2019/01/20 Javascript
详解Vue中使用插槽(slot)、聚类插槽
2019/04/12 Javascript
Vue数据驱动表单渲染,轻松搞定form表单
2019/07/19 Javascript
vue2.0 解决抽取公用js的问题
2020/07/31 Javascript
简介Python设计模式中的代理模式与模板方法模式编程
2016/02/02 Python
Flask web开发处理POST请求实现(登录案例)
2018/07/26 Python
梅尔频率倒谱系数(mfcc)及Python实现
2019/06/18 Python
淘宝秒杀python脚本 扫码登录版
2019/09/19 Python
解决Python3.8运行tornado项目报NotImplementedError错误
2020/09/02 Python
利用Python实现学生信息管理系统的完整实例
2020/12/30 Python
美国知名艺术画网站:Art.com
2017/02/09 全球购物
食品行业求职人的自我评价
2014/01/19 职场文书
趣味体育活动方案
2014/02/08 职场文书
同学会主持词
2014/03/18 职场文书
《生命的药方》教学反思
2014/04/08 职场文书
工作作风承诺书
2014/08/30 职场文书
小人国观后感
2015/06/11 职场文书
2016年小学生寒假总结
2015/10/10 职场文书
python用字节处理文件实例讲解
2021/04/13 Python
试了下Golang实现try catch的方法
2021/07/01 Golang
springboot 自定义配置 解决Boolean属性不生效
2022/03/18 Java/Android
vue中使用mockjs配置和使用方式
2022/04/06 Vue.js