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 相关文章推荐
一个js的tab切换效果代码[代码分离]
Apr 11 Javascript
js编写trim()函数及正则表达式的运用
Oct 24 Javascript
js改变鼠标的形状和样式的方法
Mar 31 Javascript
我的Node.js学习之路(三)--node.js作用、回调、同步和异步代码 以及事件循环
Jul 06 Javascript
使用Chrome浏览器调试AngularJS应用的方法
Jun 18 Javascript
JavaScript给input的value赋值引发的关于基本类型值和引用类型值问题
Dec 07 Javascript
javascript断点调试心得分享
Apr 23 Javascript
jQuery中layer分页器的使用
Mar 13 Javascript
基于 webpack2 实现的多入口项目脚手架详解
Jun 26 Javascript
微信小程序开发实现消息推送
Nov 18 Javascript
记一次react前端项目打包优化的方法
Mar 30 Javascript
JS canvas实现画板和签字板功能
Feb 23 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
IIS6的PHP最佳配置方法
2007/03/19 PHP
PHP curl 获取响应的状态码的方法
2014/01/13 PHP
以文件形式缓存php变量的方法
2015/06/26 PHP
ThinkPHP表单数据智能写入create方法实例分析
2015/09/27 PHP
php 根据URL下载远程图片、压缩包、pdf等文件到本地
2019/07/26 PHP
PHP 图片处理
2020/09/16 PHP
读jQuery之九 一些瑕疵说明
2011/06/21 Javascript
工作需要写的一个js拖拽组件
2011/07/28 Javascript
Ext JS添加子组件的误区探讨
2013/06/28 Javascript
PHP使用方法重载实现动态创建属性的get和set方法
2014/11/17 Javascript
jquery比较简洁的软键盘特效实现方法
2015/03/19 Javascript
jquery实现图片上传之前预览的方法
2015/07/11 Javascript
jQuery联动日历的实例解析
2016/12/02 Javascript
超全面的vue.js使用总结
2017/02/12 Javascript
ie下js不执行的几种可能
2017/02/28 Javascript
JavaScript和JQuery获取DIV值的方法示例
2017/03/07 Javascript
Vue 中的受控与非受控组件的实现
2018/12/17 Javascript
vuex实现购物车功能
2020/06/28 Javascript
js属性对象的hasOwnProperty方法的使用
2021/02/05 Javascript
Python实现向服务器请求压缩数据及解压缩数据的方法示例
2017/06/09 Python
回调函数的意义以及python实现实例
2017/06/20 Python
PyCharm在win10的64位系统安装实例
2017/11/26 Python
Python面向对象之反射/自省机制实例分析
2018/08/24 Python
python利用wx实现界面按钮和按钮监听和字体改变的方法
2019/07/17 Python
Django logging配置及使用详解
2019/07/23 Python
Python+Redis实现布隆过滤器
2019/12/08 Python
用python3读取python2的pickle数据方式
2019/12/25 Python
python图形开发GUI库wxpython使用方法详解
2020/02/14 Python
python 通过exifread读取照片信息
2020/12/24 Python
NFL加拿大官方网上商店:NHLShop.ca
2019/03/12 全球购物
天逸系统(武汉)有限公司Java笔试题
2015/12/29 面试题
保密工作整改情况汇报
2014/11/06 职场文书
2015年法院工作总结范文
2015/04/28 职场文书
2019同学聚会主持词
2019/05/06 职场文书
Python如何导出导入所有依赖包详解
2021/06/08 Python
win10电脑关机快捷键是哪个 win10快速关机的几种方法
2022/08/14 数码科技