多ubuntu主机远程桌面连接方案

article/2025/10/9 3:17:04
一、需求背景

公司有一批ubuntu的主机,需要研发远程上去进行代码调试,普通的远程桌面方式不易于管理,并且无法进行连接控制。

二、方案制定

基于web的远程方案有Guacamole、NoVNC两种方案,但都不利于后期工具与公司整体的SSO进行对接。
虽然Guacamole作为的guacad作为整个web工具的后端能够实现包括加密传输、用户认证、图像优化等能力,但适配的前端代码开发难度也相对较高。
由于需求比较急切,所以最后采用的是react-vnc + websockify代理的方式,将工具嵌入已有的运维平台中,实现对用户的管理和访问的控制。

三、react部分代码
import * as React from 'react'
import { VncScreen } from 'react-vnc'
import { Drawer, Button, Slider, InputNumber, Row, Col, message, Input } from 'antd';
import RFB from 'react-vnc/dist/types/noVNC/core/rfb';
import { FullscreenOutlined, SettingOutlined } from '@ant-design/icons';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import copy from "copy-to-clipboard"; 
import TextArea from 'antd/lib/input/TextArea';interface IProps {url: string;style?: object;className?: string;viewOnly?: boolean;focusOnClick?: boolean;clipViewport?: boolean;dragViewport?: boolean;scaleViewport?: boolean;resizeSession?: boolean;showDotCursor?: boolean;background?: string;qualityLevel?: number;compressionLevel?: number;autoConnect?: number; // defaults to trueretryDuration?: number; // in millisecondsdebug?: boolean; // show logs in the consoleloadingUI?: React.ReactNode; // custom component that is displayed when loading}interface IState {rfb: RFB | undefinedvisible: booleancompressionLevel: numberqualityLevel: numberviewOnly: booleansetting: stringsettingHeight: numberinputVisible: booleanclipboardtext?: string
}export default  class VNCClient extends React.Component<IProps, IState>{constructor(props: IProps){super(props)this.state = {rfb: undefined,visible: false,compressionLevel: 9,qualityLevel: 2,viewOnly: false,setting: 'none',settingHeight: 10,inputVisible: false}this.connectCallback = this.connectCallback.bind(this)this.showDrawer = this.showDrawer.bind(this)this.onDrawerClose = this.onDrawerClose.bind(this)this.onChangeCompressionLevel = this.onChangeCompressionLevel.bind(this)this.onChangeQualityLevel = this.onChangeQualityLevel.bind(this)this.onChangeViewOnly = this.onChangeViewOnly.bind(this)this.reViewScreen = this.reViewScreen.bind(this)this.showSetting = this.showSetting.bind(this)this.hideSetting = this.hideSetting.bind(this)this.onClipboard = this.onClipboard.bind(this)this.onPaste = this.onPaste.bind(this)this.openInput = this.openInput.bind(this)this.hideInput = this.hideInput.bind(this)this.handleHotkeysPress = this.handleHotkeysPress.bind(this)this.sendCtrl = this.sendCtrl.bind(this)this.sendShift = this.sendShift.bind(this)this.sendWin = this.sendWin.bind(this)this.sendAlt = this.sendAlt.bind(this)}//屏蔽prompt弹窗componentDidMount(){// window.prompt = (message?: string, _default?: string)=>{//     console.log('异步拷贝失败,当前页面已禁用prompt')//     if(message){//         return '异步拷贝失败'//     }else{//         return null//     }// }window.addEventListener('keydown', this.handleHotkeysPress, true)// document.getElementsByTagName('textarea')[0].addEventListener('paste', this.handlePaste)}handlePaste(event: ClipboardEvent){console.log(event)}sendCtrl(){this.state.rfb?.sendKey(0xffe3,"XK_Control_L",false)}sendShift(){this.state.rfb?.sendKey(0xffe1,"XK_Shift_L",false)}sendWin(){this.state.rfb?.sendKey(0xffeb,"XK_Super_R",false)// this.state.rfb?.sendKey(0xff52,"XK_Up",true)// this.state.rfb?.sendKey(0xff52,"XK_Up",false)//this.state.rfb?.sendKey(0xffeb,"XK_Super_R",false)}sendAlt(){this.state.rfb?.sendKey(0xffe9,"XK_Alt_L",false)}handleHotkeysPress = (event: any) => {if ((event.keyCode === 86 && event.ctrlKey) || (event.keyCode === 45 && event.shiftKey)) {console.log(event.keyCode, event.ctrlKey,event.shiftKey)try{navigator.clipboard.readText().then((v) => {this.state.rfb?.sendKey(0xffe3,"XK_Control_L",false)let pastedText = vthis.pasteSim(pastedText)}).catch((v) => {console.log("获取剪贴板失败: ", v);});}catch{alert("目前仅chrome浏览器支持剪贴板功能,但http环境剪贴板功能被禁用,请访问chrome://flags/#unsafely-treat-insecure-origin-as-secure,修改Insecure origins treated as secure为enabled,添加http://cop.cargo.intra.xiaojukeji.com,根据提示relaunch浏览器")return false}}else if ((event.keyCode === 67 && event.ctrlKey)||(event.keyCode === 88 && event.ctrlKey)||(event.keyCode === 67 && event.ctrlKey && event.shiftKey)) {//终端ctrl+shift+c  文本编辑软件ctrl+c ctrl+xif(this.state.clipboardtext){copy(this.state.clipboardtext)}     }if(event.altKey && event.keyCode===38){this.state.rfb?.sendKey(0xffe3,"XK_Alt_L",false)this.state.rfb?.sendKey(0xff52,"XK_UP",false)this.state.rfb?.sendKey(0xffeb,"XK_Super_L",true)this.state.rfb?.sendKey(0xff52,"XK_UP",true)this.state.rfb?.sendKey(0xffeb,"XK_Super_L",false)this.state.rfb?.sendKey(0xff52,"XK_UP",false)}if(event.altKey && event.keyCode===40){this.state.rfb?.sendKey(0xffe9,"XK_Alt_L",false)this.state.rfb?.sendKey(0xff54,"XK_Down",false)this.state.rfb?.sendKey(0xffeb,"XK_Super_L",true)this.state.rfb?.sendKey(0xff54,"XK_Down",true)this.state.rfb?.sendKey(0xffeb,"XK_Super_L",false)this.state.rfb?.sendKey(0xff54,"XK_Down",false)}if(event.altKey && event.keyCode===37){this.state.rfb?.sendKey(0xffe9,"XK_Alt_L",false)this.state.rfb?.sendKey(0xff51,"XK_Left",false)this.state.rfb?.sendKey(0xffeb,"XK_Super_L",true)this.state.rfb?.sendKey(0xff51,"XK_Left",true)this.state.rfb?.sendKey(0xffeb,"XK_Super_L",false)this.state.rfb?.sendKey(0xff51,"XK_Left",false)}if(event.altKey && event.keyCode===39){this.state.rfb?.sendKey(0xffe9,"XK_Alt_L",false)this.state.rfb?.sendKey(0xff53,"XK_Right",false)this.state.rfb?.sendKey(0xffeb,"XK_Super_L",true)this.state.rfb?.sendKey(0xff53,"XK_Right",true)this.state.rfb?.sendKey(0xffeb,"XK_Super_L",false)this.state.rfb?.sendKey(0xff53,"XK_Right",false)}// if(event.altKey && event.ctrlKey && event.keyCode===84){//     this.state.rfb?.sendKey(0xffe9,"XK_Alt_L",false)//     this.state.rfb?.sendKey(0xffe3,"XK_Control_L",false)//     this.state.rfb?.sendKey(0xffe3,"XK_Control_L",true)//     this.state.rfb?.sendKey(0xffe9,"XK_Alt_L",true)//     this.state.rfb?.sendKey(0x0054,"XK_T",true)//     this.state.rfb?.sendKey(0xffe9,"XK_Alt_L",false)//     this.state.rfb?.sendKey(0xffe3,"XK_Control_L",false)//     this.state.rfb?.sendKey(0x0054,"XK_T",false)// }// if (event.keyCode === 91) {//     this.state.rfb?.sendKey(0xffeb,"XK_Super_L",true)//     this.state.rfb?.sendKey(0xffeb,"XK_Super_L",false)// }};handleMousePress = (event: any) => {console.log(event)};//使用rfb内置复制接口pasteSim(text: string){this.state.rfb?.clipboardPasteFrom(text)}//vnc远程连接回调函数,返回rfb对象connectCallback(rfb: RFB|undefined){if(rfb){//rfb.qualityLevel=2this.setState({rfb:rfb})}}showDrawer(){this.setState({visible: true});}onDrawerClose() {this.setState({visible: false});}fullscreen() {var de = document.documentElement;de.requestFullscreen();}onChangeCompressionLevel(newValue: number){this.setState({compressionLevel: newValue})}onChangeQualityLevel(newValue: number){this.setState({qualityLevel: newValue})}onChangeViewOnly(e: CheckboxChangeEvent){if(this.state.viewOnly){this.setState({viewOnly: false})}else{this.setState({viewOnly: true})}}//rfb返回剪贴板内容复制到本地剪贴板onClipboard(e:any){let copy_text = e.detail.textthis.setState({clipboardtext: copy_text})}//临时实现单行文本发送到远程主机onPaste(e:any){let dom = document.getElementsByTagName('textarea')var text = dom[0].valuefor(let char of text){if(char=='\n'){this.state.rfb?.sendKey(0xff8d,"Enter",true)this.state.rfb?.sendKey(0xff8d,"Enter",false)this.state.rfb?.sendKey(0xff50,"XK_Home",true)this.state.rfb?.sendKey(0xff50,"XK_Home",false)continue}this.state.rfb?.sendKey(char.charCodeAt(0),char,true)this.state.rfb?.sendKey(char.charCodeAt(0),char,false); }// this.state.rfb?.sendKey(0xff8d,"Enter",true)// this.state.rfb?.sendKey(0xff8d,"Enter",false)message.info("传输"+text.length+"个字符");}reViewScreen(){if(this.state.rfb){this.state.rfb.compressionLevel = this.state.compressionLevelthis.state.rfb.qualityLevel = this.state.qualityLevelthis.state.rfb.viewOnly = this.state.viewOnlyif(this.state.viewOnly){message.info('设置已修改');message.warning('进入观察者模式');}else{message.info('设置已修改');}}}showSetting(){this.setState({setting: "",settingHeight: 38})}hideSetting(){this.setState({setting: "none",settingHeight: 10})}openInput(){this.setState({inputVisible: true})}hideInput(){this.setState({inputVisible: false})}render(){return (<div style={{background:"#000000"}} ><div style={this.state.inputVisible===false?{display:'none'}:{position: 'absolute', right:'95px', textAlign:'right', bottom: '5px',width: '90%',}}><TextAreaplaceholder="不支持中文和字符转译"  style={{width:'40%',//backgroundColor: 'rgba(0,0,0,0)',color: '#ffffff'}}allowClear//onPressEnter={this.onPaste}></TextArea>;{/* <Button size='small' style={{backgroundColor: 'rgba(0,0,0,0)', color:'#ffffff'}}>历史</Button> */}</div><textarea style={{display:'none'}}></textarea><div style={{position: 'absolute', right: 0, bottom: '9px'}}><Button onClick={this.onPaste} size='small' style={this.state.inputVisible===false?{display:'none'}:{backgroundColor: 'rgba(0,0,0,0)', color:'#ffffff'}}>发送</Button><Button onClick={this.openInput} size='small' style={this.state.inputVisible===true?{display:'none'}:{backgroundColor: 'rgba(0,0,0,0)', color:'#ffffff'}}>文本输入框</Button><Button onClick={this.hideInput} size='small' style={this.state.inputVisible===false?{display:'none'}:{backgroundColor: 'rgba(0,0,0,0)', color:'#ffffff'}}>隐藏</Button></div><div style={{ position: "absolute", top: "0",left: "48%",width: 100,height: this.state.settingHeight,textAlign:"center",background: "#ffffff",borderRadius: 10}}onMouseEnter={this.showSetting}onMouseLeave={this.hideSetting}><Row style={{ margin: "7px",display: this.state.setting  }}><Col span={12}><Button type='default' onClick={this.fullscreen}  size="small"><FullscreenOutlined /></Button></Col><Col span={12}><Button type='default' onClick={this.showDrawer} size="small">< SettingOutlined/></Button></Col></Row></div><Drawer title="设置" placement="left" width="300" onClose={this.onDrawerClose} visible={this.state.visible}>compressionLevel:<Row><Col span={12}><Slidermin={0}max={9}onChange={this.onChangeCompressionLevel}value={typeof this.state.compressionLevel === 'number' ? this.state.compressionLevel : 0}/></Col><Col span={12}><InputNumbermin={0}max={9}style={{ margin: '0 16px' }}value={this.state.compressionLevel}onChange={this.onChangeCompressionLevel}/></Col> </Row>qualityLevel:<Row><Col span={12}><Slidermin={0}max={9}onChange={this.onChangeQualityLevel}value={typeof this.state.qualityLevel === 'number' ? this.state.qualityLevel : 0}/></Col><Col span={12}><InputNumbermin={0}max={9}style={{ margin: '0 16px' }}value={this.state.qualityLevel}onChange={this.onChangeQualityLevel}/></Col> </Row>viewOnly: <Checkbox onChange={this.onChangeViewOnly} checked={this.state.viewOnly}></Checkbox><Row style={{ margin: 80}}><Button onClick={this.reViewScreen}>修改</Button></Row></Drawer><VncScreenurl={ this.props.url }compressionLevel={9}qualityLevel={2}focusOnClick={true}scaleViewportshowDotCursor={true}viewOnly={this.state.viewOnly}background="#000000"style={{width: '100vw',height: '100vh',cursor: 'pointer'}}onClipboard={this.onClipboard}onConnect={this.connectCallback}/>{/* <div style={{ position:"absolute", top:0, width:'45px', background:'#ffffff' }}><Button onClick={this.sendCtrl} size='small' >ctrl</Button><Button onClick={this.sendShift} size='small' >shift</Button><Button onClick={this.sendWin} size='small' >win</Button><Button onClick={this.sendAlt} size='small' >alt</Button></div> */}</div>)}
}
四、VNC代理

