更多内容请点击 我的博客 查看,欢迎来访。

本教程基于《Django使用Channels实现WebSocket消息通知功能》

逻辑简述

xterm.js : 前端模拟 shell 终端的一个库,当用户每输入一个键,就向后端发送该数据

paramiko : Python 下对 ssh2 封装的一个库,可以使用他来远程连接主机

  1. 用户首先通过网页上的WebSocket连接到后端,后端再通过SSH连接到远程主机,实现一个长连接;
  2. 连接成功后, xterm.js 在浏览器中模拟shell终端,监听用户按键,将每次按键输入通过已连接好的WebSocket;
  3. 后端通过WebSocket收到数据后,将用户输入的内容通过 paramiko 建立的 SSH 通道连接到远程主机上执行;
  4. paramiko 将远程主机上的处理结果返回给后端;
  5. 后端将返回结果通过WebSocket返回用户页面;
  6. xterm.js 将从WebSocket接收的数据显示到到模拟终端中;

创建webssh app

创建app。名为webssh

将应用添加到 settings.py

INSTALLED_APPS = [# 。。。。'webssh.apps.WebsshConfig',  # 网页远程ssh终端
]

修改应用下的 apps.py

from django.apps import AppConfigclass WebsshConfig(AppConfig):name = 'webssh'verbose_name = '远程终端'

修改应用下的 __init__.py

default_app_config = 'webssh.apps.WebsshConfig'

webssh逻辑代码

前端页面链接webssh

