热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Django使用Channels实现WebSocket--下篇

希望通过对这两篇文章的学习,能够对Channels有更加深入的了解,使用起来得心应手游刃有余通过上一篇《Django使用Channels实现WebSocket--上篇》的学习应该对Cha

希望通过对这两篇文章的学习,能够对Channels有更加深入的了解,使用起来得心应手游刃有余

通过上一篇《Django使用Channels实现WebSocket--上篇》的学习应该对Channels的各种概念有了清晰的认知,可以顺利的将Channels框架集成到自己的Django项目中实现WebSocket了,本篇文章将以一个Channels+Celery实现web端tailf功能的例子更加深入的介绍Channels

先说下我们要实现的目标:所有登录的用户可以查看tailf日志页面,在页面上能够选择日志文件进行监听,多个页面终端同时监听任何日志都互不影响,页面同时提供终止监听的按钮能够终止前端的输出以及后台对日志文件的读取

最终实现的结果见下图

接着我们来看下具体的实现过程

技术实现

所有代码均基于以下软件版本:

  • python==3.6.3
  • django==2.2
  • channels==2.1.7
  • celery==4.3.0

celery4在windows下支持不完善,所以请在linux下运行测试

日志数据定义

我们只希望用户能够查询固定的几个日志文件,就不是用数据库仅借助settings.py文件里写全局变量来实现数据存储

在settings.py里添加一个叫TAILF的变量,类型为字典,key标识文件的编号,value标识文件的路径

TAILF = {
    1: '/ops/coffee/error.log',
    2: '/ops/coffee/access.log',
}

基础Web页面搭建

假设你已经创建好了一个叫tailf的app,并添加到了settings.py的INSTALLED_APPS中,app的目录结构大概如下

tailf
    - migrations
        - __init__.py
    - __init__.py
    - admin.py
    - apps.py
    - models.py
    - tests.py
    - views.py

依然先构建一个标准的Django页面,相关代码如下

url:

from django.urls import path
from django.contrib.auth.views import LoginView,LogoutView

from tailf.views import tailf

urlpatterns = [
    path('tailf', tailf, name='tailf-url'),

    path('login', LoginView.as_view(template_name='login.html'), name='login-url'),
    path('logout', LogoutView.as_view(template_name='login.html'), name='logout-url'),
]

因为我们规定只有通过登录的用户才能查看日志,所以引入Django自带的LoginView,logoutView帮助我们快速构建Login,Logout功能

指定了登录模板使用login.html,它就是一个标准的登录页面,post传入username和password两个参数即可,不贴代码了

view:

from django.conf import settings
from django.shortcuts import render
from django.contrib.auth.decorators import login_required


# Create your views here.
@login_required(login_url='/login')
def tailf(request):
    logDict = settings.TAILF
    return render(request, 'tailf/index.html', {"logDict": logDict})

引入了login_required装饰器,来判断用户是否登录,未登录就给跳到/login登录页面

logDict 去setting里取我们定义好的TAILF字典赋值,并传递给前端

template:

{% extends "base.html" %}

{% block content %}


{% endblock %}

前端拿到TAILF后通过循环的方式填充到select选择框下,因为数据是字典格式,使用logDict.items的方式可以循环出字典的key和value

这样一个日志监听页面就完成了,但还无法实现日志的监听,继续往下

集成Channels实现WebSocket

日志监听功能主要的设计思路就是页面跟后端服务器建立websocket长连接,后端通过celery异步执行while循环不断的读取日志文件然后发送到websocket的channel里,实现页面上的实时显示

接着我们来集成channels

  1. 先添加routing路由,直接修改webapp/routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter

from django.urls import path, re_path
from chat.consumers import ChatConsumer
from tailf.consumers import TailfConsumer

application = ProtocolTypeRouter({
    'websocket': AuthMiddlewareStack(
        URLRouter([
            path('ws/chat/', ChatConsumer),
            re_path(r'^ws/tailf/(?P\d+)/$', TailfConsumer),
        ])
    )
})

