前言
最近不是在想方案,就是在方案的预研上,头发越来越少啊。
今天要跟大家分享的是坐席远程帮办,名字定义很高大,技术落地实际上就是远程桌面+语音指导。
技术选型
我们团队很健全,各语种的研发都有,但是就是兄弟们现在都项目缠身。
我作为技术负责人,肯定是要自己先有方向,至少要能够在各种需求场景抛砖引玉吧。所以在远程桌面这块先行预研,先给c语种方向的兄弟整个可行性高点的方案:
1、https://github.com/xunki/remotedesktopmanage
2、java方向我自己来
远程桌面协议分析
其实在选型时也是各方考量,首先是要不能用人民币,用人民币要考虑投入与回报是否对等。然后就是要考量我们现在的人力储备、技术底蕴等等。
在这过程中,首先是考量资源、带宽,然后就是针对vnc与rdp协议的抉择了。
按说vnc协议旧,那肯定就是资源多、可参考的资料就相对多,但是对比优缺点,实在是不想到时候因为效果、延时等影响形象,免得到时候吃亏了还不讨好(按说不应该太计较个人得失,可是现实很残酷啊)。
废话不说了,先看对比协议:
原理与协议
| 特性 | vnc(virtual network computing) | rdp(remote desktop protocol) |
|---|---|---|
| 协议类型 | 基于 rfb(remote frame buffer)协议,传输的是屏幕像素信息 | 微软专有协议,传输 图形对象、指令、音视频、剪贴板 等 |
| 工作方式 | 服务器端抓取屏幕像素并发送给客户端 | 客户端发送操作命令,服务器端渲染界面并发送指令给客户端,客户端再绘制界面 |
| 平台支持 | 跨平台,windows / linux / macos / 嵌入式 | 主要是 windows,但有 linux/macos 客户端支持 |
性能与体验
| 特性 | vnc | rdp |
|---|---|---|
| 带宽占用 | 较高,因为传输的是像素 | 较低,因为只传输界面指令而非像素 |
| 响应速度 | 较慢,高分辨率时延迟明显 | 快,延迟低,尤其在低带宽下表现更好 |
| 图像质量 | 像素传输,容易有模糊或延迟 | 高质量,支持压缩和多种图形加速 |
| 音频/多媒体 | 一般不传输音频,需要额外设置 | 原生支持音频重定向、多媒体优化 |
功能与安全
| 特性 | vnc | rdp |
|---|---|---|
| 文件传输 | 一般需要额外工具 | 原生支持文件共享 |
| 剪贴板共享 | 支持,但实现依赖客户端 | 原生支持 |
| 多会话 | 通常一个显示器对应一个会话(linux 可以多个会话) | windows 支持多用户多会话 |
| 安全性 | 默认明文传输,需要加密(ssh/vpn) | 支持 tls 加密、网络级认证(nla) |
使用场景
| 场景 | 适合vnc | 适合rdp |
|---|---|---|
| 跨平台远程访问 | 是 | 非 windows 客户端有限 |
| 高性能远程办公 | 否 | 是 |
| 服务器管理 | linux 常用 | windows server |
| 低带宽环境 | 否 | rdp 优化带宽 |
小结
vnc
- 优点:跨平台、轻量、简单
- 缺点:性能低、延迟大、默认不安全
- 适合远程监控、嵌入式系统、跨平台访问
rdp
- 优点:低延迟、高性能、功能丰富(音频、剪贴板、文件)
- 缺点:跨平台受限,windows 外的体验不如原生
- 适合 windows 远程办公、远程服务器管理
架构选型
经过多方了解,既然vnc放弃,那java基于vnc的ultravnc就不看了。
rdp了解到的就是guacamole,而且活力还很大哦,最近才升级。
https://guacamole.apache.org/releases/1.6.0/