{% extends 'pxectrl/base-pxectrl.html' %}
{% load static %}
{% block title %}远程终端{% endblock %}{% block css %}<link href="{% static 'xterm/xterm.css' %}" rel="stylesheet"><link href="{% static 'hadmin/css/plugins/toastr/toastr.min.css' %}" rel="stylesheet">
{% endblock %}{% block content %}<div class="wrapper wrapper-content animated fadeInRight"><div class="row"><div class="col-sm-12"><div class="ibox float-e-margins" id="id-box"><div class="ibox-title"><h5>在线终端</h5></div><div class="ibox-content" id="id-content"><div class="row"><form role="form" id="id-form" autocomplete="off"><div class="col-md-4"><div class="form-group"><label>用户名</label><input type="text" placeholder="User" id="id-user" class="form-control"autocomplete="off" value="user"></div></div><div class="col-sm-4"><div class="form-group"><label>主机地址</label><input type="text" placeholder="Host" id="id-host" class="form-control" requiredautocomplete="off" value="192.168.96.20"></div></div><div class="col-sm-4"><div class="form-group"><label>端口</label><input type="text" placeholder="Port" id="id-port" class="form-control"value="22" required autocomplete="off"></div></div><div class="col-sm-12"><div class="form-group"><label>认证类型</label> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<label class="radio-inline"><input type="radio" name="auth" id="id-use-pwd" value="pwd" checked> 密码认证</label><label class="radio-inline"><input type="radio" name="auth" id="id-use-key" value="key"> 秘钥认证</label></div></div><div class="col-sm-6"><div class="form-group"><label id="id-auth-type">登录密码</label><input type="password" placeholder="Pwd" id="id-pwd" class="form-control"required autocomplete="off" value=""></div></div><div class="col-sm-6 hide" id="id-show-upload"><div class="form-group"><label>密钥文件</label><input class="filestyle" id="id-key-file" type="file"></div></div><div class="col-sm-12">注意:<p style="color: red; font-size: 10px">1、当认证类型为密码认证时, 秘钥文件上传将不可用</p><p style="color: red; font-size: 10px">2、当认证类型为秘钥认证时, 如果密码输入框不为空,则密码输入框的内容将作为秘钥的解密密码</p><button class="btn btn-sm btn-info" type="button" onclick="connectWebSocket()"><strong>连接</strong></button></div></form></div><br><hr><br><table class="table table-striped"><thead><tr><th>#</th><th>服务器</th><th>用途</th><th>SSH连接</th></tr></thead><tbody><tr><td>1</td><td>192.168.96.20</td><td>PXE服务器</td><td><button type="button" class="btn btn-info btn-sm"onclick="connectWebSocket(host_id=1)">连接</button><button type="button" class="btn btn-primary btn-sm"onclick="connectWebSocket(host_id=1, team=true)">协作连接</button></td></tr><tr><td>2</td><td>192.168.96.21</td><td>Samba服务器:镜像从服务器</td><td><button type="button" class="btn btn-info btn-sm"onclick="connectWebSocket(host_id=2)">连接</button><button type="button" class="btn btn-primary btn-sm"onclick="connectWebSocket(host_id=2, team=true)">协作连接</button></td></tr></tbody></table></div><div class="ibox-content hide" id="id-ssh-content" style="padding: 0;"><p style="position:absolute; padding: 0; right:0; margin-right:17px; text-align: center; z-index: 999"><button class="btn btn-danger btn-lg" id="id-close-conn">关闭连接</button></p><div id="terminal"></div></div></div></div></div></div>
{% endblock %}{% block js %}<!--首先引入jquery--><!-- 图片上传按钮样式 --><script src="{% static 'hadmin/js/plugins/bootstrap-filestyle/bootstrap-filestyle.min.js' %}"></script><script>//上传文件$(":file").filestyle({btnClass: "btn-default", text: "更改"});</script><script src="{% static 'xterm/xterm.js' %}"></script><script src="{% static 'hadmin/js/plugins/toastr/toastr.min.js' %}"></script><script>toastr.options = { // toastr配置"closeButton": true,"debug": false,"progressBar": true,"positionClass": "toast-top-center","showDuration": "400","hideDuration": "1000","timeOut": "3000","extendedTimeOut": "1000","showEasing": "swing","hideEasing": "linear","showMethod": "fadeIn","hideMethod": "fadeOut"};</script><script>$("#id-use-pwd").click(function () {$('#id-auth-type').html('登录密码');$('#id-show-upload').addClass('hide')});$("#id-use-key").click(function () {$('#id-auth-type').html('解密密码');$('#id-show-upload').removeClass('hide')})</script><script>function get_box_size() {let init_width = 9;let init_height = 22;let windows_width = $('#id-box').width();let windows_height = $(window).height();return {cols: Math.floor(windows_width / init_width),rows: Math.floor(windows_height / init_height),}}function connectWebSocket(host_id = null, team = false) {let cols = get_box_size().cols;let rows = get_box_size().rows;console.log(cols);//根据div的大小初始化终端,待WebSocket连接上后,显示终端let term = new Terminal({cols: cols,rows: rows,useStyle: true,cursorBlink: true});//建立WebSocket连接if (host_id === null) {//获取表单中的信息,并去掉两端空格let host = $.trim($('#id-host').val());if (host === '') {toastr.warning('主机地址不能为空', '提示');return;}let port = $.trim($('#id-port').val());if (port === '') {toastr.warning('端口不能为空', '提示');return;}let user = $.trim($('#id-user').val());if (user === '') {toastr.warning('用户名不能为空', '提示');return;}let auth = $("input[name='auth']:checked").val();console.log('选择认证方式:' + auth);let sshkey_filename = '';if (auth === 'key') {//上传密钥文件let key_file = $('#id-key-file')[0].files[0];let csrf = '{{ csrf_token }}';let formData = new FormData();formData.append('key_file', key_file);formData.append('csrfmiddlewaretoken', csrf);$.ajax({url: '{% url "webssh:upload_ssh_key" %}',type: 'post',data: formData,async: false,processData: false,contentType: false,mimeType: 'multipart/form-data',success: function (data) {sshkey_filename = data;  //返回保存文件的名称}});}let pwd = $.trim($('#id-pwd').val());pwd = window.btoa(pwd); //加密密码传输//组装为ssh连接参数let ssh_args = `user=${user}&host=${host}&port=${port}&auth=${auth}&pwd=${pwd}&sshkey_filename=${sshkey_filename}`;console.log(ssh_args);let ws_scheme = window.location.protocol === "https:" ? "wss" : "ws"; //获取协议let ws_port = (window.location.port) ? (':' + window.location.port) : '';  // 获取端口ws = new WebSocket(ws_scheme + '://' + window.location.host + ws_port + '/ws/webssh/?' + ssh_args + `&width=${cols}&height=${rows}`);} else {//指定服务器id连接if (team) {let ws_scheme = window.location.protocol === "https:" ? "wss" : "ws"; //获取协议let ws_port = (window.location.port) ? (':' + window.location.port) : '';  // 获取端口ws = new WebSocket(ws_scheme + '://' + window.location.host + ws_port + `/ws/webssh/${host_id}/` + `?width=${cols}&height=${rows}&team=${team}`);} else {let ws_scheme = window.location.protocol === "https:" ? "wss" : "ws"; //获取协议let ws_port = (window.location.port) ? (':' + window.location.port) : '';  // 获取端口ws = new WebSocket(ws_scheme + '://' + window.location.host + ws_port + `/ws/webssh/${host_id}/` + `?width=${cols}&height=${rows}`);}}//打开websocket连接,并打开终端ws.onopen = function () {console.log('WebSocket建立连接,打开Web终端');$('#id-content').addClass('hide');$('#id-ssh-content').removeClass('hide');term.open(document.getElementById('terminal'));};ws.onclose = function (e) {console.error('WebSocket关闭连接,关闭Web终端');toastr.success('SSH连接已关闭', '消息');//term.write(message);setTimeout(function () {window.location.reload();}, 3000);};//读取服务器发送的数据并写入web终端ws.onmessage = function (e) {console.log('WebSocket接收消息,ssh交互中');let data = JSON.parse(e.data);console.log(data);let message = data['message'];if (data.flag === 'msg') {term.write(message);} else if (data.flag === 'fail') {term.write(message);  //连接ssh的异常提示toastr.error(message + "返回登录页", '失败');setTimeout(function () {window.location.reload();}, 5000);} else if (data.flag === 'user') {toastr.info(message, '消息');} else if (data.flag === 'error') {toastr.error(message, '失败');//term.write(message);setTimeout(function () {window.location.reload();}, 5000);}};//向服务器发送数据,flag=1term.on('data', function (data) {//data为每个按键输入内容,例如按A,就传递给后端:{'flag': 1, 'data': 'a', 'cols': None, 'rows': None}let send_data = JSON.stringify({'flag': 'entered_key','entered_key': data,'cols': null,'rows': null});//向WebSocket发送消息,也就是输入的每一个按键ws.send(send_data)});//终端右上角显示的关闭连接安装,当点击是,关闭ws$('#id-close-conn').click(function () {ws.close();});// 监听浏览器窗口, 根据浏览器窗口大小修改终端大小$(window).resize(function () {let cols = get_box_size().cols;let rows = get_box_size().rows;console.log(`更改显示终端窗口大小,行${rows}列${cols}`);let send_data = JSON.stringify({'flag': 'resize', 'cols': cols, 'rows': rows});ws.send(send_data);term.resize(cols, rows) //调整页面终端大小})}</script>
{% endblock %}