由于前端代码需要后端提供websocket的连接,所以使用websockify这个代理软件,将远程主机的VNC端口代理为websocket端口。

在启动websockify的过程中,需要指定一个文件,对自动创建的连接进行管理,通过这种方式实现的对远程主机的访问控制。

websockify --target-config=./vnc_token 8080 --log-file=/tmp/websocket.log -D
五、nginx配置

由于前端服务和websocket服务是相互独立的,这就需要使用nginx进行统一的代理,以便用户使用统一的地址进行访问。

六、后端控制逻辑

后端采用的是tornado进行的开发,其中实现了两部分内容
1、 前端主机信息展示数据接口
2、vnc页面请求访问websocket的预连接请求接口
数据展示部分代码不做过多说明。

vnc页面请求访问websocket的预连接请求接口
1、用户向后端请求要访问的主机A
2、后端接收到请求,获取主机A的vnc服务连接信息
3、后端将主机A的vnc服务连接信息写入到websockify所需配置文件中
4、后端根据主机A的vnc服务连接信息生成token返回给前端vnc页面
5、前端使用token进行websocket的访问,实现远程连接的功能
所有提供VNC服务的主机都可被远程访问,如下图,windows电脑使用UltraVNC Server实现VNC服务
在这里插入图片描述


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

相关文章

