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 获取中文拼音,Select自动匹配字母获取值的代码
Sep 23 Javascript
JQuery 写的个性导航菜单
Dec 24 Javascript
jQuery插件原来如此简单 jQuery插件的机制及实战
Feb 07 Javascript
JavaScript中几个重要的属性(this、constructor、prototype)介绍
May 19 Javascript
BootStrap轻松实现微信页面开发代码分享
Oct 21 Javascript
通过jsonp获取json数据实现AJAX跨域请求
Jan 22 Javascript
vue.js获取数据库数据实例代码
May 26 Javascript
详解vee-validate的使用个人小结
Jun 07 Javascript
Vue.js中轻松解决v-for执行出错的三个方案
Jun 09 Javascript
jquery.pagination.js分页使用教程
Oct 23 jQuery
使用Vue.js中的过滤器实现幂方求值的方法
Aug 27 Javascript
Vue获取微博授权URL代码实例
Nov 04 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和ACCESS写聊天室(二)
2006/10/09 PHP
PHP数组相加操作及与array_merge的区别浅析
2016/11/26 PHP
PHP的PDO连接讲解
2019/01/24 PHP
JavaScript confirm选择判断
2008/10/18 Javascript
通过判断JavaScript的版本实现执行不同的代码
2010/05/11 Javascript
javascript 寻找错误方法整理
2014/06/15 Javascript
JSONObject使用方法详解
2015/12/17 Javascript
Bootstrap编写一个同时适用于PC、平板、手机的登陆页面
2016/06/30 Javascript
JS添加删除DIV的简单实例
2016/07/08 Javascript
javascript实现文字无缝滚动效果
2017/08/26 Javascript
基于vue的换肤功能的示例代码
2017/10/10 Javascript
JS控制只能输入数字并且最多允许小数点两位
2019/11/24 Javascript
JavaScript实现移动端弹窗后禁止滚动
2020/05/25 Javascript
原生JS实现拖拽功能
2020/12/16 Javascript
[53:18]Spirit vs Liquid Supermajor小组赛A组 BO3 第三场 6.2
2018/06/03 DOTA
Python THREADING模块中的JOIN()方法深入理解
2015/02/18 Python
python selenium UI自动化解决验证码的4种方法
2018/01/05 Python
python+opencv识别图片中的圆形
2020/03/25 Python
使用python实现抓取腾讯视频所有电影的爬虫
2019/04/15 Python
python+openCV调用摄像头拍摄和处理图片的实现
2019/08/06 Python
tensorflow使用range_input_producer多线程读取数据实例
2020/01/20 Python
Python3标准库之dbm UNIX键-值数据库问题
2020/03/24 Python
使用Python合成图片的实现代码(图片添加个性化文本,图片上叠加其他图片)
2020/04/30 Python
使用Python通过oBIX协议访问Niagara数据的示例
2020/12/04 Python
HTML最新标准HTML5总结(必看)
2016/06/13 HTML / CSS
英国经典球衣网站:Classic Football Shirts
2017/05/20 全球购物
尤妮佳moony海外旗舰店:日本殿堂级纸尿裤品牌
2018/02/23 全球购物
老师对学生的评语
2014/04/18 职场文书
初中作文评语大全
2014/04/23 职场文书
会计求职信
2014/05/29 职场文书
正科级干部考察材料
2014/05/29 职场文书
处级领导班子全部召开专题民主生活会情况汇报
2014/09/27 职场文书
土地转让协议书
2014/09/27 职场文书
JavaScript小技巧带你提升你的代码技能
2021/09/15 Javascript
MySql数据库触发器使用教程
2022/06/01 MySQL
postgresql如何找到表中重复数据的行并删除
2023/05/08 MySQL