Django+Vue实现WebSocket连接的示例代码


Posted in Javascript onMay 28, 2019

近期有一需求:前端页面点击执行任务,实时显示后端执行情况,思考一波;发现 WebSocket 最适合做这件事。

效果

测试 ping www.baidu.com 效果

点击连接建立ws连接

Django+Vue实现WebSocket连接的示例代码

后端实现

所需软件包

后端主要借助Django Channels 实现socket连接,官网文档链接

这里想实现每个连接进来加入组进行广播,所以还需要引入 channels-redis

pip

channels==2.2.0
channels-redis==2.4.0

引入

settings.py

INSTALLED_APPS = [
  'django.contrib.admin',
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.messages',
  'django.contrib.staticfiles',
  'rest_framework.authtoken',
  'rest_framework',
        ...
  'channels',
]

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

代码

apps/consumers.py

新建一个消费处理

实现: 默认连接加入组,发送信息时的处理。

from channels.generic.websocket import WebsocketConsumer

class MyConsumer(WebsocketConsumer):
  def connect(self):
    """
    每个任务作为一个频道
    默认进入对应任务执行频道
    """
    self.job_name = self.scope['url_route']['kwargs']['job_name']
    self.job_group_name = 'job_%s' % self.job_name
    async_to_sync(self.channel_layer.group_add)(
      self.job_group_name,
      self.channel_name
    )
    self.accept()

  def disconnect(self, close_code):
    async_to_sync(self.channel_layer.group_discard)(
      self.job_group_name,
      self.channel_name
    )

  # job.message类型处理
  def job_message(self, event):

    # 默认发送收到信息
    self.send(text_data=event["text"])

apps/routing.py

ws类型路由

实现:ws/job/<job_name>由 MyConsumer 去处理。

from . import consumers
from django.urls import path
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.sessions import SessionMiddlewareStack

application = ProtocolTypeRouter({
  'websocket': SessionMiddlewareStack(
    URLRouter(
     [
       path('ws/job/<str:job_name>', consumers.MyConsumer)
     ]
    )
  ),
})

apps/views.py

在执行命令中获取 webSocket 消费通道,进行异步推送

  • 使用异步推送async_to_sync是因为在连接的时候采用的异步连接,所以推送必须采用异步推送。
  • 因为执行任务时间过长,启动触发运行时加入多线程,直接先返回ok,后端运行任务。
from subprocess import Popen,PIPE
import threading

def runPopen(job):
  """
  执行命令,返回popen
  """
  path = os.path
  Path = path.abspath(path.join(BASE_DIR, path.pardir))
  script_path = path.abspath(path.join(Path,'run.sh'))
  cmd = "sh %s %s" % (script_path, job)
  return Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)

def runScript(job):
  channel_layer = get_channel_layer()
  group_name = "job_%s" % job

  popen = runPopen(job)
  while True:
    output = popen.stdout.readline()
    if output == '' and popen.poll() is not None:
      break

    if output:
      output_text = str(output.strip())
      async_to_sync(
        channel_layer.group_send
        )(
          group_name, 
          {"type": "job.message", "text": output_text}
        )
    else:
      err = popen.stderr.readline()
      err_text = str(err.strip())
      async_to_sync(
        channel_layer.group_send
        )(
          group_name,
          {"type": "job.message", "text": err_text}
        )
      break

class StartJob(APIView): 
  def get(self, request, job=None):
    run = threading.Thread(target=runScript, args=(job,))
    run.start()
    return HttpResponse('ok')

apps/urls.py

get请求就能启动任务

urlpatterns = [
        ...
  path('start_job/<str:job>', StartJob.as_view())
]

前端实现

所需软件包

vue-native-websocket

代码实现

plugins/vueNativeWebsocket.js

import Vue from 'vue'
import VueNativeSock from '../utils/socket/Main.js'

export default function ({ store }) {
 Vue.use(VueNativeSock, 'http://localhost:8000/ws/job', {connectManually: true,});
}

nuxt.config.js