直接将路由信息写入到了URLRouter里,注意路由信息的外层多了一个list,区别于上一篇中介绍的写路由文件路径的方式

页面需要将监听的日志文件传递给后端,我们使用routing正则P\d+传文件ID给后端程序,后端程序拿到ID之后根据settings中指定的TAILF解析出日志路径

routing的写法跟Django中的url写法完全一致,使用re_path匹配正则routing路由

  1. 添加consumer在tailf/consumers.py文件中
import json
from channels.generic.websocket import WebsocketConsumer
from tailf.tasks import tailf


class TailfConsumer(WebsocketConsumer):
    def connect(self):
        self.file_id = self.scope["url_route"]["kwargs"]["id"]

        self.result = tailf.delay(self.file_id, self.channel_name)

        print('connect:', self.channel_name, self.result.id)
        self.accept()

    def disconnect(self, close_code):
        # 中止执行中的Task
        self.result.revoke(terminate=True)
        print('disconnect:', self.file_id, self.channel_name)

    def send_message(self, event):
        self.send(text_data=json.dumps({
            "message": event["message"]
        }))

这里使用Channels的单通道模式,每一个新连接都会启用一个新的channel,彼此互不影响,可以随意终止任何一个监听日志的请求

connect

我们知道self.scope类似于Django中的request,记录了丰富的请求信息,通过self.scope["url_route"]["kwargs"]["id"]取出routing中正则匹配的日志ID

然后将idchannel_name传递给celery的任务函数tailf,tailf根据id取到日志文件的路径,然后循环文件,将新内容根据channel_name写入对应channel

disconnect

当websocket连接断开的时候我们需要终止Celery的Task执行,以清除celery的资源占用

终止Celery任务使用到revoke指令,采用如下代码来实现

self.result.revoke(terminate=True)

注意self.result是一个result对象,而非id

参数terminate=True的意思是是否立即终止Task,为True时无论Task是否正在执行都立即终止,为False(默认)时需要等待Task运行结束之后才会终止,我们使用了While循环不设置为True就永远不会终止了

终止Celery任务的另外一种方法是:

from webapp.celery import app
app.control.revoke(result.id, terminate=True)

send_message

方便我们通过Django的view或者Celery的task调用给channel发送消息,官方也比较推荐这种方式

使用Celery异步循环读取日志

上边已经集成了Channels实现了WebSocket,但connect函数中的celery任务tailf还没有实现,下边来实现它

关于Celery的详细内容可以看这篇文章:《Django配置Celery执行异步任务和定时任务》,本文就不介绍集成使用以及细节原理,只讲一下任务task

task实现代码如下:

from __future__ import absolute_import
from celery import shared_task

import time
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from django.conf import settings


@shared_task
def tailf(id, channel_name):
    channel_layer = get_channel_layer()
    filename = settings.TAILF[int(id)]

    try:
        with open(filename) as f:
            f.seek(0, 2)

            while True:
                line = f.readline()

                if line:
                    print(channel_name, line)
                    async_to_sync(channel_layer.send)(
                        channel_name,
                        {
                            "type": "send.message",
                            "message": "微信公众号【运维咖啡吧】原创 版权所有 " + str(line)
                        }
                    )
                else:
                    time.sleep(0.5)
    except Exception as e:
        print(e)

这里边主要涉及到Channels中另一个非常重要的点:从Channels的外部发送消息给Channel

其实上篇文章中检查通道层是否能够正常工作的时候使用的方法就是从外部给Channel通道发消息的示例,本文的具体代码如下

async_to_sync(channel_layer.send)(
    channel_name,
    {
        "type": "send.message",
        "message": "微信公众号【运维咖啡吧】原创 版权所有 " + str(line)
    }
)

channel_name 对应于传递给这个任务的channel_name,发送消息给这个名字的channel

type 对应于我们Channels的TailfConsumer类中的send_message方法,将方法中的_换成.即可

message 就是要发送给这个channel的具体信息

上边是发送给单Channel的情况,如果是需要发送到Group的话需要使用如下代码