应用视图

import time
import random
import hashlib
import os
from django.conf import settings
from django.shortcuts import render, HttpResponsedef index(request):return render(request, 'webssh/index.html')def unique():ctime = str(time.time())salt = str(random.random())m = hashlib.md5(bytes(salt, encoding='utf-8'))m.update(bytes(ctime, encoding='utf-8'))return m.hexdigest()def upload_ssh_key(request):if request.method == 'POST':key_file = request.FILES.get('key_file')if not key_file:return HttpResponse('')print(type(key_file.read()))ssh_key = key_file.read().decode('utf-8')  # 获取上传文件的内容sshkey_filename = unique()print('文件保存为唯一名称:', sshkey_filename)ssh_key_path = os.path.join(settings.MEDIA_ROOT, 'sshkey')if not os.path.exists(ssh_key_path):os.mkdir(ssh_key_path)  # 创建保存key文件的文件夹with open(os.path.join(ssh_key_path, sshkey_filename), 'w', encoding='utf-8') as f:f.write(ssh_key)return HttpResponse(sshkey_filename)

应用路由

from django.urls import path
from .views import index, upload_ssh_key
from simpleauth.tools.simpleauth_tool import simple_permission_requiredapp_name = 'webssh'urlpatterns = [path('', simple_permission_required(permission='it_sys_user')(index), name='index'),  # 终端主页path('upload_ssh_key/', simple_permission_required(permission='it_sys_user')(upload_ssh_key), name='upload_ssh_key'),  # 终端主页
]