具体部署细节,看官网就行,建议用docker,部署guacamole-server/guacamole-client。
如果不用docker,用tomcat部署client,那就有点折腾,其实,也就是tomcat放war包的地方要放扩展包,也就是处理授权的,官网提供了对应jar,放在extensions目录下。
要理解的是
- guacamole-server(guacd)提供服务
- guacamole-client(restapi客户端)对外暴露接口
整体逻辑
浏览器 (html5/js)
│ websocket ▼
java web 应用 ── rest api ──► guacamole (guacd)
│
▼
windows rdp server
我们java能做的就是在java web应用层做认证、连接管理等等。
再来看看springboot的集成吧。
就是一个普通的springboot服务,关于guacamole不需要额外的依赖,要说用到的,那就是hutool的httputil请求工具类。其实还有一种集成方案,那个就需要相关依赖,但是那个要自己造的轮子也多,不适合快速落地。
rdpcontroller
/**
* rdp管理
*
* @author zwmac
*/
@slf4j
@restcontroller
@requestmapping("/rdp")
public class rdpcontroller {
@resource
private guacamolerestservice guacamolerestservice;
/**
* guacamole登录
*
* @param rdpinfovo 登录信息
* @return 登录结果
*/
@getmapping("/login")
public restresponse<?> login(@requestbody rdpinfovo rdpinfovo) {
return guacamolerestservice.login(rdpinfovo.getusername(), rdpinfovo.getpassword());
}
/**
* 列出所有连接
*
* @param token 登录token
* @return 连接列表
*/
@getmapping("/listconnections")
public restresponse<?> listconnections(@requestparam string token) {
return guacamolerestservice.listconnections(token);
}
/**
* 添加连接
*
* @param rdpconnectioninfovo 连接信息
* @return 添加结果
*/
@postmapping("/addconnection")
public restresponse<?> addconnection(@requestbody rdpconnectioninfovo rdpconnectioninfovo) {
return guacamolerestservice.addconnection(rdpconnectioninfovo);
}
}
guacamolerestservice
/**
* @author zwmac
*/
public interface guacamolerestservice {
/**
* guacamole登录
*
* @param username 用户名
* @param password 密码
* @return 登录结果
*/
restresponse<?> login(string username, string password);
/**
* 列出所有连接
*
* @param token
* @return
*/
restresponse<?> listconnections(string token);
/**
* 添加连接
*
* @param rdpconnectioninfovo 连接信息
* @return 添加结果
*/
restresponse<?> addconnection(rdpconnectioninfovo rdpconnectioninfovo);
}
guacamolerestserviceimpl
/**
* @author zwmac
*/
@service
public class guacamolerestserviceimpl implements guacamolerestservice {
@value("${guacamole.base-url}")
private string guacamolebaseurl;
@value("${guacamole.datasource}")
private string datasource;
@value("${guacamole.admin-user}")
private string adminusername;
@value("${guacamole.admin-password}")
private string adminpassword;
@autowired
private resttemplate resttemplate;
@override
public restresponse<?> login(string username, string password) {
if (stringutils.isblank(username) || stringutils.isblank(password)) {
username = adminusername;
password = adminpassword;
}
assert.istrue(!stringutils.isanyblank(username, password), "用户名或密码不能为空");
map<string, object> formmap = new hashmap<>();
formmap.put("username", username);
formmap.put("password", password);
httprequest post = httputil.createpost(guacamolebaseurl + "/tokens");
post.form(formmap);
post.header("content-type", "application/x-www-form-urlencoded;charset=utf-8");
httpresponse execute = post.execute();
if (execute.isok()) {
string executebody = execute.body();
return restresponse.success(executebody);
}
return restresponse.fail("登录失败:" + execute.body());
}
@override
public restresponse<?> listconnections(string token) {
assert.hastext(token, "token不能为空");
string geturl = guacamolebaseurl + "/session/data/" + datasource + "/connections?token=" + token;
httprequest get = httputil.createget(geturl);
httpresponse execute = get.execute();
if (execute.isok()) {
string executebody = execute.body();
return restresponse.success(executebody);
}
return restresponse.fail("获取连接列表失败:" + execute.body());
}
@override
public restresponse<?> addconnection(rdpconnectioninfovo rdpconnectioninfovo) {
//参数校验
assert.notnull(rdpconnectioninfovo, "参数不能为空");
return null;
}
}
配置项
guacamole: base-url: http://你的guacamole部署ip:端口/guacamole/api datasource: mysql # 对应 guacamole.properties 里的配置 admin-user: guacadmin admin-password: guacadmin
最后,看看效果:
apifox接口:

guacamole服务端管理界面:


远程效果:

总结
好了,就写到这里,希望能帮到大家。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
其实还是那句话,关键的关键是要有思路,思路很重要!!!
发表评论