async_to_sync(channel_layer.group_send)(
    group_name,
    {
        'type': 'chat.message',
        'message': '欢迎关注公众号【运维咖啡吧】'
    }
)

只需要将发送单channel的send改为group_sendchannel_name改为group_name即可

需要特别注意的是:使用了channel layer之后一定要通过async_to_sync来异步执行

页面添加WebSocket支持

后端功能都已经完成,我们最后需要添加前端页面支持WebSocket

  function connect() {
    if ( $('#file').val() ) {
      window.chatSocket = new WebSocket(
        'ws://' + window.location.host + '/ws/tailf/' + $('#file').val() + '/');

      chatSocket.Onmessage= function(e) {
        var data = JSON.parse(e.data);
        var message = data['message'];
        document.querySelector('#chat-log').value += (message);
        // 跳转到页面底部
        $('#chat-log').scrollTop($('#chat-log')[0].scrollHeight);
      };

      chatSocket.Onerror= function(e) {
        toastr.error('服务端连接异常!')
      };

      chatSocket.Onclose= function(e) {
        toastr.error('websocket已关闭!')
      };
    } else {
      toastr.warning('请选择要监听的日志文件')
    }
  }

上一篇文章中有详细介绍过websocket的消息类型,这里不多介绍了

至此我们一个日志监听页面完成了,包含了完整的监听功能,但还无法终止,接着看下面的内容

Web页面主动断开WebSocket

web页面上“终止监听”按钮的主要逻辑就是触发WebSocket的onclose方法,从而可以触发Channels后端consumer的disconnect方法,进而终止Celery的循环读取日志任务

前端页面通过.close()可以直接触发WebSocket关闭,当然你如果直接关掉页面的话也会触发WebSocket的onclose消息,所以不用担心Celery任务无法结束的问题

  function goclose() {
    console.log(window.chatSocket);

    window.chatSocket.close();
    window.chatSocket.Onclose= function(e) {
      toastr.success('已终止日志监听!')
    };
  }

至此我们包含完善功能的Tailf日志监听、终止页面就全部完成了

写在最后

两篇文章结束不知道你是否对Channels有了更深一步的了解,能够操刀上手将Channels用在自己的项目中,实现理想的功能。个人觉得Channels的重点和难点在于对channel layer的理解和运用,真正的理解了并能熟练运用,相信你一定能够举一反三完美实现更多需求。最后如果对本文的demo源码感兴趣可以关注微信公众号【运维咖啡吧】后台回复小二加我微信向我索取,一定有求必应


相关文章推荐阅读:

  • Django使用Channels实现WebSocket--上篇
  • Django配置Celery执行异步任务和定时任务