Ubuntu Server 18.04安装远程桌面并连接

文章目录 一、安装桌面环境Xfce二、安装 Xrdp三、设置root密码四、连接Xrdp服务器五、设置终端 尝试了很多种方法&#xff0c;折腾了一晚上终于搞出来了呜呜…顺便记录一下,以免下次忘记! 一、安装桌面环境Xfce Ubuntu 服务器通常使用命令行进行管理&#xff0c;并且默认没有安…

Windows10远程桌面Ubuntu16.04

自己的笔记本配置太低&#xff0c;有很多图形界面的软件&#xff0c;需要在服务器上运行&#xff0c;通常只用SSH方式访问的命令行方式是无法实现的。 虽然配置XShell XManager可以实现打开图形程序&#xff0c;但速度之慢&#xff0c;即使内网也无法忍受。 今天来推荐一个更…

Ubuntu 安装远程桌面

转自&#xff1a;https://blog.csdn.net/heyangyi_19940703/article/details/77994416 1.安装xrdp软件: 运行Terminal,执行以下命令&#xff1a; sudo apt-get -y install xfce4 xrdp vnc4server 2.安装完成&#xff0c;查看下相关软件包 执行命令&#xff1a; dpkg -L xrdp…

Ubuntu 系统下如何远程访问 Windows 桌面 ?

