简介:KasmVNC是一款基于Web的现代VNC服务器与客户端,面向需要在Linux主机上提供安全远程桌面访问的开发者和运维人员。它由TigerVNC分叉而来,内置Web服务器与WebSocket支持,默认启用HTTPS和HTTP基本身份认证,规避了传统VNC密码仅支持8字符的安全短板。压缩包为zip格式,共716个文件,约1.19MB;其中222个.h头文件、150个.cxx及24个.c源文件构成核心代码,95个patch与7个diff记录补丁改动,16个build、8个in、7个cmake等配置用于编译打包,22个txt说明文档与14个sh脚本方便部署调试,多份Dockerfile还覆盖Ubuntu、CentOS、Kali等主流发行版。目前已有3327人学习下载。阅读源码与构建脚本,可以深入理解Web化VNC的实现原理、服务端与客户端的交互机制,以及不同Linux系统下的定制打包方式;从WebSocket通信、HTTP认证到VNC协议兼容,均有对应代码与补丁可对照研究,适合具备C++基础并关注远程桌面安全的开发者作为参考。
1. KasmVNC 是什么:把 VNC 塞进浏览器,顺便解决暴露面问题
你想远程操作一台 Linux 服务器的图形界面,最常见的理由无非是安装软件、配置桌面、做演示,又或者排查某个只在 GUI 环境复现的问题。传统 VNC 在这一场景下暴露出来的短板很明显:服务端需要监听 RFB 端口,客户端必须安装专用软件,再加上公网访问时的端口暴露和明文传输,安全隐患很大。KasmVNC 正是围绕这三个痛点出现的现代方案:服务端核心用 C++ 重写,客户端直接跑在浏览器里,二者通过 WebSocket 通道通信,默认带 TLS 加密。它适合两类人:一类是需要在服务器上临时开图形桌面的开发者,另一类是想把远程桌面能力嵌入自己 Web 系统的团队。
2. KasmVNC 的架构核心:C++ 服务端、WebSocket 通道与浏览器端协议栈
2.1 KasmVNC 改了什么:RFB 协议入口换成了 WebSocket
传统 VNC 的通信链路是服务端监听 5900 端口,客户端直接发起 RFB 协议连接。这个模型在局域网里非常稳定,但放到公网环境下就难受了:首先,5900 这类高位端口直接在公网上暴露,很容易成为扫描工具的目标;更麻烦的是,想要安全连接还需要再套一层 TLS,而 TLS 证书的管理对很多运维同事来说本身就是额外负担。
KasmVNC 最有价值的改动,是把协议的入口从 RFB 换成了 WebSocket。浏览器通过 WebSocket 建立连接,连接建立之后,才在 WebSocket 通道内部完成 RFB 协议的握手和桌面数据传输。WebSocket 的底层由 HTTP 升级而来,所以认证、TLS、负载均衡这些 Web 领域的成熟基础设施可以全部复用。对于使用者来说,这意味着你不再需要专门安装 VNC 客户端,打开浏览器就能连接。
服务端这一侧,KasmVNC 用 C++ 实现了核心模块,包括帧缓冲捕获、图像编码、会话管理。C++ 在这里的价值主要体现在两个方面:一是内存布局的可控性,帧缓冲这种高频读写的数据结构需要精确管理内存生命周期,避免长时间运行中出现碎片化;二是可以直接调用底层编码库,在图像压缩路径上采用现代算法,相比传统 VNC 使用的 Raw 和 Hextile 编码,带宽占用明显更低。
2.2 一条远程桌面连接的生命周期
拆开看,一次成功连接的过程可以分为四个阶段。
第一阶段是 HTTPS 握手。浏览器访问 KasmVNC 暴露的 HTTPS 端口,服务端返回证书,浏览器验签。默认证书是自签的,浏览器会警告,但这个警告可以忽略,因为后续的数据传输仍然会被 TLS 保护。
第二阶段是 WebSocket 建立。页面加载完,前端脚本会发起一个 WSS 请求到同源端口。这一阶段容易出现的问题是网络策略只放行了 HTTPS 端口但没有放行 WebSocket 使用的端口,导致连接一直在转圈。KasmVNC 的 WebSocket 端口和 HTTPS 端口共用同一个 TCP 端口,所以只要页面能打开,WebSocket 通常也能建立。
第三阶段是认证校验。KasmVNC 支持 VNC 密码和 Token 两种方式。VNC 密码是长期凭证,Token 则可以设置有效期临时生成。认证通过后,服务端才允许客户端进入 RFB 握手。
第四阶段是桌面数据传输。服务端通过 X11 或 Xvfb 捕获帧缓冲,编码后通过 WebSocket 通道不断推送给浏览器。浏览器端将收到的图像解压并渲染到 Canvas 上,同时把鼠标键盘事件反向通过 WebSocket 送回服务端。这里有个值得注意的点:KasmVNC 默认支持自适应质量,网络状况不好的时候会自动降低画质来保证操作响应速度,所以你会感觉连接虽然画质模糊,但操作延迟没有想象中那么高。
2.3 与 X11vnc、TigerVNC 的对比
| 维度 | X11vnc | TigerVNC | KasmVNC |
|---|---|---|---|
| 协议入口 | RFB | RFB | WebSocket + RFB |
| 浏览器访问 | 需要额外网关或客户端 | 需要额外网关或客户端 | 自带 Web 页面 |
| 默认加密 | 无 | 可选 | TLS 默认开启 |
| 图像编码 | Raw、Tight 等 | Tight、ZRLE 等 | TurboJPEG 等现代编码 |
| 配置方式 | 命令行参数 | 命令行参数 | YAML + 命令行 |
| 部署形态 | 单独进程 | 单独进程 | 可单独部署,也可容器化 |
这张表做完,选型思路就清楚了:如果你只是在内网用 VNC Viewer 连一台服务器,X11vnc 依然能胜任;如果要求浏览器访问、公网传输、统一配置,那么 KasmVNC 的 WebSocket 架构能省掉中间再加一层 Web 网关的麻烦。这不是谁完全替代谁的问题,而是场景不同,技术选型自然不同。
3. 把 KasmVNC 跑起来:Docker 部署与容器参数逐项拆解
3.1 拉取镜像并启动第一个会话
KasmVNC 最常见的落地路径是 Docker 容器化,因为桌面环境、图形库、中文字体这些依赖打包在镜像里,部署成本最低。先拉取官方镜像并启动一个基础会话:
docker pull kasmweb/ubuntu-focal-desktop:latest docker run -d \ --name kasmvnc-demo \ -p 6901:6901 \ -e VNC_PW=your-password \ kasmweb/ubuntu-focal-desktop:latest启动命令需要拆开理解。-p 6901:6901把容器内的 WebSocket 端口映射到宿主机,浏览器通过这个端口访问远程桌面页面。VNC_PW设置的是登录密码,KasmVNC 会把密码写入容器内对应位置的认证配置里,页面打开后输入它才能进入桌面。
启动完成后,你可以直接打开浏览器访问https://宿主机IP:6901,看到自签证书警告后选择继续,输入密码就能进入桌面。第一次进入桌面环境时加载会比较慢,这是正常的,因为容器内部在初始化 X 会话和桌面组件。如果打开页面后一直转圈,优先检查 6901 端口是否在宿主机防火墙和云平台安全组里放行。
3.2 环境变量的门道:分辨率、密码、Locale 与颜色深度
生产环境里用到的环境变量比示例里的要多。我一般会把下面这几个一起设置,避免进入桌面后出现分辨率不合适或者显示效果异常的问题。
docker run -d \ --name kasmvnc-config \ -p 6902:6901 \ -e VNC_PW=office-password \ -e VNC_RESOLUTION=1920x1080 \ -e VNC_COL_DEPTH=24 \ -e LANG=zh_CN.UTF-8 \ kasmweb/ubuntu-focal-desktop:latestVNC_RESOLUTION决定了 KasmVNC 内部帧缓冲的尺寸。浏览器端是可以缩放画面的,但帧缓冲分辨率决定的是编码后的原始图像精度。设置过高会浪费带宽,设置过低则文字边缘容易发虚。VNC_COL_DEPTH是颜色深度,一般设置为 24,如果目标机器性能较差,可以降到 16,代价是颜色渐变会出现肉眼可见的断层。
LANG环境变量设置语言区域,用来避免远程桌面里中文显示为方块或者程序报错。注意一点:容器内的桌面分辨率是写死在该会话的 Xorg 配置里的,改了环境变量之后需要重建容器才会生效,不是简单重启容器就能解决的,因为镜像的启动脚本只在首次启动时根据环境变量生成配置。
3.3 多会话、持久化与容器生命周期管理
一台宿主机上可能需要同时运行多个独立桌面,最直接的办法是起多个容器,每个容器映射不同的宿主机端口。
docker run -d \ --name kasmvnc-office \ -p 6903:6901 \ -e VNC_PW=office-password \ -v kasmvnc-office-data:/home/kasm-user \ kasmweb/ubuntu-focal-desktop:latest-v kasmvnc-office-data:/home/kasm-user把容器内家目录挂载到命名卷里,这样容器删了重来,桌面里的文件和数据还在。远程桌面里产生的数据如果不做持久化,容器一删除就全没了,这是很多第一次使用者容易忽略的问题。
多容器场景下,端口冲突是最常见的翻车点。KasmVNC 容器内部固定使用 6901 作为 WebSocket 端口,因此每个容器都要显式映射到不同的宿主机端口,比如第一个映射 6901,第二个映射 6902。如果图省事使用-P自动映射,端口不可预期,排查问题时反而更费劲。
3.4 在容器内部操作 KasmVNC 服务
容器跑起来后,你还需要掌握几个容器内命令,用于排查会话状态和重启服务。KasmVNC 提供了一个vncserver脚本来管理会话。
docker exec -it kasmvnc-office bash vncserver -listvncserver -list会列出当前所有会话、显示编号和端口。显示编号与端口的关系是:显示编号:1对应传统 RFB 端口 5901 和 WebSocket 端口 6901。如果列出多个会话,说明之前有异常退出后残留了旧会话,需要逐个清理,否则新会话可能因为默认显示编号被占用而启动失败。
重启会话的完整命令是:
vncserver -kill :1 vncserver先杀掉旧的显示 1,再启动一个新会话。容器重启不等同于会话重启,因为容器内的初始化脚本可能不会重新生成桌面会话配置,这一点在改完配置文件后尤其重要。实际使用中,我习惯给每个会话固定一个显示编号,比如vncserver :1 -geometry 1920x1080,避免容器重启后会话编号漂移。
4. 面向真实网络环境的配置:认证、TLS、编码与性能参数
4.1 读懂 kasmvnc.yaml:KasmVNC 的“后悔药”
KasmVNC 把大部分运行参数集中在一个 YAML 文件里,它在容器内的默认路径是当前用户目录下的.vnc/kasmvnc.yaml。配置文件的价值在于,它可以在不改动启动脚本的前提下调整认证、端口、证书、编码等参数,这也是它和传统 VNC 的最大区别。
server: server-name: my-kasmvnc websocket: port: 6901 authentication: require-ssl: true types: - VncAuth - TokenAuth encoding: frame-rate: 15 quality: 70字段的含义不难理解:server.server-name是会话的显示名称,日志里会用它的第一条记录来标识连接来源;websocket.port是 WebSocket 监听端口;authentication.types用于启用哪些认证方式,可以同时启用 VNC 密码和 Token;encoding.frame-rate限制了服务端每秒最多推送多少帧,帧率过高除了浪费带宽,还会把 CPU 跑满。
修改配置后,必须重启 VNC 会话才能生效,光重启容器不够。我见过不止一次有人改完配置文件后重启了容器,但会话还是旧参数,原因就是容器初始化脚本里的会话还在运行旧配置。另外,YAML 文件的缩进非常严格,少一个空格都会导致解析失败,检查配置问题时先用python3 -c "import yaml; yaml.load(open('kasmvnc.yaml'))"做语法校验,能省不少时间。
4.2 认证:从静态密码到 Token 鉴权
KasmVNC 的认证方式分两类:VNC 密码和 Token。静态密码适合小范围使用,Token 适合集成到业务系统里,因为 Token 可以设置有效期和单次使用限制。
authentication: require-ssl: true types: - VncAuth - TokenAuth token-timeout: 300token-timeout是 Token 的有效期,单位秒。系统集成时,后端先生成一个 Token,前端拿着 Token 拼接 WebSocket 地址发起连接,Token 过期后自动失效,即使连接日志被截获也无法长期复用。
这两种模式可以同时启用。静态密码保留给运维人员用,Token 给自动化系统用。实际部署时我建议,凡是面向外部系统的 KasmVNC,都不要开放静态密码认证,而是统一走 Token。静态密码一旦泄露,等于给了对方反复进入桌面的机会,而 Token 至少有时间窗口限制。
4.3 TLS 与证书:从自签证书到正式证书
KasmVNC 默认自签证书,浏览器会警告。内网演示无所谓,但生产环境需要换成正式证书。证书文件直接挂载进容器,并修改 YAML 配置指定证书和私钥路径:
websocket: ssl-cert: /certs/vnc-cert.pem ssl-key: /certs/vnc-key.pem容器启动时挂载证书目录:
docker run -d \ --name kasmvnc-tls \ -p 6901:6901 \ -v /etc/ssl/kasmvnc:/certs:ro \ -e VNC_PW=your-password \ kasmweb/ubuntu-focal-desktop:latest换完证书后验证点有两个:浏览器地址栏不再显示证书警告,以及 WebSocket 连接使用的是新证书的指纹。证书链不完整时,浏览器可能连页面都能打开,但 WebSocket 握手阶段直接报错。这种错很难排查,日志里通常只显示 TLS 错误,没有更具体的信息。我的做法是先在浏览器里查看证书链是否完整,再检查私钥是否匹配,最后才怀疑 YAML 配置问题。
4.4 编码与带宽:弱网场景下的动态平衡
编码参数直接决定远程桌面在公网环境下的可用性。KasmVNC 提供的几个编码参数值得仔细调,为了适配不同的网络条件,通常并不是越清晰越好。我一般用这三个参数调出一个相对稳定的体验:
encoding: frame-rate: 15 quality: 70 lossless: falseframe-rate是每秒最大帧数,调低会明显减少带宽消耗,体现在操作上就是鼠标轨迹有点飘,但键盘输入不受影响。quality是 JPEG 压缩质量,数值越高画质越好、带宽占用越大。远程阅读代码和文档时 70 够用,远程看视频至少要到 90。lossless是是否开启无损模式,无损模式下画质无损失,但带宽消耗成倍增长,只适合内网使用。
重点提醒:调参时不要只看网络带宽,还要盯着服务器的 CPU。KasmVNC 的编码过程消耗 CPU 非常明显,把帧率和质量拉满后,单个会话可能吃满两个核心。多会话共用一台宿主机时,先算好 CPU 核数再决定能不能拉高画质。有时候把帧率从 30 降到 15,画面流畅度反而感觉更稳,因为 CPU 不被打满之后,操作响应的延迟也跟着降下来了。
5. 避坑:KasmVNC 部署与使用中的六条真实踩坑记录
5.1 打开页面后白屏
现象:输入密码后页面一直停留在灰色或白色,桌面不出现。
原因:多数情况下是容器内 D-Bus 服务没有启动或未正确初始化。KasmVNC 捕获桌面时需要经过 D-Bus 获取会话总线,容器环境默认不会自动启动它。
解决:
docker exec -it kasmvnc-demo bash -c "service dbus start"如果 D-Bus 已经启动但仍白屏,继续看日志:
docker logs kasmvnc-demo --tail 100日志中出现Failed to open display,则是显示编号冲突,先用vncserver -list看看有哪些残留会话,再杀掉占用编号的会话。
5.2 中文输入法无法切换或输入乱码
现象:远程桌面能正常显示中文文档,但输入中文要么切不出输入法,要么打出乱码。
原因:容器内没有安装中文字符集和输入法框架。KasmVNC 只负责桌面透传,不负责容器的语言环境。这个坑在标准镜像里极其常见,官方镜像默认不集成完整的中文输入环境。
解决:
docker exec -it kasmvnc-demo bash apt-get update && apt-get install -y locales fcitx locale-gen zh_CN.UTF-8 export LANG=zh_CN.UTF-8如果输入法框架已安装但还是切不出来,去桌面设置里手动选择输入法源,不要只依赖环境变量。很多情况下需要重启一次会话,让输入法框架的配置重新加载。
5.3 剪贴板无法和远程桌面互通
现象:本地复制一段代码,切到远程桌面粘贴,没有反应。
原因:KasmVNC 的剪贴板同步依赖 WebSocket 消息,默认是主动触发方式。浏览器安全策略拦住剪贴板读取权限时,同步自然失败。很多浏览器把剪贴板权限默认设为询问,而用户没有允许,粘贴就静默失败了。
解决:浏览器站点设置里允许该域名的剪贴板权限,然后在远程桌面里用鼠标手动点击输入框粘贴。若仍失败,重启 VNC 会话。KasmVNC 的剪贴板状态会缓存在会话里,会话重启后重新建立通道。这个操作要主动做,不要靠刷新页面解决,刷新不会重置会话缓存。
5.4 WebSocket 握手阶段一直转圈
现象:页面能打开,能输入密码,但一直显示正在连接桌面。
原因:网络策略只放通了 HTTPS 端口,但 WebSocket 通信依赖同一个端口的数据帧传输。有些云平台安全组或办公网防火墙会识别并拦截 WebSocket 的非标准握手信息。
解决:先在本机验证端口协议:
curl -k https://宿主机IP:6901/websockify返回 101 状态码说明协议握手正常;直接超时说明端口被拦截。换个监听端口,或者把 KasmVNC 放到 HTTPS 接入层后面,让流量统一从 443 进来。注意,改了 WebSocket 端口之后,浏览器访问的 URL 也要跟着变,前端脚本会自动请求同源端口,但如果页面和 WebSocket 端口不一致,就会一直握手失败。
5.5 多容器端口冲突导致第二个容器起不来
现象:启动第二个 KasmVNC 容器时提示端口已被占用。
原因:容器内部固定使用 6901 端口,宿主机端口映射必须每个容器单独指定。多个容器共用同一个宿主机端口肯定冲突。
解决:先看端口占用情况:
ss -tlnp | grep 6901规划端口映射表:第一个容器映射6901,第二个映射6902,第三个映射6903。不要在一条命令里给第二个容器也映射6901。另外,容器命名也要唯一,--name重复同样会启动失败。
5.6 公网访问延迟极高
现象:内网响应流畅,放在公网上鼠标明显跟手但带宽不够,操作延迟明显。
原因:帧率和画质参数没降,加上公网 RTT 高,每一帧图像都需要较长时间传输。这也是很多人在内网验证一切正常,一到公网就翻车的原因——内网带宽大,掩盖了编码参数不合理的问题。
解决:把frame-rate降到 10,quality降到 60,关闭无损模式。如果还卡,看宿主机带宽:
iftop -i eth0 -n持续占用超过 80%,说明并发会话过多,或者帧率参数不够激进。此时优先考虑限制会话数量和降低分辨率,不要一个参数一个参数反复试探,那样只会延长排查时间。
6. 进阶:动态 Token、日志审计与一次性连接体验
6.1 让远程桌面变成“可生成的链接”
KasmVNC 的 Token 认证一旦启用,远程桌面就从“固定地址 + 固定密码”变成了“临时链接”模式。后端系统在工单生成时向 KasmVNC 请求一个有效期为几分钟的 Token,前端拿到 Token 后拼接成带参的 WebSocket 地址,浏览器直接打开即可连接。
Token 的生成通常不由 KasmVNC 自己完成,而是由上层管理平台调用 KasmVNC 的认证接口去生成。你的系统如果已经做了统一的认证入口,可以考虑把 Token 生成逻辑嵌入现有后端,这样远程桌面的访问权限就和业务权限绑定在一起了。对于一次性的运维工作,这个模式能避免把长期密码交付给外部人员,也方便操作完自动回收桌面权限。
6.2 用日志把访问记录串起来
KasmVNC 的连接日志分散在容器内,我会用挂载方式统一收集。给容器加一个日志卷:
docker run -d \ --name kasmvnc-audit \ -p 6903:6901 \ -v /var/log/kasmvnc:/var/log/kasmvnc \ -e VNC_PW=your-password \ kasmweb/ubuntu-focal-desktop:latest日志里包含来源 IP、连接时间、断开时间。把这些数据同步给日志平台后,可以按 IP 维度和时间维度分析访问规律。固定 IP 在非工作时间频繁出现,通常说明账号有被滥用的风险。给每个容器设置独立的server-name也能让日志更清晰,多个桌面共用一个名称时,审计日志会很难追踪到具体是哪个会话。
6.3 一个习惯:每改一次配置,强制走完三条验证
我在部署 KasmVNC 这件事上最大的教训是,改完配置后只重启容器不够,必须把端口、日志、浏览器连接三个验证点走一遍。先ss查端口是否正常监听,再docker logs看有没有启动错误,最后用浏览器实际连一次。这三个步骤每次做,基本能在几分钟内暴露大多数配置问题。
如果你需要更精细的观察,还可以在浏览器开发者工具里盯着 WebSocket 的帧数据,看看握手完成后有没有持续的图像帧推送。如果握手成功但一帧图像都没有,问题几乎都出在服务端帧缓冲捕获环节,这时候再回到 D-Bus 和显示编号这两个点去查,效率更高。
从那以后,我每次部署 KasmVNC 都强制自己走一遍这套流程,很少再因配置细节翻车。希望这些记录能帮你在第一次部署 KasmVNC 时少踩几个坑。
本文还有配套的精品资源,点击获取