推荐阅读
  • 在 CentOS 7 系统中安装 Scrapy 时遇到了一些挑战。尽管 Scrapy 在 Ubuntu 上安装简便,但在 CentOS 7 上需要额外的配置和步骤。本文总结了常见问题及其解决方案,帮助用户顺利安装并使用 Scrapy 进行网络爬虫开发。 ... [详细]
  • 服务器部署中的安全策略实践与优化
    服务器部署中的安全策略实践与优化 ... [详细]
  • Python 伦理黑客技术:深入探讨后门攻击(第三部分)
    在《Python 伦理黑客技术:深入探讨后门攻击(第三部分)》中,作者详细分析了后门攻击中的Socket问题。由于TCP协议基于流,难以确定消息批次的结束点,这给后门攻击的实现带来了挑战。为了解决这一问题,文章提出了一系列有效的技术方案,包括使用特定的分隔符和长度前缀,以确保数据包的准确传输和解析。这些方法不仅提高了攻击的隐蔽性和可靠性,还为安全研究人员提供了宝贵的参考。 ... [详细]
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
  • 在Conda环境中高效配置并安装PyTorch和TensorFlow GPU版的方法如下:首先,创建一个新的Conda环境以避免与基础环境发生冲突,例如使用 `conda create -n pytorch_gpu python=3.7` 命令。接着,激活该环境,确保所有依赖项都正确安装。此外,建议在安装过程中指定CUDA版本,以确保与GPU兼容性。通过这些步骤,可以确保PyTorch和TensorFlow GPU版的顺利安装和运行。 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 帝国CMS中的信息归档功能详解及其重要性
    本文详细解析了帝国CMS中的信息归档功能,并探讨了其在内容管理中的重要性。通过归档功能,用户可以有效地管理和组织大量内容,提高网站的运行效率和用户体验。此外,文章还介绍了如何利用该功能进行数据备份和恢复,确保网站数据的安全性和完整性。 ... [详细]
  • Python 3与PyCharm的安装及使用指南:初学者第一课(2018年3月28日)
    本文为初学者提供了详细的 Python 3 和 PyCharm 安装及使用指南。首先,针对 Windows 系统的用户,介绍了如何从搜狐镜像网站下载适合操作系统的 Python 3.6.4 版本,并确保选择正确的 32 位或 64 位安装包。此外,还详细说明了 PyCharm 的安装步骤,帮助用户快速上手开发环境配置。通过本指南,初学者可以轻松完成 Python 开发环境的搭建,为后续学习打下坚实基础。 ... [详细]
  • FreeBSD环境下PHP GD库安装问题的详细解决方案
    在 FreeBSD 环境下,安装 PHP GD 库时可能会遇到一些常见的问题。本文详细介绍了从配置到编译的完整步骤,包括解决依赖关系、配置选项以及常见错误的处理方法。通过这些详细的指导,开发者可以顺利地在 FreeBSD 上完成 PHP GD 库的安装,确保其正常运行。此外,本文还提供了一些优化建议,帮助提高安装过程的效率和稳定性。 ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • MATLAB字典学习工具箱SPAMS:稀疏与字典学习的详细介绍、配置及应用实例
    SPAMS(Sparse Modeling Software)是一个强大的开源优化工具箱,专为解决多种稀疏估计问题而设计。该工具箱基于MATLAB,提供了丰富的算法和函数,适用于字典学习、信号处理和机器学习等领域。本文将详细介绍SPAMS的配置方法、核心功能及其在实际应用中的典型案例,帮助用户更好地理解和使用这一工具箱。 ... [详细]
  • Python 程序转换为 EXE 文件:详细解析 .py 脚本打包成独立可执行文件的方法与技巧
    在开发了几个简单的爬虫 Python 程序后,我决定将其封装成独立的可执行文件以便于分发和使用。为了实现这一目标,首先需要解决的是如何将 Python 脚本转换为 EXE 文件。在这个过程中,我选择了 Qt 作为 GUI 框架,因为之前对此并不熟悉,希望通过这个项目进一步学习和掌握 Qt 的基本用法。本文将详细介绍从 .py 脚本到 EXE 文件的整个过程,包括所需工具、具体步骤以及常见问题的解决方案。 ... [详细]
  • Linux基础知识:Vi与Vim编辑器详解
    Linux基础知识:Vi与Vim编辑器详解 ... [详细]
  • 通过使用 `pandas` 库中的 `scatter_matrix` 函数,可以有效地绘制出多个特征之间的两两关系。该函数不仅能够生成散点图矩阵,还能通过参数如 `frame`、`alpha`、`c`、`figsize` 和 `ax` 等进行自定义设置,以满足不同的可视化需求。此外,`diagonal` 参数允许用户选择对角线上的图表类型,例如直方图或密度图,从而提供更多的数据洞察。 ... [详细]
  • 在Ubuntu系统中安装Android SDK的详细步骤及解决“Failed to fetch URL https://dlssl.google.com/”错误的方法
    在Ubuntu 11.10 x64系统中安装Android SDK的详细步骤,包括配置环境变量和解决“Failed to fetch URL https://dlssl.google.com/”错误的方法。本文详细介绍了如何在该系统上顺利安装并配置Android SDK,确保开发环境的稳定性和高效性。此外,还提供了解决网络连接问题的实用技巧,帮助用户克服常见的安装障碍。 ... [详细]
author-avatar
kafka
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有