你一定听说过 Windows 应用程序远程桌面连接。该应用程序系统自带不用安装&#xff0c;并允许您远程访问另一台 PC 或服务器。它使用远程桌面协议建立远程桌面连接会话。 一些 Linux 发行版可能会提供 RDP 客户端来连接到 Windows 系统。但是&#xff0c;对于某些 linux 发行版…

ubuntu20.04远程桌面这样装

我的记录本 安装了新系统之后&#xff0c;直接就进行了远程桌面的测试1.系统设置2.安装xrdp3.安装dconf-editor并设置4.切换windows系统打开远程桌面 安装了新系统之后&#xff0c;直接就进行了远程桌面的测试 更新源都没有动&#xff0c;直接进行远程桌面的测试。 参考文献&a…

Windows 远程桌面 Ubuntu

参考 Windows远程桌面工具连接Ubuntu系统使用总结_CHH3213的博客-CSDN博客_远程连接ubuntu 开启ssh服务&#xff08;非必须 查看ssh是否已经开启 sudo ps -e | grep ssh 如果最后返回是sshd&#xff0c;证明ssh已经开启&#xff0c;跳到第四步 第二步&#xff0c;如果没有…

ubuntu使用VNC实现远程桌面

2022.11更新 目前我已经放弃VNC了&#xff0c;虽然局域网连着画面还行&#xff0c;但是太容易出bug了。 现在更推荐向日葵或Todesk这些远程控制软件&#xff0c;虽然画面糊一点&#xff0c;但是稳定&#xff0c;而且不受局域网限制。 前言 我是在树莓派4B上安装的Ubuntu20.10&…