访问 http://127.0.0.1/webssh/ 可以连接到主页

应用下创建 consumers.py

apps/webssh/consumers.py

from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer
import json
import base64
from django.http.request import QueryDict
import paramiko
import socket
from threading import Thread
import time
import os
from django.utils.six import StringIO
from django.conf import settingsdef get_key_obj(pkeyobj, pkey_file=None, pkey_obj=None, password=None):if pkey_file:with open(pkey_file) as fo:try:pkey = pkeyobj.from_private_key(fo, password=password)return pkeyexcept:passelse:try:pkey = pkeyobj.from_private_key(pkey_obj, password=password)return pkeyexcept:pkey_obj.seek(0)class SSHBridge(object):"""桥接WebSocket和ssh参考:https://blog.51cto.com/hongchen99/2336087"""def __init__(self, websocket, simpleuser):self.websocket = websocketself.simpleuser = simpleuserdef connect(self, host, user, pwd=None, key=None, port=22, timeout=6, term='xterm', pty_width=80, pty_height=24):"""建立SSH连接,放在 self.ssh_channel 通道中,之后直接在通道中交互数据:param host::param user::param pwd::param key::param port::param timeout::param term::param pty_width::param pty_height::return:"""ssh_client = paramiko.SSHClient()ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())try:if key:# 密钥方式认证pkey = get_key_obj(paramiko.RSAKey, pkey_obj=key, password=pwd) or \get_key_obj(paramiko.DSSKey, pkey_obj=key, password=pwd) or \get_key_obj(paramiko.ECDSAKey, pkey_obj=key, password=pwd) or \get_key_obj(paramiko.Ed25519Key, pkey_obj=key, password=pwd)ssh_client.connect(username=user, hostname=host, port=port, pkey=pkey, timeout=timeout)else:ssh_client.connect(hostname=host, port=port, username=user, password=pwd, timeout=timeout)except Exception as e:# pri00nt(e)message = json.dumps({'flag': 'fail', 'message': str(e)})self.websocket.send_message_or_team(message)returntransport = ssh_client.get_transport()"""另一种方式建立通道transport = paramiko.Transport((host, port,))transport.start_client()transport.auth_password(username=user, password=pwd)"""# 打开一个通道self.ssh_channel = transport.open_session()# 获取一个终端self.ssh_channel.get_pty(term=term, width=pty_width, height=pty_height)# 激活终端,这样就可以登录到终端了,就和我们用类似于xshell登录系统一样self.ssh_channel.invoke_shell()# 获取ssh连接主机后的返回内容,例如Linux,会显示上次登录等信息,把这些信息通过WebSocket显示到Web终端。# 连接建立一次,之后交互数据不会再进入该方法for i in range(2):recv = self.ssh_channel.recv(1024).decode('utf-8')message = json.dumps({'flag': 'msg', 'message': recv})# pri00nt('【WS  --websocket-->  Web】建立SSH通道后,返回欢迎信息:', message)self.websocket.send_message_or_team(message)def close(self):message = {'flag': 0, 'message': '关闭WebSocket和SSH连接'}# 向WebSocket发送一个关闭消息self.websocket.send_message_or_team(json.dumps(message))try:# 关闭ssh通道self.ssh_channel.close()# 关闭WebSocket连接self.websocket.close()except BaseException as e:# pri00nt('关闭WebSocket和SSH连接产生异常:', e)passdef _ws_to_ssh(self, data):"""尝试发送数据到ssh通道,产生异常则关闭所有连接"""try:# pri00nt('【Func  --paramiko-->  SSH】WebSocket中的数据发送数据到ssh通道:', data)self.ssh_channel.send(data)except OSError as e:# pri00nt(e)self.close()def _ssh_to_ws(self):try:# while True:while not self.ssh_channel.exit_status_ready():data = self.ssh_channel.recv(1024).decode('utf-8')# pri00nt('【SSH  --paramiko-->  Func】获取ssh通道返回的数据:', data)if len(data) != 0:message = {'flag': 'msg', 'message': data}# pri00nt('【WS --websocket-->  Web】通过WebSocket把信息发回前端,显示到Web终端:', message)self.websocket.send_message_or_team(json.dumps(message))else:breakexcept:self.close()def shell(self, data):Thread(target=self._ws_to_ssh, args=(data,)).start()Thread(target=self._ssh_to_ws).start()"""t1 = Thread(target=self._ws_to_ssh, args=(data,))t1.setDaemon(True)t1.start()t2 = Thread(target=self._ssh_to_ws)t2.setDaemon(True)t2.start()"""def resize_pty(self, cols, rows):self.ssh_channel.resize_pty(width=cols, height=rows)class WebsshConsumer(WebsocketConsumer):"""1、xterm.js 在浏览器端模拟 shell 终端, 监听用户输入通过 websocket 将用户输入的内容上传到 django2、django 接受到用户上传的内容, 将用户在前端页面输入的内容通过 paramiko 建立的 ssh 通道上传到远程服务器执行3、paramiko 将远程服务器的处理结果返回给 django4、django 将 paramiko 返回的结果通过 websocket 返回给用户5、xterm.js 接收 django 返回的数据并将其写入前端页面"""def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)self.host_id = ''self.simple_user = ''self.is_team = Falseself.team_name = ''def connect(self):"""建立WebSocket连接,并实例化SSHBridge类,在这个对象中建立SSH连接,放在 self.ssh_channel 通道中:return:"""self.host_id = self.scope['url_route']['kwargs'].get('host_id')self.simple_user = self.scope["session"]["session_simple_nick_name"]  # 获取session中的值# pri00nt('【Web  --websocket-->  WS】建立WebSocket通道,当前连接用户:', self.simple_user)self.accept()# WebSocket连接成功后,连接sshquery_string = self.scope.get('query_string')ws_args = QueryDict(query_string=query_string, encoding='utf-8')# # pri00nt(ws_args)# <QueryDict: {'user': ['admin'], 'host': ['192.168.96.20'], 'port': ['22'], 'auth': ['pwd'], 'pwd': ['ZGphbmdvYWRtaW4='], 'key': [''], 'width': ['113'], 'height': ['43']}># 根据参数判断是否是协作team = ws_args.get('team')if team:self.is_team = Trueself.team_name = "team_{}".format(self.host_id)  # 加到这个通道组async_to_sync(self.channel_layer.group_add)(self.team_name,self.channel_name)# 用户连接时,同一群组发送消息self.send_message_or_team(json.dumps({'flag': 'user', 'message': '用户 {} 已连接本终端'.format(self.simple_user)}))width = ws_args.get('width')height = ws_args.get('height')width = int(width)height = int(height)  # ssh连接要求int类型:required argument is an integerssh_connect_dict = {}if self.host_id:# 指定连接# pri00nt('连接的服务器id:', self.host_id)if int(self.host_id) == 1:ssh_connect_dict = {'host': '192.168.96.20','user': 'user','port': 22,'timeout': 30,'pty_width': width,'pty_height': height,'pwd': 'user'}elif int(self.host_id) == 2:ssh_connect_dict = {'host': '192.168.96.21','user': 'user','port': 22,'timeout': 30,'pty_width': width,'pty_height': height,'pwd': 'user'}else:self.close()returnelse:user = ws_args.get('user')host = ws_args.get('host')port = ws_args.get('port')port = int(port)auth = ws_args.get('auth')pwd = ws_args.get('pwd')if pwd:pwd = base64.b64decode(pwd).decode('utf-8')sshkey_filename = ws_args.get('sshkey_filename')ssh_connect_dict = {'host': host,'user': user,'port': port,'timeout': 30,'pty_width': width,'pty_height': height,'pwd': pwd}if auth == 'key':sshkey_file = os.path.join(settings.MEDIA_ROOT, 'sshkey', sshkey_filename)if not os.path.exists(sshkey_file):self.send(json.dumps({'flag': 'error', 'message': '密钥文件不存在'}))else:try:f = open(sshkey_file, 'r', encoding='utf-8')key = f.read()string_io = StringIO()string_io.write(key)string_io.flush()string_io.seek(0)ssh_connect_dict['key'] = string_ioos.remove(sshkey_file)  # 用完之后删除key文件except BaseException as e:# pri00nt('打开密钥文件出错', e)pass# 建立SSH连接self.ssh = SSHBridge(websocket=self, simpleuser=self.simple_user)# pri00nt('【WS  --SSHBridge-->  SSH】连接SSH参数:', ssh_connect_dict)self.ssh.connect(**ssh_connect_dict)def disconnect(self, close_code):# 断开连接# pri00nt('用户 {} 断开WebSocket连接,断开SSH连接'.format(self.simple_user))try:if self.is_team:# 用户连接时,同一群组发送消息self.send_message_or_team(json.dumps({'flag': 'user', 'message': '用户 {} 已断开本终端'.format(self.simple_user)}))# 退出群组async_to_sync(self.channel_layer.group_discard)(self.team_name,self.channel_name)self.ssh.close()except BaseException as e:passdef receive(self, text_data=None, bytes_data=None):# 从WebSocket中接收消息text_data = json.loads(text_data)  # json字符串转字典# pri00nt('\n\n【Web  --websocket-->  WS】Web终端按键内容通过WebSocket传到后端:', text_data)if type(text_data) == dict:if text_data.get('flag') == 'entered_key':data = text_data.get('entered_key', '')  # 获取前端传过来输入的按键值,并传递给shell# pri00nt('【WS  --SSHBridge-->  Func】WebSocket转发SSHBridge:', text_data)self.ssh.shell(data=data)else:cols = text_data['cols']rows = text_data['rows']# 改变通道中终端大小self.ssh.resize_pty(cols=cols, rows=rows)else:# pri00nt('【!!!】收到的数据不是dict类型')passdef send_message_or_team(self, message):if self.is_team:async_to_sync(self.channel_layer.group_send)(self.team_name,{'type': 'team_message','message': message})else:self.send(message)def team_message(self, event):message = event['message']# 发送消息到WebSocketself.send(message)

