ssh2实现远程连接机器webShell

article/2025/11/6 17:28:59

1. nodejs使用ssh2连接shell,简单交互

示例:使用conn.shell

import { Client } from 'ssh2';const conn = new Client();
conn
.on('ready', () => {console.log('Client :: ready');conn.shell({ term: 'xterm' }, (err, stream) => {if (err) {throw err;}// stream.end('ls -l\nexit\n');// 向 shell 中写入命令// stream.write('ls -l');// 监听 shell 的输出stream.on('data', data => {console.log('STDOUT: ' + data);}).on('close', () => {console.log('Stream :: close');conn.end();}).on('exit', (code, signal) => {const msg = 'Stream :: exit :: code: ' + code + ', signal: ' + signal;console.log(msg);conn.end();});});
})
.on('error', async err => {console.log('Client conn Error:' + err.toString());conn.end();
})
.connect({host: ip, // 机器ipport: 22,username: username, // 用户名password: password, // 密码readyTimeout: 3000, // 握手超时实现限制 - 暂时设置3s
});

 2. 使用xterm设置宽高

// 初始化
conn.shell({ term: 'xterm', rows: rows, cols: cols }
// 监听窗口改变后设置宽高
conn.setWindow(rows, cols)

 3.结合egg-socket.io实现前后端交互

import { Controller } from 'egg';
import { Client } from 'ssh2';export default class InteractiveShellController extends Controller {async loginByAccount() {const { ctx } = this;const params = ctx.args[0];const { ip, eventName, defaultCommand, timeout, is_cluster, cols = 200, rows = 40 } = params;let { username, password } = params;// ctx.logger.info(//   '+++++++++++++++ loginByAccount +++++++++++',//   `eventName:${eventName}, ip:${ip}, username: ${username}, password: ${password}`// );// 获取用户密码if (!username || !password) {const errorMsg = `执行命令机器信息缺失!`;ctx.socket.emit(eventName, { message: errorMsg, type: 'error' });throw new Error(errorMsg );}ctx.socket.emit(eventName, { message: `loginByAccount: ${username}, eventName: ${eventName}` });const sshConfig = {host: ip,port: 22,username: username,password: password,readyTimeout: timeout || 3000, // 握手超时实现限制 - 暂时设置3s};const rowsAndCols = {cols,rows,};this.initInteractiveShell(sshConfig, eventName, rowsAndCols, defaultCommand);}initInteractiveShell(sshConfig, eventName: string, rowsAndCols, defaultCommand = 'ls -l\n') {const { ctx } = this;// ctx.logger.info('++++++ initInteractiveShell ++++++', sshConfig);try {const conn = new Client();let connStream;let isReady = false;conn.on('ready', () => {ctx.logger.info('Client :: ready');isReady = true;conn.shell({ term: 'xterm', rows: rowsAndCols.rows, cols: rowsAndCols.cols }, (err, stream) => {if (err) {isReady = false;throw err;}connStream = stream;// stream.end('ls -l\nexit\n');// 向 shell 中写入命令stream.write(defaultCommand);// 监听 shell 的输出stream.on('data', data => {// ctx.logger.info('STDOUT: ' + data);ctx.socket.emit(eventName, { message: this.formatMachineMessage(data, '1') });}).on('close', () => {const msg = 'Stream :: close';// ctx.logger.info(msg);isReady = false;ctx.socket.emit(eventName, { message: this.formatMachineMessage(msg, '2') });ctx.socket.disconnect();conn.end();}).on('exit', (code, signal) => {// const msg = 'Stream :: exit :: code: ' + code + ', signal: ' + signal;// ctx.logger.info(msg);isReady = false;ctx.socket.disconnect();conn.end();});});}).on('error', async err => {// ctx.logger.info('Client conn Error:' + err.toString());ctx.socket.emit(eventName, { message: this.formatMachineMessage(err.message, '2'), type: 'error' });ctx.socket.disconnect();isReady = false;conn.end();}).connect(sshConfig);// 监控前端发送的消息ctx.socket.on(eventName, d => {const { message } = d;// ctx.logger.info('++Client onMessage++', message);// ctx.logger.info(message);const formatMsg = this.formatClientMessage(message);if (!message || !formatMsg) {return;}if (message[0] === '4') {// 设置宽高// ctx.logger.info('++Set Width Height++:', formatMsg);const termObj = JSON.parse(formatMsg);this.setRowsAndCols(termObj, connStream); // 设置宽高} else if (isReady && connStream) {// ctx.logger.info('++Cliend Send++:', formatMsg);connStream.write(formatMsg);} else {const msg = '++Conn.ready++' + isReady;// ctx.logger.info(msg);ctx.socket.emit(eventName, { message: msg, type: 'error' });}});// 监控前端断开ctx.socket.on('disconnect', msg => {// ctx.logger.info('++Client disconnect++', msg);// 向 shell 中写入退出命令, 前端断开前已发送exit// if (isReady && connStream) {//   connStream.write('exit\n');// }// 断开与机器的连接conn.end();});} catch (e) {// ctx.logger.error(e);ctx.socket.emit(eventName, { message: e, type: 'error' });ctx.socket.disconnect();}}// 0,4客户端标识头formatClientMessage(message: string) {let res = '';const data = message.slice(1);switch (message[0]) {case '0':case '4':// 将 base64 转为 utf8res = Buffer.from(data, 'base64').toString('utf-8');break;}return res;}// 1-3服务端返回标识头formatMachineMessage(message: string, code: string) {return code + Buffer.from(message).toString('base64');}// 设置宽高setRowsAndCols(termObj, connStream) {const cols = termObj.Width;const rows = termObj.Height;if (connStream) {connStream.setWindow(rows, cols);}}
}

4.前端部分,使用vue2+elementUI+socket.io-client

视觉效果:

 

代码如下:

<template><div class="termWrapper"><el-tabsv-model="editableTabsValue"type="border-card"@tab-click="switchTerm"><el-tab-panev-for="(item, idx) in connectArr":label="item.name":name="idx+''":key="'container-' + idx"><!-- <el-button @click="toggleFullScreen">全屏</el-button> --><div :id="'term'+ idx" class="termMain" /></el-tab-pane></el-tabs></div>
</template>
// 初始化终端
import { Terminal } from 'xterm'
import 'xterm/css/xterm.css'
import { FitAddon } from 'xterm-addon-fit'
import io from 'socket.io-client'export default {name: 'Shell',props: {connectList: { // ip列表type: Array,default: () => []}},data() {return {editableTabsValue: '0',connectArr: [],throttleTimer: null}},watch: {connectList(v, oldVal) {this.onChangeValue(v, oldVal)}},mounted() {// 监听窗口改变window.addEventListener('resize', this.resizeScreen)},created() {this.bindBeforeUnload()},beforeDestroy() {this.closeConnectDgl()window.removeEventListener('resize', this.resizeScreen)},destroyed() {window.removeEventListener('beforeunload', this.beforeunloadFn)},methods: {onChangeValue(val, oldVal) {if (!val) returnthis.connectArr = []this.connectList.map(item => {const obj = {...item,term: '',websock: '',fitAddon: null}this.connectArr.push(obj)})this.$nextTick(() => {this.createConnect(this.connectArr[0], 0)})},bindBeforeUnload() {if (this.$route.name === '当前路由名称') {window.addEventListener('beforeunload', this.beforeunloadFn)}},// 刷新前断开socketasync beforeunloadFn() {if (this.$route.name === '当前路由名称') {this.closeConnectDgl()}},// toggleFullScreen() {//   const obj = this.connectArr[this.editableTabsValue]//   obj.term.toggleFullScreen(true)// },sizeTerminal(obj, cols, rows) {// console.log('cols:', cols, 'rows', rows)obj.term.resize(cols, rows)const ws = obj.websockif (ws && ws.io.readyState !== 'closed') {const msg = '4' + this.utf8_to_b64('{"Width":' + cols + ',"Height":' + rows + '}')ws.emit(obj.eventName, { message: msg })}},resizeScreen() {if (!this.throttleTimer) {this.throttleTimer = setTimeout(() => {this.throttleTimer = nullthis.connectArr.map(obj => {try {obj.fitAddon && obj.fitAddon.fit()// 窗口大小改变时触发xterm的resize方法,向后端发送行列数,格式由后端决定obj.term && obj.term.onResize((size) => {// console.log('size.cols', size.cols)const { cols, rows } = this.getRowsAndCols()this.sizeTerminal(obj, cols, rows)})} catch (e) {console.log('e', e.message)}})}, 1000)}},createConnect(obj, idx) {// Terminal.applyAddon(fullscreen)const { cols, rows } = this.getRowsAndCols()obj.term = new Terminal({cols,rows,cursorBlink: true, // 光标闪烁cursorStyle: 'underline', // 光标样式  null | 'block' | 'underline' | 'bar'scrollback: 1000, // 回滚tabStopWidth: 8, // 制表宽度fontSize: 14,disableStdin: false, // 是否应禁用输入theme: {background: '#000'},windowsMode: true,windowOptions: {fullscreenWin: true}})const fitAddon = new FitAddon()obj.fitAddon = fitAddonobj.term.loadAddon(fitAddon)fitAddon.fit()// 将term挂砸到dom节点上obj.term.open(document.getElementById('term' + idx))this.initSocketIo(obj, cols, rows)},initSocketIo(obj, cols, rows) {obj.websock = io('后端域名',{path: '/socket.io',transports: ['websocket'],secure: true})obj.command = []const { websock: socket, term, eventName } = objconst params = {...obj,cols,rows}delete params.websockdelete params.termdelete params.fitAddondelete params.commandsocket.on('connect', () => {console.log('connected success!')obj.connectSuccess = trueobj.aliveInter = window.setInterval(() => {socket.emit(eventName, { message: '0' })console.log('keep alive')}, 10 * 1000)term.writeln('connect success !!!')socket.emit('跟服务端约定事件名', params)this.$emit('connect', obj.name)})// 监听服务端发送消息this.onMessage(obj)// 系统事件-关闭socket.on('disconnect', msg => {console.log('#disconnect', msg)term.writeln('connect closed !!!')obj.connectSuccess = falsewindow.clearInterval(obj.aliveInter)obj.aliveInter = null})socket.on('error', (msg) => {console.log('#error', msg)// 失败重连// this.initSocketIo(obj, idx)})// 监听终端输入term.onData(data => {if (socket && socket.io.readyState !== 'closed') {const msg = '0' + this.utf8_to_b64(data)console.log(data)socket.emit(eventName, { message: msg })// term.write(data)}})// 添加事件监听器,支持输入方法term.onKey(e => {if (socket && socket.io.readyState !== 'closed') {// const printable = !e.domEvent.altKey && !e.domEvent.altGraphKey && !e.domEvent.ctrlKey && !e.domEvent.metaKeyif (e.domEvent.keyCode === 13) { // 回车this.handleCommandEnter(obj.name, [...obj.command])obj.command = []// term.prompt()} else if (e.domEvent.keyCode === 8) { // back 删除的情况obj.command.pop()//   if (term._core.buffer.x > 2) {//     term.write('\b \b')//   }// } else if (printable) {//   term.write(e.key)// }} else {obj.command.push(e.key)}}})},// 监听消息onMessage(obj) {obj.websock.on(obj.eventName, d => {const { message, type } = dif (message === 'WebSocket Client Connected') { // 跟服务端约定判断返回连接成功const { cols, rows } = this.getRowsAndCols()this.sizeTerminal(obj, cols, rows)}if (type === 'error') {console.error(message)} else {// console.log('服务端返回', message)}const data = message.slice(1)let first = true// 对返回内容做处理switch (message[0]) {case '1':case '2':case '3':obj.term.write(this.b64_to_utf8(data))if (first) {first = falseobj.term.cursorHidden = false// this.term.showCursor();if (obj.term.element) {obj.term.focus()}}breakdefault:console.log(message)break}})},// eslint-disable-next-line camelcaseutf8_to_b64(str) {return window.btoa(window.unescape(encodeURIComponent(str)))},// eslint-disable-next-line camelcaseb64_to_utf8(str) {return decodeURIComponent(window.escape(window.atob(str)))},getRowsAndCols() {const offsetWidth = document.documentElement.offsetWidth// 参数调整,兼容mac、pcconst ratio = offsetWidth > 1500 ? 7.6 : 8const adjustedValue = offsetWidth > 1500 ? 15 : 10return {cols: Math.max(Math.round((offsetWidth - adjustedValue) / ratio), 120),rows: Math.round((document.documentElement.clientHeight - 130) / 16)}},// 切换tabswitchTerm() {if (this.connectArr[this.editableTabsValue].websock) returnthis.$nextTick(() => {this.createConnect(this.connectArr[this.editableTabsValue],this.editableTabsValue)})},// 每次enter记录输入命令handleCommandEnter(name, command) {this.$emit('enter', name, command.join(''))},closeConnectDgl() {console.log('===============closeConnectDgl================')this.connectArr.map(item => {// 发送 exit 命令console.log('item.connectSuccess', item.connectSuccess)if (item.websock && item.connectSuccess) {const { websock, eventName } = item// 向服务端发送退出websock.emit(eventName, { message: '0' + this.utf8_to_b64('e') })websock.emit(eventName, { message: '0' + this.utf8_to_b64('x') })websock.emit(eventName, { message: '0' + this.utf8_to_b64('i') })websock.emit(eventName, { message: '0' + this.utf8_to_b64('t') })websock.emit(eventName, { message: '0' + this.utf8_to_b64('\n') })websock.disconnect()}// 销毁终端item.term && item.term.dispose()window.clearInterval(item.aliveInter)item.aliveInter = null})this.$emit('closeConnect', false)}}
}


http://chatgpt.dhexx.cn/article/k6hjU2kB.shtml

相关文章

Putty上传文件报Received SSH2_MSG_CHANNEL_DATA for nonexistent channel 0

生产服务器通过Putty上传jar包报错信息如下&#xff1a; 排查发现出现原因是上传目录磁盘空间满了 解决办法&#xff1a;进入磁盘空间满的目录&#xff0c;执行du -a|sort -rn|head -10&#xff0c;对磁盘空间进行清理&#xff0c;注意不要删除data目录的数据&#xff0c;尽量找…

SSH J2EE

<I>what is "SSH" 在J2EE项目中表示了3种框架,即 Spring+Struts+Hibernate。 Struts对Model,View和Controller都提供了对应的组件。 Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。它是为了解决企业应用开发的复杂性而创建的。 Hibernat…

ssh2框架整合详细版(使用较新jar包)

ssh2框架整合Maven详细配置、多种方式介绍&#xff0c;中间碰到问题与注意点&#xff0c;部分已有指出。 关于Eclipse中使用Maven详见http://blog.csdn.net/it_faquir/article/details/54562242 步骤一 导入必备junit、javax.servlet相关jar包&#xff0c;maven配置如下&…

nodejs系列-如何用JS代码连接远程服务器并执行命令创建文件-ssh2

ssh2是什么&#xff1f; SSH2 client and server模块用纯JavaScript为node.js编写。我们可以在代码里使用它链接远端服务器&#xff0c;执行一些必要的操作 为什么要使用ssh2&#xff1f; 作为一名前端&#xff0c;我们在某些情况下&#xff0c;需要使用代码去登录Linux服务器…

【SSH2框架(理论篇)】--SSH2 Vs 经典三层

&#xfeff;&#xfeff; 这几天一直在学习使用SSH2框架&#xff0c;对于框架本身的使用并不是很困难&#xff0c;相信经过多锻炼就能够熟练的掌握框架的使用&#xff0c;让我匪夷所思的是在使用框架的时候感觉很熟悉&#xff0c;好像在哪里用过似得。就在某次查看代码的时候突…

xshell7无法远程连接linux虚拟机,启动SSHD报错:Bad SSH2 cipher spec...

项目场景&#xff1a; xshell7无法远程连接linux虚拟机 问题描述 终端输入systemctl start sshd 启动报错&#xff1a; 输入sshd -t 查看具体报错信息&#xff1a; 解决方案&#xff1a; 输入ssh -Q cipher&#xff0c;查看ssh使用了哪些ciphers: 复制结果 输入vim …

2.ssh远程登录

1.ssh命令 &#xff08;1&#xff09;ssh命令的参数ssh- l 指定登陆用户 - i 指定私钥 - X 开启图形 - f 后台运行 - o 指定连接参数 - t 制定连接跳板 ssh - l root 172.25.254 .x -o "StrictHostKeyChecking no" 首次连接不需要…

SSH建立原理

一、ssh2协议 在ssh1中&#xff0c;由单个协议提供密钥交换、身份认证与加密的功能&#xff0c;而ssh2内部由3个协议组合一起&#xff0c;为其提供这些功能。这3个协议: 传输层协议 认证协议 连接协议 传输层协议 主要提供密钥交换与服务器端认证功能 认证协议…

SSH2协议加密与连接过程

最近在做ssh2联动交换机模块&#xff0c;看到libssh2-1.9.0版本对于用户认证有三种不同的方式&#xff1a; ① password&#xff08;默认&#xff09;② keyboard-interactive ③ publickey 看代码并不是太理解&#xff0c;所以查询了一番&#xff0c;SSH2通信大致分为两步&a…

来说说datatype

今天敲代码一直卡在一个问题上面好久那就是--datatype的未定义&#xff0c;起初不晓得datatype的含义&#xff0c;遇到这种情况首先想到的就是自己又忘记加上面头文件了。随即写了个stdlib.h上去。可是问题并没有得到解决&#xff0c;还是显示未定义。 怀疑是不是自己拼写的错误…

StringType报错,提示不是DataType类型

使用scala语言将RDD转化为sparkSQL时&#xff0c;构造StructType时StringType报错 val schema StructType( //构建函数&#xff0c;给每列数据一个统一的列名Seq(StructField("phoneNum",IntegerType,true),StructField("time",Integer…

mysql workbench的datatype设置问题

create table之后&#xff0c;创建column&#xff0c;选择datatype。我选择timestamp和datetime如图所示后就失败。 问题就在于要么去掉括号&#xff0c;要么括号里面填写数字。 成功后如图。 这里只要看paidTime的datatype即可。 这个问题真的花了我很久的时间&#xff0c;我…

datalist

在Web设计中,经常会用到如输入框的自动下拉提示,这将大大方便用户的输入。在以前&#xff0c;如果要实现这样的功能&#xff0c;必须要求开发者使用一些Javascript的技巧或相关的框架进行ajax调用&#xff0c;需要一定的编程工作量。但随着HTML5 的慢慢普及&#xff0c;开发者可…

TS DataType

TypeScript有13中数据类型 布尔值(Boolean) 最基本的数据类型就是简单的 true/ false值&#xff0c;在 JavaScript和 TypeScript里叫做 boolean&#xff08;其它语言中也一样&#xff09;。 let isDone: boolean false;数字(Number) 和 JavaScript一样&#xff0c; TypeSc…

Autosar DataType介绍

前言 看了一下基本的配置&#xff0c;发现Autosar的DataType的内容比较多&#xff0c;也比较复杂&#xff0c;所以单独开一章来记录一下 Autosar DataType介绍 前言一.基本数据类型1.1基本类型1.2其他类型 二&#xff0c;详细数据介绍2.1 新建数据类型的具体类型2.2 详细举例介…

contentType与dataType

$.ajax contentType 和 dataType , contentType 主要设置你发送给服务器的格式&#xff0c;dataType设置你收到服务器数据的格式。 在http 请求中&#xff0c;get 和 post 是最常用的。在 jquery 的 ajax 中&#xff0c; contentType都是默认的值&#xff1a;application/x-ww…

Types of Data

企业中的数据都如何分类&#xff1f; 粗略的分类 如果粗略点的分类话&#xff0c;可以分为两类数据&#xff1a;主数据和事务型数据。 主数据(Master Data) “Master Data is your business critical data that is stored in disparate systems spread across your Enterprise.…

Python DataType(数据类型)

简述 变量&#xff1a;指代任意一个数&#xff0c;或其他数据类型 变量名&#xff1a;大小写英文、数字和下划线&#xff08;_&#xff09;的组合&#xff0c;且不能用数字开头 Python主要的数据类型有如下&#xff1a;&#xff08;允许自定义数据类型&#xff09; 整数&…

条件随机场适用于无监督学习吗?

条件随机场适用于无监督学习吗&#xff1f; 在网上搜到的资料比较少。 HMM模型可以用EM算法来进行无监督学习。

深入理解机器学习——概率图模型(Probabilistic Graphical Model):条件随机场(Conditional Random Field,CRF)

分类目录&#xff1a;《深入理解机器学习》总目录 条件随机场&#xff08;Conditional Random Field&#xff0c;CRF&#xff09;是一种判别式无向图模型&#xff0c;在《概率图模型&#xff08;Probabilistic Graphical Model&#xff09;&#xff1a;隐马尔可夫模型&#xff…