Windows10远程登陆Ubuntu桌面

简介 我们在使用Ubuntu系统有时可能需要看Ubuntu上的仿真界面和可视化数据&#xff0c;可能会有这样一个需求&#xff1a;使用Windows系统接入Ubuntu&#xff0c;本文章提供一种方法&#xff0c;使用Windows自带的远程桌面Ubuntu安装VNC解决问题。 Ubuntu上的配置 1.下载配置…

window10远程桌面控制Ubuntu系统

Windows操作系统作为全球使用最多的个人操作系统&#xff0c;在我们身边随处可见&#xff0c;但放眼各类电子设备的操作系统&#xff0c;windows并不是一家独大&#xff0c;服务器系统大多基于Linux系统开发、手机操作系统几乎都是安卓、更不用说还有苹果的iOS、树莓派、Ubuntu…

Ubuntu Desktop 启用远程桌面(Vino和TigerVNC方式)

文章目录 前言使用Vino方式无显示器使用使用TigerVNC方式 前言 在很多领域的生产开发工作中常常需要用到 Ubuntu Desktop 系统&#xff0c;但是在一些日常的工作交流中又离不开Windows系统&#xff0c;这种时候比较常用的解决方案就是在Windows系统上使用虚拟机安装Ubuntu。不…

Ubuntu 22.04 远程桌面