配置文件引入, 这里我使用的是 nuxt 框架

plugins: [ 
   { 
    src: '@/plugins/vueNativeWebsocket.js', 
    ***: false 
   },
  ],

封装 socket

export default (connection_url, option) => {
  // 事件
  let event = ['message', 'close', 'error', 'open'];

  // 拷贝选项字典
  let opts = Object.assign({}, option);

  // 定义实例字典
  let instance = {

   // socket实例
   socket: '',

   // 是否连接状态
   is_conncet: false,

   // 具体连接方法
   connect: function() {
    if(connection_url) {
     let scheme = window.location.protocol === 'https:' ? 'wss' : 'ws'
     connection_url = scheme + '://' + connection_url.split('://')[1];
     this.socket = new WebSocket(connection_url);
     this.initEvent();
    }else{
     console.log('wsurl?榭?);
    }
   },

   // 初始化事件
   initEvent: function() {
    for(let i = 0; i < event.length; i++){
     this.addListener(event[i]);
    }
   },

   // 判断事件
   addListener: function(event) {
    this.socket.addEventListener(event, (e) => {
     switch(event){
      case 'open':
       this.is_conncet = true;
       break;
      case 'close':
       this.is_conncet = false;
       break;
     }
     typeof opts[event] == 'function' && opts[event](e);
    });
   },

   // 发送方法,失败则回调
   send: function(data, closeCallback) {
    console.log('socket ---> ' + data)
    if(this.socket.readyState >= 2) {
     console.log('ws已经关闭');
     closeCallback && closeCallback();
    }else{
     this.socket.send(data);
    }
   }

  };

  // 调用连接方法
  instance.connect();
  return instance;
 }

index.vue

具体代码

x2Str 方法,因为后端返回的是bytes,格式 b'xxx' ,编写了方法对其进行转换。

<template>
    <div>

        <el-button type="primary" @click="runFunction" >执行</el-button>
        <el-button type="primary" @click="connectWebSock" >显示</el-button>

  <div class="socketView">
   <span v-for="i in socketMessage" :key="i">{{i}}</span>
  </div>
 </div>
</template>
<script>
 import R from '@/plugins/axios';
 import ws from '@/plugins/socket'
 export default {
  data() {
   return {
    webSocket: '',
    socketMessage: [],
   }
  },

    methods: {
     // 打开连接的处理
   openSocket(e) {
    if (e.isTrusted) {
     const h = this.$createElement;
     this.$notify({
      title: '提示',
      message: h('i', { style: 'color: teal'}, '已建立Socket连接')
     });
    }
   },

  // 连接时的处理
  listenSocket(e) {
   if (e.data){
    this.socketMessage.push(this.x2Str(e.data))
   }
  },

  // 连接webSocket
        connectWebSock() {
   let wsuri = process.env.BACKEND_URL + '/ws/job/' + this.selectFunctions
   this.webSocket = ws(wsuri, {
    open: e => this.openSocket(e),
    message: e => this.listenSocket(e),
    close: e => this.closeSocket(e)
   })
  },

     // 转码
  x2Str(str) {
   if (str) {
    let reg = new RegExp("(?<=^b').*(?='$)")
    let result = str.replace(/(?:\\x[\da-fA-F]{2})+/g, m =>
     decodeURIComponent(m.replace(/\\x/g, '%'))
    )
    return reg.exec(result)[0]
   }
  },

  // 执行方法
  runFunction() {
   R.myRequest('GET','api/start_job/' + this.selectFunctions, {}, {}).then((response) => {
    if (response.hasOwnProperty('response')){
      this.$message({
      type: 'error',
      message: '服务端返回错误,返回码:' + response.response.status 
      });
    }; 
    if (response.data == 'ok') {
      this.$message({
       type: 'success',
       message: '开始执行[' + this.selectFunctions + ']'
      });
    }
   });
  }   
  }
}
</script>

至此,实现前后端 websocket 通讯。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
用 JSON 处理缓存
Apr 27 Javascript
原生Js实现元素渐隐/渐现(原理为修改元素的css透明度)
Jun 24 Javascript
confirm的用法示例用于按钮操作时确定是否执行
Jun 19 Javascript
javascript仿京东导航左侧分类导航下拉菜单效果
Nov 25 Javascript
Bootstrap编写一个兼容主流浏览器的受众巨幕式风格页面
Jul 01 Javascript
KnockoutJS 3.X API 第四章之click绑定
Oct 10 Javascript
vue实现页面加载动画效果
Sep 19 Javascript
vue实现引入本地json的方法分析
Jul 12 Javascript
详解Require.js与Sea.js的区别
Aug 05 Javascript
vue input实现点击按钮文字增删功能示例
Jan 29 Javascript
vue canvas绘制矩形并解决由clearRec带来的闪屏问题
Sep 02 Javascript
详解Angular cli配置过程记录
Nov 07 Javascript
Vue3.0结合bootstrap创建多页面应用
May 28 #Javascript
Vue实现搜索结果高亮显示关键字
May 28 #Javascript
Vue2.x通用编辑组件的封装及应用详解
May 28 #Javascript
JS拖动选择table里的单元格完整实例【基于jQuery】
May 28 #jQuery
小程序多图列表实现性能优化的方法步骤
May 28 #Javascript
实例详解带参数的 npm script
May 28 #Javascript
jquery实现Ajax请求的几种常见方式总结
May 28 #jQuery
You might like
php生成excel文件的简单方法
2014/02/08 PHP
从零开始学YII2框架(二)通过 Composer 安装扩展插件
2014/08/20 PHP
PHP微信公众号自动发送红包API
2016/06/01 PHP
微信支付开发告警通知实例
2016/07/12 PHP
PHP设计模式之状态模式定义与用法详解
2018/04/02 PHP
PHP实现的数据对象映射模式详解
2019/03/20 PHP
PHP笛卡尔积实现原理及代码实例
2020/12/09 PHP
jQuery总体架构的理解分析
2011/03/07 Javascript
$.getJSON在IE下失效的原因分析及解决方法
2013/06/16 Javascript
jquery将一个表单序列化为一个对象的方法
2013/12/02 Javascript
JS+DIV实现鼠标划过切换层效果的方法
2015/05/25 Javascript
jQuery 1.9.1源码分析系列(十四)之常用jQuery工具
2015/12/02 Javascript
基于JavaScript实现图片点击弹出窗口而不是保存
2016/02/06 Javascript
AngularJS入门教程之AngularJS表达式
2016/04/18 Javascript
详解nodejs异步I/O和事件循环
2017/06/07 NodeJs
vue自定义全局组件(自定义插件)的用法
2018/01/30 Javascript
详解Webpack + ES6 最新环境搭建与配置
2018/06/04 Javascript
vue使用keep-alive保持滚动条位置的实现方法
2019/04/09 Javascript
[55:03]LGD vs EG 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/18 DOTA
Python使用Tkinter实现机器人走迷宫
2018/01/22 Python
Python之用户输入的实例
2018/06/22 Python
基于Python列表解析(列表推导式)
2018/06/23 Python
Python面向对象之反射/自省机制实例分析
2018/08/24 Python
Python3最长回文子串算法示例
2019/03/04 Python
Django中信号signals的简单使用方法
2019/07/04 Python
Python中__repr__和__str__区别详解
2019/11/07 Python
python 6行代码制作月历生成器
2020/09/18 Python
Optimalprint加拿大:在线打印服务
2020/04/03 全球购物
英文请假条
2014/04/11 职场文书
食品工程专业求职信
2014/06/15 职场文书
日语系毕业求职信
2014/07/27 职场文书
竞选大队干部演讲稿
2014/09/11 职场文书
委托书格式要求
2015/01/28 职场文书
自荐信怎么写
2015/03/04 职场文书
人事行政助理岗位职责
2015/04/11 职场文书
Java实现多文件上传功能
2021/06/30 Java/Android