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获取某年中每个月的第一天和最后一天
Dec 15 Python
Python进阶-函数默认参数(详解)
May 18 Python
Python内存管理方式和垃圾回收算法解析
Nov 11 Python
Python使用pickle模块实现序列化功能示例
Jul 13 Python
Django中的文件的上传的几种方式
Jul 23 Python
Python玩转加密的技巧【推荐】
May 13 Python
python射线法判断检测点是否位于区域外接矩形内
Jun 28 Python
通过python实现弹窗广告拦截过程详解
Jul 10 Python
matplotlib绘制多个子图(subplot)的方法
Dec 03 Python
python实现3D地图可视化
Mar 25 Python
使用OpenCV获取图像某点的颜色值,并设置某点的颜色
Jun 02 Python
python爬虫基础之urllib的使用
Dec 31 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中上传大体积文件时需要的设置
2006/10/09 PHP
『PHP』PHP截断函数mb_substr()使用介绍
2013/04/22 PHP
destoon二次开发模板及调用语法汇总
2014/06/21 PHP
php的laravel框架快速集成微信登录的方法
2016/12/12 PHP
php+redis实现多台服务器内网存储session并读取示例
2017/01/12 PHP
关于B/S判断浏览器断开的问题讨论
2008/10/29 Javascript
javascript 简练的几个函数
2009/08/29 Javascript
js数字输入框(包括最大值最小值限制和四舍五入)
2009/11/24 Javascript
javaScript中push函数用法实例分析
2015/06/08 Javascript
给before和after伪元素设置js效果的方法
2015/12/04 Javascript
jquery实现文本框textarea自适应高度
2016/03/09 Javascript
jQuery使用Selectator插件实现多选下拉列表过滤框(附源码下载)
2016/04/08 Javascript
数据结构中的各种排序方法小结(JS实现)
2016/07/23 Javascript
JavaScript实现横线提示输入验证码随输入验证码输入消失的方法
2016/09/24 Javascript
js按条件生成随机json:randomjson实现方法
2017/04/07 Javascript
jquery平滑滚动到顶部插件使用详解
2017/05/08 jQuery
angular 实现的输入框数字千分位及保留几位小数点功能示例
2018/06/19 Javascript
详解webpack模块加载器兼打包工具
2018/09/11 Javascript
详解Vue 项目中的几个实用组件(ts)
2019/10/29 Javascript
jQuery 函数实例分析【函数声明、函数表达式、匿名函数等】
2020/05/19 jQuery
原生JS实现多条件筛选
2020/08/19 Javascript
[01:36]DOTA2完美大师赛趣味视频之与队友相处的十万个技巧
2017/11/19 DOTA
Python实现向QQ群成员自动发邮件的方法
2014/11/19 Python
在windows系统中实现python3安装lxml
2016/03/23 Python
Python实现KNN邻近算法
2021/01/28 Python
django中模板的html自动转意方法
2018/05/27 Python
python3利用tcp实现文件夹远程传输
2018/07/28 Python
python中多个装饰器的执行顺序详解
2018/10/08 Python
python将字母转化为数字实例方法
2019/10/04 Python
django框架中ajax的使用及避开CSRF 验证的方式详解
2019/12/11 Python
CSS3属性 line-clamp控制文本行数的使用
2020/03/19 HTML / CSS
广州地球村科技数据库题目
2016/04/25 面试题
没有孩子的离婚协议书怎么写
2014/09/17 职场文书
个性发展自我评价2015
2015/03/09 职场文书
2015年董事长秘书工作总结
2015/07/23 职场文书
银行柜员工作心得体会
2016/01/23 职场文书