参考&#xff1a;1、Ubuntu 22.04 Finally Supports Remote Desktop Control via MS RDP Protocol | UbuntuHandbook 2、22.04 - Remote Desktop Sharing authentication password changes every reboot - Ask Ubuntu 一、无法连接 有可能是没登录到 gnome 桌面。因为 gnome r…

Ubuntu远程桌面连接

0.相关准备&#xff1a; 开启屏幕共享功能的NX、显卡诱骗器、远程连接电脑&#xff08;Ubuntu&#xff09; 屏幕共享开启如下&#xff1a; 在设置的sharing中打开Remote Login&#xff0c;选中Screen Sharing 选中对应的选项&#xff0c;如图所示&#xff0c;完成在对应网络…

Ubuntu开启图形化远程桌面

1、先更新一下软件列表。 sudo apt-get update sudo apt-get upgrade 2、有些闪退情况就是因为缺少安装gnome这一步&#xff0c;用以下命令安装一下。 sudo apt install gnome-session gdm3 3、 在右上角点击设置。 4、选择“共享”标签页&#xff0c;点击右上角的滑块开启共…

ubuntu远程桌面

1.xp下默认的远程桌面协议是rdp&#xff0c;默认端口3389&#xff0c;而ubuntu用的时vnc&#xff1a;默认端口5900 2.首先被访问的主机&#xff08;windows/linux&#xff09;都要设置为允许其他主机远程访问该主机。 ubuntu下只要设置 System->Preferences->Remote Desk…

烂泥:学习ubuntu远程桌面(一):配置远程桌面

本文由秀依林枫提供友情赞助&#xff0c;首发于烂泥行天下 公司服务器目前安装的都是ubuntu 14.04系统&#xff0c;而且由于业务需要&#xff0c;需要使用到ubuntu的远程桌面功能。所以本篇文章都是围绕ubuntu的远程桌面来介绍。 一、远程桌面连接方式 ubuntu的远程桌面连接要说…

ubuntu开启远程桌面功能

本文介绍ubuntu自带的xrdp工具进行远程桌面登陆。 第一步&#xff1a;安装vnc服务 sudo apt-get install tightvncserver第二步&#xff1a;安装xrdp服务 sudo apt-get install xrdpPS&#xff1a;VNC与xrdp服务安装顺序不可以颠倒&#xff0c;否则可能在登陆的时候报错Error…

信号处理之freqz函数

freqz概念及函数说明 freqz函数可分析离散系统的频率相应&#xff0c;主要的应用语法如下所示 yfreqz&#xff08;b,a,w&#xff09;;其中b为分子系数向量&#xff0c;a为分母系数向量&#xff0c;w表示需要计算的抽样频率点向量。&#xff08;至少俩点&#xff09; 下面我以…

freqz之C实现例程

参考文章 绘制数字滤波器的频域响应gnuplot Octave pkg load signalb[1.009874 -1.973835 0.980993]; a[1.000000 -1.973835 0.990867];Fs 48000 for k1:Fsw(k)2*pi*(k-1)/(Fs - 1);zexp(j*w(k));Z[1 z^-1 z^-2];H_z(k)sum(b.*Z)./sum(a.*Z); end% Transfer from Rad to Hz…

数字信号处理FIR滤波器实验

目录 一、题目介绍以及函数准备 二、程序源代码以及图像 三、思考 为何线性相位是一个来回往返的折线形&#xff1f; 一、题目介绍以及函数准备 要求设计一个线性相位FIR数字低通滤波器来对模拟信号进行滤波&#xff0c; 技术要求为fp4kHz&#xff0c;fst4.5kHz&#xff0c…

绘制系统响应函数的频率响应曲线

在z变换中&#xff0c;对于系统响应函数H(z) 绘制频率响应曲线 注意点&#xff1a; 绘制零极点图的函数 zplane(B,A);%%B为分母的系数矩阵 A为分子的系数矩阵 第一个数为z^0 第二个数为z^-1的系数 以此类推 freqz(B,A) %%计算频率响应H(e^jw) angle(H) %% 绘制相频响应 …