应用下创建 routing.py

apps/webssh/routing.py

from django.conf.urls import urlfrom . import consumerswebsocket_urlpatterns = [url(r'^ws/webssh/(?P<host_id>\d+)/$', consumers.WebsshConsumer),url(r'^ws/webssh/$', consumers.WebsshConsumer),
]

修改项目下 routing.py (主WS路由)

合并多个应用的url

from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from channels.sessions import SessionMiddlewareStack
import pxectrl.routing
import webssh.routingapplication = ProtocolTypeRouter({# (http->django views is added by default)# 【channels】(第6步)添加路由配置指向应用的路由模块'websocket': SessionMiddlewareStack(  # 使用Session中间件,可以请求中session的值# 一个url# URLRouter(#     pxectrl.routing.websocket_urlpatterns,# ),# 多个url合并一起使用,多个子路由列表相加URLRouter(pxectrl.routing.websocket_urlpatterns + webssh.routing.websocket_urlpatterns,),),
})

整个流程分析

  1. 页面点击连接进入connectWebSocket(host_id = null, team = false)函数,首先获取div的大小,初始化终端大小;
  2. 检测用户输入是否完成,如果是密钥认证将先使用ajax上传文件,这儿只讲下密码验证,组装成ws://IP地址/ws/webssh/?参数1=xxx&参数2=xxx的地址,创建WebSocket连接;
  3. ws.onopen打开WebSocket连接,并隐藏输入表单,显示网页终端,此时通过apps/webssh/routing.py中的url进入WebsshConsumer(WebsocketConsumer).connect(self),接受WebSocket连接,并根据ws的参数,实例化self.ssh = SSHBridge(websocket=self, simpleuser=self.simple_user),连接到SSHself.ssh.connect(**ssh_connect_dict),这儿主要是创建一个SSH通道self.ssh_channel,并激活self.ssh_channel.invoke_shell(),类似xshell功能,后面通过该通道进行SSH请求主机;
  4. SSH通道连接成功后,从该通道中获取返回的数据,并通过WebSocket通道返回到前端,ws.onmessage接到后端返回的数据,显示到网页终端;
  5. 网页终端按键后,通过term.on('data', function (data) {...})实时ws.send(send_data)发送到后端;
  6. 后端def receive(self, text_data=None, bytes_data=None)接收数据,调用self.ssh.shell(data=data)函数,这儿创建一个正向命令和反向接收的线程,主要用于实时交互数据;
  7. 通过SSH通道self.ssh_channel.send(data)传入输入的键,反向获取通道中的内容self.ssh_channel.recv(1024).decode('utf-8'),并通过WebSocket返回前端。

