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 相关文章推荐
jQuery 关于伪类选择符的使用说明
Apr 24 Javascript
jquery中one()方法的用法实例
Jan 16 Javascript
jQuery实现简易的天天爱消除小游戏
Oct 16 Javascript
对js eval()函数的一些见解
Aug 15 Javascript
Javascript动画效果(3)
Oct 11 Javascript
JavaScript队列函数和异步执行详解
Jun 19 Javascript
写一个移动端惯性滑动&amp;回弹Vue导航栏组件 ly-tab
Mar 06 Javascript
浅析vue.js数组的变异方法
Jun 30 Javascript
浅谈Angularjs中不同类型的双向数据绑定
Jul 16 Javascript
微信域名检测接口调用演示步骤(含PHP、Python)
Dec 08 Javascript
vue 组件简介
Jul 31 Javascript
Vue执行方法,方法获取data值,设置data值,方法传值操作
Aug 05 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全排列递归算法代码
2012/10/09 PHP
PHP5中实现多态的两种方法实例分享
2014/04/21 PHP
php 的反射详解及示例代码
2016/08/25 PHP
简单的自定义php模板引擎
2016/08/26 PHP
PHP实现的简单对称加密与解密方法实例小结
2017/08/28 PHP
IE与FireFox的兼容性问题分析
2007/04/22 Javascript
javascript globalStorage类代码
2009/06/04 Javascript
扩展jQuery 键盘事件的几个基本方法
2009/10/30 Javascript
jQuery中:last选择器用法实例
2014/12/30 Javascript
jQuery使用hide方法隐藏元素自身用法实例
2015/03/30 Javascript
前端框架Vue.js中Directive知识详解
2016/09/12 Javascript
用jquery快速解决IE输入框不能输入的问题
2016/10/04 Javascript
使用BootStrap实现表格隔行变色及hover变色并在需要时出现滚动条
2017/01/04 Javascript
Node.js制作简单聊天室
2017/01/12 Javascript
JavaScript 数据类型详解
2017/03/13 Javascript
js案例之鼠标跟随jquery版(实例讲解)
2017/07/21 jQuery
jQuery中的$是什么意思及 $. 和 $().的区别
2018/04/20 jQuery
9个JavaScript日常开发小技巧
2020/10/06 Javascript
Array.filter中如何正确使用Async
2020/11/04 Javascript
python中利用xml.dom模块解析xml的方法教程
2017/05/24 Python
python定时利用QQ邮件发送天气预报的实例
2017/11/17 Python
Linux系统(CentOS)下python2.7.10安装
2018/09/26 Python
Django model序列化为json的方法示例
2018/10/16 Python
python实现简单名片管理系统
2018/11/30 Python
python字典嵌套字典的情况下找到某个key的value详解
2019/07/10 Python
python虚拟环境模块venv使用及示例
2020/03/04 Python
Pandas缺失值2种处理方式代码实例
2020/06/13 Python
Python调用C/C++的方法解析
2020/08/05 Python
目前不被任何主流浏览器支持的CSS3属性汇总
2014/07/21 HTML / CSS
如何选择使用结构还是类
2014/05/30 面试题
客运企业隐患排查工作方案
2014/06/06 职场文书
学校学习雷锋活动总结
2014/07/03 职场文书
高中学生自我评价范文
2014/09/23 职场文书
2015年元旦促销方案书
2014/12/09 职场文书
2016年度基层党建工作公开承诺书
2016/03/25 职场文书
vue-element-admin项目导入和导出的实现
2021/05/21 Vue.js