遇到的问题:用协作连接时,用户加入到一个通道组,往这个通道组发送命令,这个通道所有用户都能收到,来实现协作的功能,但是从self.ssh_channel接收返回的数据,可能会存在和预想的不同,特别是top命令。示例如下,不知道怎么解决了!!!

使用截图

[外链图片转存失败(img-Z9ACxmep-1564388455352)(https://blog.starmeow.cn/media/blog/images/2019/07/BLOG_20190729_161838_38.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 “博客图集BLOG_20190729_161838_38.png”)]

参考链接:https://github.com/huyuan1999/django-webssh

https://www.cnblogs.com/52op/articles/9327733.html 【gevent库】

Django使用Channels实现WebSSH网页终端,实现SSH堡垒机雏形相关推荐

  1. Django与Channels实现WebSocket

    文章目录 WebSocket ajax轮询 long poll Websocket Channels WSGI ASGI Django中使用 信息交互的周期 前端实现WebSocket 前后端分离项目 ...

  2. Django使用Channels实现WebSocket消息通知功能

    更多内容请点击 我的博客 查看,欢迎来访. Django Channels https://channels.readthedocs.io/en/latest/installation.html Ch ...

  3. Django使用channels进行消息推送

    Django使用channels进行消息推送 说明 这个只是说了Django后端的搭建,前端我就不讲了, 可以使用这个网站进行测试 http://www.websocket-test.com/ 环境 ...

  4. 保姆级别 附带源码 Django集成channels(一)实现简单聊天功能

    目录 前言 不想看我瞎BB可以直接跳到这里 1.WebSocket 1.1 ajax轮询 1.2 long poll 1.3 Websocket 2.Channels 2.1 WSGI 2.2 ASG ...

  5. Gateone初步--django堡垒机实现

    部署 首先需要部署gateone,gateone是用tornado写的 可以直接使用docker,然后启动之后简单的配置就可以了.或者下载源码包,或者rpm包进行安装,这里就不说详细的安装过程了. 具 ...

  6. 云桌面 瘦终端_云桌面选择云终端还是瘦客户机?

    我们发现最近很多用户在部署青椒云桌面的时候,经常会出现这么一种情况,那就是不知道是该选择云终端还是瘦客户机而不断纠结的,云终端和瘦客户机到底有什么不同的呢,为什么经常会有很多用户因为不知道如何选择它们 ...

  7. mac的终端通过ssh远程连接Linux服务器

    mac的终端通过ssh远程连接Linux服务器: 切换到root:sudo -i 输入ssh root@+ip:ssh root@ip  然后显示 root@ip's password:直接在pass ...

  8. 终端便捷ssh(免密)连接

    普通ssh方法 物理机ssh连接虚拟机 没基础的可以这样连:复杂 终端快捷ssh 复制's前面的 打开终端设置 左侧添加新配置文件->添加新控制文件 然后填写第二行即命令行 ssh 虚拟机登录用 ...

  9. 云终端和瘦客户机的区别

    很多小伙伴不太清楚云终端(零客户机)和瘦客户机的区别,以及如何去搭配,今天和大家分享一下. 一 定义 瘦客户机 洋名:Thin Client 小名:瘦客户机(瘦终端.X86终端) 普遍为X86架构,有 ...

  10. 瘦客户服务器哪个系统最好,云终端和瘦客户机的区别以及优缺点分析

    原标题:云终端和瘦客户机的区别以及优缺点分析 我们发现最近很多用户在部署云桌面的时候,经常会出现这么一种情况,那就是不知道是该选择云终端还是瘦客户机而不断纠结的,云终端和瘦客户机到底有什么不同的呢,为 ...

最新文章

  1. PHP 打印调用堆栈信息
  2. 【原】iOS学习之UITabBar的隐藏
  3. cmd SETLOCAL使用介绍
  4. gitbook安装与使用之windows下搭建gitbook平台
  5. php图片抖动,css3,jquery_css3图片抖动,css3,jquery - phpStudy
  6. asp.net中有关URL的信息
  7. 三、悟透javascript中的function
  8. Web Server 和 HTTP 协议
  9. bzoj 1637: [Usaco2007 Mar]Balanced Lineup【瞎搞】
  10. sql Server获取表中今天、昨天、本周、上周、本月、上月等数据
  11. 随机过程(1.2)—— 数学期望与条件期望
  12. Deep-learning augmented RNA-seq analysis of transcript splicing | 用深度学习预测可变剪切
  13. GoogleMap获取地图中心点位置信息
  14. 深度 | 实景三维与CIM,谁才是时空数据第一底板 三维视频融合 三维投影融合 时空克隆 点卯-魔镜系列
  15. 5G手机芯片如何选择?
  16. 太秀了!用Pandas秒秒钟搞定24张Excel报表,还做了波投放分析!
  17. 苹果系统装win7教程
  18. 什么是RTK?RTK的原理是什么?可以应用在哪些领域
  19. java 过期数据_TTL过期的数据包丢失
  20. SOEM 源码解析 ecx_lookup_prev_sii

热门文章

  1. 西门子PLC常用指令
  2. 用matlab符号计算求解二元函数极值
  3. 使用记账软件 记录日常收支并将数据导出表格保存
  4. 江湖人物之滴滴打车张博
  5. Linux 常用的zip压缩和解压命令详解
  6. 手机隐藏Magisk的root痕迹,适用于含zygisk的Magisk
  7. mongodb数据的导入导出备份恢复
  8. 用Java实现 通过两个栈实现一个队列
  9. 特征筛选:WOE值与IV值
  10. uniapp移动端实现pdf预览