☰
VNC远程实战:RFB原理、TigerVNC配置与安全加固
2026/11/2 7:18:44 网站建设 项目流程

1. 先把“VNC远程”这件事的来龙去脉讲明白

VNC远程这套东西,我用了快十年,从最早折腾一台老旧图形工作站,到后来帮人部署实验室的共享机器,兜兜转转绕不开它。它的全称是 Virtual Network Computing,直译叫“虚拟网络计算”,名字听着挺玄,本质其实很朴素:把一台机器的屏幕像素,实时搬到另一台机器的窗口里,同时把你的键盘鼠标动作反向送回源头。中间走的是一套叫 RFB 的协议,全称 Remote Framebuffer,翻译过来就是“远程帧缓冲”。你用VNC远程开会话,本地端看到的不是视频流,而是一张不断被局部刷新的位图。

为什么到今天还在用它?因为它足够“低要求”。被控端只要能把画面画出来、把输入事件读进去,VNC 就能工作,跟这台机器跑什么桌面环境、什么显卡、什么操作系统关系不大。Linux 图形终端、Windows 老服务器、树莓派、工控小主机,甚至是某些只开放了图形界面的专用设备,VNC 几乎是唯一不折腾驱动的办法。它适合的人群也很明确:需要一台机器长时间挂着图形程序、自己时不时连上去看一眼的后端开发者;要给团队共享一块“公共桌面”的运维和实验人员;还有像我这样,不想为了跑一个必须带界面的工具就专门搬一台显示器过来的人。

但我得先说清楚一件事:VNC 好装,不代表好用。默认配置上线,你大概率会遇到三个问题——画面卡得像幻灯片、密码形同虚设、一觉醒来发现端口被人扫了几万次。这三个坑对应着三块完全不同的知识:编码与带宽、认证与会话模型、网络边界与访问控制。这篇东西我会按我自己的推进顺序讲:先拆原理,再选实现,然后手把手落地,最后把安全加固和踩过的坑一次性说透。读完你至少能做到:在自己的机器上搭出一套能长期用、不需要每次重启就重新配一遍的VNC远程环境。

1.1 它解决的到底是什么问题

很多人对VNC远程的第一印象是“远程桌面”,然后下意识拿它跟远程控制类工具类比。其实两者解决的问题不完全一样。远程控制类工具默认是“接管”你现在正坐在前面的那台机器的真实屏幕,你走了,别人走过去看到的东西就和你刚才看到的完全一样,甚至你还在操作的时候,坐在机器前的人能看到鼠标自己动。

VNC 的模型多了一层选择。它可以是“共享”,也可以是“独立”。共享模式就是把物理屏幕广播出去,你看到的和别人看到的是同一块画面;独立模式则是服务端在内存里凭空开出一块虚拟帧缓冲,比例、分辨率、色深全由你指定,跟机器接没接显示器毫无关系。这一点是 VNC 最被低估的价值——我可以在一个没有显示器的机箱里开三块虚拟桌面对应三个不同分辨率的测试环境,互不干扰,各自登录各自的账号。如果你只是想要“远程看一下我桌上那台机器的屏幕”,共享模式就够了;如果你是“我要在一台无头机器上跑一个 GUI 工具”,那独立模式才是正解。这两条路后面配置步骤完全不一样,选错了会一直卡在黑屏上。

另一个常被问到的问题是延迟。VNC 不是云游戏,它不会为了流畅去牺牲画面完整性,默认是“画准”优先于“画快”。局域网里基本感觉不到延迟,跨地区走公网时,打字还行、拖窗口会有明显拖影。所以我在给VNC远程定位功能边界时,心里有条线:跑终端、改配置、看日志、点几下图形工具,完全够;剪视频、跑实时渲染预览、玩需要反应速度的东西,别难为它。

1.2 谁在真正用它,用在哪

我接触过的真实场景,大致可以归成四类,每类的配置重点都不一样。

第一类是无头服务器的图形工具。某实验室有一批机架式机器,常年不接显示器,但上面要跑一些必须带图形界面的数据处理工具。这种情况我会给每台机器开一个独立会话,分辨率设成 1600×900,色深 24,桌面环境用最轻量的窗口管理器,不装完整桌面套件。理由是:没人会盯着看,只需要工具能弹窗、能滚动、能保存文件,装整套桌面纯属浪费内存。

第二类是长期挂机的状态面板。有些监控面板或者内部小工具是网页形式,但要求浏览器保持登录状态、不能锁屏。这种我会用共享物理会话的方式,配合一个自动登录的轻量桌面,让它在虚拟机上一直开着。这类场景最怕的是会话超时被锁,所以要在桌面环境里把屏保、自动锁屏、休眠全部关掉。

第三类是跨平台的临时协助。比如 A 同学的本机环境出了怪问题,截图描述半天说不清,直接连过去看一眼最快。这种是“短连接”场景,重点是快,密码可以临时设、用完就改,甚至可以直接用一次性口令。

第四类是多分辨率 / 多语言环境的验证。前端或者客户端开发经常需要确认界面在不同分辨率、不同 DPI 下的表现,开几个不同尺寸的虚拟会话挨个截图,比反复改本机分辨率要省事得多。

把这四类想清楚之后你会发现,VNC 的配置其实没有“标准答案”,只有“针对场景的一组参数”。我在后面给的所有命令和数值,都是按“一台 Linux 无头机器,要长期稳定运行一个独立图形会话,只允许自己访问”这个最常见的目标来写的。如果你的场景是共享物理屏幕,参考 4.4 节里关于共享模式的差异说明调整即可。

1.3 我对“能连上”和“能天天用”的区分标准

能连上,只说明端口通了、认证过了。能天天用,还得同时满足下面这几条,这也是我给自己的验收清单:

  • 重启机器之后不用重新敲一遍命令,服务自己起来;
  • 会话不会被“上一次异常断开”卡住,重连不上;
  • 中文能打出来,复制粘贴能把内容带过去;
  • 公网上扫不到我的端口,或者扫到了也进不来;
  • 断网重连之后桌面还在,跑着的程序没被干掉。

这五条里,前两条靠 systemd 和会话清理策略,第三条靠输入法环境变量和剪贴板同步组件,第四条靠监听地址和防火墙,第五条靠“会话常驻”而不是“登录即启动”。很多人卡在第五条,是因为用错了启动方式——一断开,服务端就把整个 X 会话销毁了,程序自然全没了。

2. 拆开看:RFB 协议和它到底在传什么

想把VNC远程调到顺手,绕不开对 RFB 的一点理解。你不需要去读协议文档,但要知道它为什么卡、卡在哪、哪个参数能救。RFB 的设计哲学特别“抠”:它假设网络又慢又不可靠,所以每一次更新都只传变化的那一小块区域,而且尽量用最省字节的方式描述这块区域。

2.1 帧缓冲模型:所有操作都变成“像素补丁”

服务端维护一块内存里的像素数组,就是帧缓冲。客户端也维护一块同样大小的本地缓冲区。服务端的工作是持续对比“上一帧”和“这一帧”,找出发生变化的矩形区域,打包成一个个“更新矩形”发出去;客户端收到之后,把对应区域贴到自己那块缓冲区里,然后整块刷新到窗口上。

这个模型带来两个直接后果。第一,静态画面几乎不占带宽。你打开一个文本编辑器不动,连接就是闲置的,一小时也传不了几 KB。第二,大面积变化就是灾难。拖一个窗口横穿屏幕,或者播放视频,几乎每一帧都是全屏变化,带宽瞬间打满。这也解释了为什么很多人觉得“VNC 打开网页挺流畅,一放视频就卡死”——不是 VNC 不行,是它压根不是为连续全屏刷新设计的。

理解这一点,很多优化手段就顺理成章了:把桌面背景换成纯色(去掉渐变和壁纸能省掉大量初始传输)、关掉窗口动画和透明特效、把终端字号调大减少滚动重绘面积。这些土办法的效果,比你去改编码参数还明显。

2.2 一次连接握手会经历什么

从点击“连接”到看见画面,中间大概走这几步,每一步出问题表现都不一样,排查时按这个顺序对号入座最省时间。

  1. TCP 建连。客户端连到服务端的 5900 加显示号端口。连不上就是网络或防火墙问题,跟 VNC 本身无关。
  2. 协议版本协商。双方各报一个 RFB 版本号,取较低的那个。版本不匹配的表现通常是连上后立刻断开,客户端提示协议错误。
  3. 安全类型协商。服务端列出自己支持的认证方式,客户端选一个。这一步是安全加固的重点,后面 5.1 会细讲。
  4. 认证。最常见的是 VNC 认证,服务端发一个 16 字节的挑战值,客户端用密码做处理之后回一个响应。
  5. 客户端初始化。客户端告诉服务端自己要什么像素格式(色深、字节序、是否压缩),服务端回一条 ServerInit,包含屏幕宽高和桌面名字。
  6. 编码协商。客户端报一串自己认识的编码方式,服务端从中挑一个用。
  7. 开始推送更新。客户端不停地发“我准备好了,给我下一块更新”,服务端按需回应。

这七步里,第 4 步和第 6 步是最容易出玄学问题的地方。密码明明对却提示认证失败,八成是密码被截断或者客户端用了不兼容的认证方式;画面能出来但花屏、错位,基本可以锁定在编码协商上,换个编码方式试试往往立刻见效。

2.3 编码方式决定了你卡不卡

同一个画面,用不同的编码方式打包,体积能差十倍以上。我按实际使用感受排个序,从省带宽到费带宽:

编码方式传输原理适用场景我的实际体感
Tight / ZRLE分块 + zlib 压缩 + 调色板优化广域网、普通办公画面默认首选,兼顾速度和画质
Hextile16×16 小块,同色块合并老客户端兼容、低算力设备兼容好,压缩比一般
RRE用矩形填充描述同色区域大面积纯色界面纯色桌面下很省,复杂画面反而变大
Raw原始像素直传局域网、调试对比画质无损但极费带宽,只适合内网
H.264 类视频编码部分较新实现支持动效场景明显好,但兼容性看客户端

算一笔账你就知道为什么必须用压缩编码。1920×1080、24 位色,一帧原始像素是 1920×1080×3 ≈ 5.9 MB。假如每秒刷 30 帧,就是 178 MB/s,折合下来一千四百多兆比特每秒,这还没算协议开销。而 Tight 编码在典型办公画面上能压到几十分之一,一屏静态界面刷新一次可能就几十 KB。所以VNC远程的带宽规划从来不是按“帧率”算的,而是按“你每秒制造多少变化”算的。

注意:不要迷信“把色深调低就更流畅”。24 位降到 16 位确实能省三分之一字节,但某些桌面主题在 16 位色下会出现色带和图标发虚,反而让人看着更累。我的经验是色深保持 24,省带宽靠编码和减少动效,不靠降色深。

3. 选型:我在几种实现里为什么挑了这一套

VNC 只是一种协议规范,具体实现有一大把,行为差异比想象中大。选错实现,你可能在“能不能多人同时连”“会话断了还在不在”“中文能不能粘贴”这些问题上反复吃亏。

3.1 几种常见实现的横向对比

下面这张表是我按“一台 Linux 无头机器、需要独立图形会话、偶尔要从浏览器临时看一眼”这个组合打的分,换场景结论会变,别直接照抄。

实现会话模型优点短板我什么时候用它
TigerVNC独立虚拟会话编码效率高、剪贴板支持好、配置项丰富新版配置方式变过,老教程容易踩坑主力方案,日常都用它
TightVNC独立虚拟会话部署简单、老系统兼容性好功能更新较慢,剪贴板偶有抽风临时机器、老旧发行版
x11vnc共享物理会话能把“已经开着的”桌面直接分享出去依赖物理会话,无头机器上用不了需要看真实屏幕时
noVNC浏览器客户端不用装软件,打开网页就能连依赖额外的前端转换服务借用别人电脑临时访问

这里我要特别强调 TigerVNC 和 x11vnc 的区别,这是新手最容易混的一对。TigerVNC 是“我新建一个桌面给你”,x11vnc 是“我把已经存在的那个桌面广播出去”。前者在无头机器上工作,后者必须有真实会话在跑。你要是照着一篇 x11vnc 的教程在一台没接显示器的机器上敲命令,会得到“cannot open display”这种让人一头雾水的报错,问题不在命令,在你选错了工具。

3.2 独立会话和共享物理会话,怎么选不后悔

我给一个简单粗暴的判断法:问自己“我不在的时候,机器前面会不会有人坐下来用”。会,就用共享模式;不会,就用独立会话。

独立会话的好处一大堆:分辨率随便设、开几个都行、崩溃了不影响别人、会话可以挂很久。代价是它跟你本机的物理桌面是两套环境——你在物理屏幕上装的字体、配的输入法,独立会话里要重新确认一遍。我第一次配的时候就是忘了在启动脚本里带输入法环境变量,结果中文死活打不出来,查了半小时才发现是会话环境没继承。

共享模式的好处是“所见即所得”,装什么就能用什么,不用重新配环境。代价是无头机器上根本用不了,而且一旦物理会话被锁屏,你远程连上去看到的也是锁屏界面。

我目前的组合是:主力用独立会话,因为它能常驻、能多开、不干扰别人;另外保留一个共享模式的配置作为应急,万一某个程序只能在真实桌面环境下跑,切过去就行。两套配置分别对应两个不同的服务,端口分开,互不影响。

3.3 客户端这边我踩过的坑

客户端选型没那么多讲究,但有几个细节值得提前知道。

第一,客户端和服务端的显示号要对应。服务端开的是:1,端口就是 5901,客户端里填IP:1或者IP:5901都行,但别填错。我见过有人服务端开:1,客户端连 5900,然后抱怨“连接被拒绝”。

第二,“自动缩放”和“适应窗口”是两回事。前者是把远端分辨率改成跟你窗口一样大,后者是把画面拉伸显示。缩放会触发服务端的模式切换,有些实现下切换瞬间会闪一下甚至短暂断连;拉伸则不改变远端,但字会糊。我的习惯是固定一个常用分辨率,客户端窗口就开那么大,两边都不缩放,看着最舒服。

第三,记住“只读模式”这个选项。多人围观某个操作过程的时候,把其他人设成只读,避免手滑误操作,这个功能救过我很多次。

4. 实操:从零搭出一台可远程的图形桌面

下面这套流程我按“全新安装的 Linux 机器,最小化安装,没有图形界面”来写。你可以照着敲,命令里的用户名、分辨率按自己的改。

4.1 环境准备与依赖安装

先装服务端和一套轻量桌面。我这里用了一个精简的窗口管理器加一点基础工具,不用完整桌面套件,原因是独立会话里跑完整桌面又慢又占资源,而绝大多数场景只需要窗口、终端、文件管理器和浏览器。

# Debian / Ubuntu 系 sudo apt update sudo apt install -y tigervnc-standalone-server tigervnc-common \ xfce4 xfce4-terminal dbus-x11 \ xterm # RHEL / CentOS / Alma 系 sudo dnf install -y tigervnc-server xterm dbus-x11

这里有两个包容易被忽略,但很关键。dbus-x11提供会话总线,很多桌面组件靠它通信,缺了会导致窗口管理器启动失败、剪贴板不工作。xterm是我留的兜底终端——万一桌面环境起不来,至少还能在纯 X 环境里开个终端查日志。

装完之后先确认服务端的可执行文件路径,不同发行版放的位置不一样:

which Xvnc tigervncserver vncserver 2>/dev/null

输出的路径记下来,写 systemd 单元的时候要用绝对路径。

4.2 设置访问密码与权限

VNC 的密码不是明文存的,而是一个独立的密码文件。生成方式:

vncpasswd # 按提示输入两次密码 # 询问是否设置“只读密码”时,一般选 n

默认会写到~/.vnc/passwd。这个文件一定要收紧权限:

chmod 600 ~/.vnc/passwd

注意:VNC 认证的密码长度被协议本身限制在8 位以内,超出的部分会被直接丢掉,而且不报错。也就是说你设了MyPassword2024,实际生效的是MyPasswo。这不是实现的问题,是协议设计遗留。所以第一,密码别指望它有多强;第二,务必不要把它当作唯一的防线,5.2 节的网络层限制才是真正管用的。

顺手建一下配置目录,有些版本首次启动会自己建,但手动建好更省心:

mkdir -p ~/.vnc

4.3 编写启动脚本

独立会话启动时,服务端会执行一个启动脚本,你要在这个脚本里告诉它“会话起来之后跑什么”。这个脚本叫xstartup,位置在~/.vnc/xstartup。

#!/bin/sh unset SESSION_MANAGER unset DBUS_SESSION_BUS_ADDRESS # 输入法环境,中文输入必配 export XMODIFIERS="@im=fcitx" export GTK_IM_MODULE=fcitx export QT_IM_MODULE=fcitx export LANG=zh_CN.UTF-8 # 剪贴板同步 vncconfig -iconic & xterm -geometry 100x30+10+10 -ls -title "Session" & # 启动窗口管理器 exec startxfce4

写成之后必须给执行权限,这是最常见的“黑屏原因”之一:

chmod +x ~/.vnc/xstartup

这段脚本里有几个点值得单独说。

unset SESSION_MANAGER和unset DBUS_SESSION_BUS_ADDRESS这两行,是为了避免会话继承到不该继承的桌面总线。少了这两行,在某些发行版上会出现窗口管理器启动失败或者启动了两遍的怪现象。

输入法那几行,是让会话内的图形程序知道该找哪个输入法框架。你如果用的是另一套输入法框架,把fcitx换成对应名字即可,但环境变量名不要改。我见过有人把GTK_IM_MODULE写成GTK_IM_MODULES,然后死活查不出为什么浏览器里打不了中文。

vncconfig负责剪贴板同步和几项会话设置,后台常驻。少了它,你复制的东西在两端之间传不过去,只能靠手打,体验非常糟糕。

如果你的场景是“把已有的物理桌面共享出来”,那这里不用写xstartup,改用共享模式的启动方式,加上-display参数指向真实显示即可。但再次提醒:无头机器上走这条路必然失败。

4.4 启动、验证与会话常驻

手动跑一次,先确认能通:

vncserver :1 -geometry 1920x1080 -depth 24 -localhost yes

参数逐个解释。:1是显示号,对应端口是 5900 + 1 =5901,这个加法关系要刻在脑子里,后面防火墙、端口转发全靠它。-geometry是虚拟屏幕分辨率。-depth 24是色深。-localhost yes表示只监听回环地址,这是安全加固的第一道锁,先加上,后面要远程访问时用加密隧道转发过来。

启动成功会输出类似这样的信息:

New 'hostname:1 (user)' desktop at :1 on machine hostname Starting applications specified in /home/user/.vnc/xstartup Log file: /home/user/.vnc/hostname:1.log

日志文件路径一定要记住,所有启动失败的原因都写在里面。看日志是第一反应,不是最后手段。

验证端口:

ss -tlnp | grep 590

正常应该看到只监听在127.0.0.1:5901。如果看到的是0.0.0.0:5901或*:5901,说明-localhost没生效,去检查是不是被配置文件里的设置覆盖了。

会话常驻这一块,很多人遇到的“断线后重连不上”,根子是上次会话没被清理干净。手动管理时用这两个命令:

vncserver -list # 看当前有哪些会话 vncserver -kill :1 # 杀掉指定会话

重启前先确认旧会话已经清掉,否则新会话会启动失败,报“会话已存在”。

4.5 用 systemd 接管,让它开机自启

手动敲命令只能算调试,真正要天天用,必须交给 systemd。有两种写法,我推荐用户级服务,改起来不用 sudo。

建目录和单元文件:

mkdir -p ~/.config/systemd/user
# ~/.config/systemd/user/vncserver@.service [Unit] Description=VNC Server on display %i After=network.target [Service] Type=simple ExecStartPre=-/usr/bin/vncserver -kill :%i ExecStartPre=/bin/sh -c 'mkdir -p %h/.vnc' ExecStart=/usr/bin/vncserver :%i -geometry 1920x1080 -depth 24 -localhost yes ExecStop=/usr/bin/vncserver -kill :%i Restart=on-failure RestartSec=5 [Install] WantedBy=default.target

这里有几个细节。ExecStartPre前面加了个减号,表示“这条命令失败也不阻断启动”,因为首次启动时没有旧会话可杀,-kill会返回非零。Type=simple配合vncserver是可行的,因为它启动后会一直挂着;如果你用的是直接调用Xvnc的方式,就要改成Type=forking并补上PIDFile。

启用和启动:

systemctl --user daemon-reload systemctl --user enable --now vncserver@1.service systemctl --user status vncserver@1.service

还有一步特别容易被忘:用户级服务默认在用户没有登录会话时不会启动,机器重启后它可能压根没跑。要让它常驻,得开启 linger:

sudo loginctl enable-linger $USER

这一条我在第一台机器上找了整整一个下午,症状就是“手动启动一切正常,重启之后端口不监听,但服务状态显示 inactive 而不是 failed”。加上 linger 之后问题消失。

4.6 客户端连接与画质参数

客户端这边,第一次连建议按下面的顺序调,别一上来就动高级选项。

先确认网络通:在客户端机器上测试端口可达。然后用客户端连接,地址填127.0.0.1:5901——注意,因为我们加了-localhost yes,直接连远端 IP 是连不上的,必须先建隧道(见 5.2 节)。如果你暂时只在内网测试,可以先把-localhost yes改成-localhost no,测通了再改回来,别带着这个设置上线。

连接成功后,画质按这个顺序优化:

  1. 编码方式选 Tight 或 ZRLE,这是通用最优解;
  2. 压缩等级拉到 5~6,兼顾 CPU 和带宽,拉到 9 省带宽但服务端 CPU 会明显升高;
  3. JPEG 质量如果支持调节,设 6~7,低于 5 文字边缘会发虚;
  4. 色深保持 24,除非带宽确实紧张;
  5. 关掉桌面的窗口动画、透明效果、背景图片。

我实测下来最有效的一条,其实是第 5 条。把壁纸换成纯深色,窗口动画全关,同等网络条件下拖窗口的拖影会少一大截。原理在第 2.1 节说过:变化面积越小,传输量越小。

5. 安全加固:别让 5901 直接暴露

这块我要说得重一点。VNC 协议设计于一个人人互相信任的年代,默认配置几乎没有任何防护:认证只有 8 位有效密码、流量全程明文、端口规律到 5900 起。一台把 5901 开在公网上的机器,被扫描和尝试登录是必然事件,快则几小时。

5.1 密码认证的真实强度

前面提过,VNC 认证的密码被截断到 8 位。更麻烦的是它的认证流程:服务端发挑战,客户端用密码算出一个响应,这个响应对于同一个挑战是固定的。明文传输意味着任何能抓到握手包的人,都可以离线拿这个响应去尝试爆破,8 位密码的搜索空间在普通机器上并不算大。

所以我的结论很直接:VNC 自带的密码只能当作“防止同事误连”,不能当作“防止外人入侵”。真正的防线必须在网络层。

如果你用的是较新的实现,可以看看是否支持 TLS 类认证方式,能开就开。但即使开了,我也不建议直接把端口暴露出去,因为配置复杂度上去了,一个参数写错就可能退回明文,而你不一定察觉得到。

5.2 只监听回环,用加密隧道转发

这是我目前唯一推荐的做法,没有例外。服务端只监听127.0.0.1,外部访问一律走加密隧道的本地端口转发。好处是:VNC 流量根本不出现在网络上,外面的人连端口都看不到,认证强度的问题也就无从谈起。

客户端这边执行:

ssh -N -L 5901:127.0.0.1:5901 user@server-address

参数解释。-N表示只建立隧道、不执行远程命令。-L 5901:127.0.0.1:5901表示把本地的 5901 端口,转发到远端机器视角下的127.0.0.1:5901。这样你在本地客户端里连127.0.0.1:5901,流量实际上是被加密送到远端,再由远端本机转发给 VNC 服务。

-N这个参数别省。少了它,ssh 会给你开一个交互终端,关掉终端隧道就断了。加上-N,它就是一个纯后台通道。想让它更省心,可以再补两个参数:-f让它转到后台,-o ServerAliveInterval=60让它定期发心跳,避免中间的连接跟踪表把空闲连接清掉。我踩过一次坑:隧道建好后去吃了个饭,回来发现画面还在但操作没反应,就是因为空闲超时被中间设备掐了,加了ServerAliveInterval之后再没出现过。

如果嫌每次敲命令麻烦,写进本地的~/.ssh/config:

Host vncbox HostName server-address User myuser LocalForward 5901 127.0.0.1:5901 ServerAliveInterval 60

之后一句ssh -N vncbox就行。

5.3 防火墙白名单与访问日志

万一你的场景确实必须直接开放端口(比如内网里多台机器都要连、不方便人手一条隧道),那至少要把它锁在最小的网络范围内。

# 只允许某一个网段访问 5901 sudo ufw allow from 192.168.10.0/24 to any port 5901 proto tcp sudo ufw deny 5901/tcp sudo ufw status verbose

顺序很重要。放行规则要在拒绝规则前面,因为多数防火墙是按顺序匹配的,先命中先生效。写反了就是“明明加了白名单但还是连不上”。

云环境上还要注意一层:机器自带的防火墙和安全组是两套独立的规则,两边都要配。我见过有人在本机防火墙里放行了,安全组没开,然后花很长时间怀疑服务端配置。

另外建议做一件小事:把访问日志留下来。服务端启动日志里会记录每次连接的时间和来源地址,定期扫一眼,如果出现大量来自你完全没见过的地址的尝试记录,说明端口已经暴露了,赶紧收回来。

grep -i "connection\|accepted\|auth" ~/.vnc/*.log | tail -50

5.4 浏览器接入的 HTTPS 网关方案

有些场景确实需要在别人的电脑上临时连一下,不方便装客户端。这时候可以上浏览器方案:服务端还是只监听回环,另起一个小的转换服务,把 WebSocket 请求转成对本地 VNC 端口的 TCP 连接,前端用一个静态页面渲染。

关键点在于,这个转换服务本身也必须放在 HTTPS 后面,而且只对已认证的用户开放。我的做法是把它挂在统一登录的网关后面,网关负责身份验证,验证通过才转发到后端。这样外部能看到的只有 443 一个端口,其他全部关闭。

这套方案我一般只作为临时通道,用完就停掉。理由很简单:任何多出来的一个组件,都是多出来的一个可能出错的地方,长期开着的服务越少越好。

6. 常见问题与排查实录

这一节都是我真金白银踩出来的。每条我按“现象—原因—动作”写,方便你直接对号入座。

6.1 连上了,但是黑屏、灰屏、只有鼠标

这是出现频率最高的问题,占我处理过的故障一多半。区别在于屏幕是什么颜色。

纯黑屏,什么都没有:最常见原因是xstartup没有执行权限。服务端找到了脚本但执行不了,于是会话里什么都没起。动作:chmod +x ~/.vnc/xstartup,然后 kill 掉会话重启。

灰白底加一个 X 形光标,或者只有一个鼠标指针:脚本执行了,但最后的桌面环境没起来。原因通常是脚本里exec的那行命令不存在,或者依赖缺了。动作:看日志里的报错,tail -50 ~/.vnc/主机名:1.log,常见的是“command not found”,说明桌面环境包没装全。

能看到桌面但一片花:编码协商或者色深不匹配。动作:客户端里换编码方式,把色深明确指定成 24 位,重连。

还有一种伪装成黑屏的情况:机器本身是 Wayland 会话。部分发行版默认走 Wayland,而传统 VNC 服务端依赖 X 的能力,在 Wayland 下会各种不正常。动作:注销后在登录界面切回 Xorg 会话,再启动服务端。这个坑很隐蔽,因为日志里不一定有明显错误。

6.2 画面卡、花屏、鼠标漂移

卡:先分清楚是“操作延迟高”还是“画面更新慢”。延迟高看网络,用一个简单的测速和延迟测试就能判断;更新慢看编码和变化面积。我遇到最多的是后者——桌面上挂了一张渐变壁纸,加上一个每秒刷新的系统监视器小工具,服务端一直在算差异。关掉这些之后立刻顺畅。

花屏:典型表现是画面某一块错位、颜色不对、出现残影。八成是编码实现的问题,动作是换编码方式,从 Tight 换到 ZRLE 或者 Hextile 试一遍。如果只在特定客户端出现,那就是客户端的问题,换一个版本。

鼠标位置偏移:在缩放或者高 DPI 场景下容易出现。原因是远端分辨率和你本地窗口尺寸不成整数比,坐标映射有误差。动作:把远端分辨率设成和本地窗口完全一致,不做缩放。如果非要用高 DPI,把远端分辨率设成物理分辨率的两倍,让缩放变成整数倍。

6.3 中文输入法和剪贴板

打不出中文:先确认会话里输入法框架真的起来了。开一个终端执行:

echo $XMODIFIERS $GTK_IM_MODULE $QT_IM_MODULE

三个变量都应该有值。空的说明xstartup里的环境变量没生效,检查是不是写在了exec后面——写在exec之后的命令根本不会执行,因为exec会替换掉当前进程。

变量都有值但还是打不出来,那就是输入法服务本身没启动。在会话里手动起一次:fcitx -d(或对应的框架),观察有没有报错。

剪贴板传不过去:检查vncconfig是不是在跑,ps aux | grep vncconfig。没有的话说明xstartup里那行没生效。另外要注意,剪贴板同步在部分实现下对“富文本”支持不好,纯文本最可靠。我复制一大段带格式的内容过去经常变成纯文本,这是正常现象,不是故障。

6.4 掉线之后连不回去,提示会话被占

VNC 服务端默认可能会有“同一个会话只允许一个客户端”或者“已有客户端连接时拒绝新连接”的行为。异常断开(比如网线被拔、客户端崩溃)之后,服务端认为那个连接还在,于是新的连接被拒。

处理办法分两步。临时解决:vncserver -kill :1然后重新启动。根治:启动参数里加上允许共享连接、允许重复连接的相关选项,让服务端不排斥新的连接请求。具体参数名各实现不同,看文档里的“shared”相关选项。

再补一个更隐蔽的情况:会话列表显示没有会话,但端口还在监听。这是上次的进程没退干净。ss -tlnp | grep 5901找到进程号,确认是自己的 VNC 进程之后杀掉,再重启服务。

6.5 排查速查表

现象最可能原因第一步动作
连接被拒绝服务未启动 / 端口不对 / 防火墙拦截ss -tlnp | grep 590看有没有监听
连接超时网络不通 / 安全组未放行先在源端测端口可达性
认证失败密码超 8 位被截断 / 密码文件权限不对重新vncpasswd设 8 位以内
黑屏无桌面启动脚本无执行权限chmod +x ~/.vnc/xstartup
灰屏只有光标桌面环境未安装或启动失败看会话日志末尾报错
画面花屏编码不兼容客户端换编码方式
中文打不出输入法环境变量缺失检查三个环境变量
剪贴板不同步同步组件未运行确认后台同步进程在跑
重启后服务不自启用户级服务未开启 lingerloginctl enable-linger
断线后连不上旧连接残留杀会话重启

7. 长期使用中攒下的几条经验

最后这部分不按教程写,纯粹是我自己用下来觉得值钱的东西。

7.1 分辨率别贪大,按用途定

我一开始把所有会话都设成 1920×1080,觉得越大越清楚。用了半年发现完全没必要,反而拖慢了响应。后来按用途分了档:写代码、看日志的会话用 1600×900,字号调大之后一屏能看的内容差不多,传输量少了快一半;需要看网页排版的用 1920×1080;做界面截图验证的按目标设备尺寸单独开。这个习惯养成之后,同样的网络环境下体验明显变好。

另一个相关的小技巧:如果你的机器根本没有接显示器,某些情况下 X 服务起不来,需要配置一个虚拟显示驱动才能撑起高分辨率。这时候先别急着折腾驱动,把分辨率降到 1280×720 试试能不能起来,能起来就说明是驱动的事,不能起来说明是别的问题,能省掉不少误判。

7.2 会话里跑什么,要有取舍

独立会话是“另一个世界”,你在物理桌面装的东西它不继承。所以我给自己定了个规矩:会话里只装必需品,不放开发环境的重型工具。理由很实际——独立会话的图形性能有限,跑一个重型 IDE 索引代码,界面会明显迟钝;而这些活完全可以在终端里用命令行工具完成。

反过来,有些东西必须在会话里跑:需要图形界面的专用工具、需要浏览器保持登录状态的面板、需要展示给别人看的演示环境。这些的共同特点是“不跑图形界面就干不了”,正好是 VNC 的用武之地。

7.3 关于“要不要一直开着”

有人问我是不是所有机器都常开 VNC 服务。不是。我只在真正需要的那几台上常开,其余的按需启动、用完就关。原因有两个:一是减少暴露面,多开一个服务就多一份维护成本;二是资源,一个常驻会话即使什么都不做,也会占掉几百兆内存和一点 CPU。

如果一个会话确实要长期挂着,我会额外做两件事:把桌面的屏保、自动锁屏、休眠全部关掉,避免回来发现锁屏了还要输密码;定期清一下会话里的临时文件,因为独立会话的/tmp是共享的,长期跑的东西容易把磁盘撑满。

至于“断开之后程序会不会被干掉”,关键取决于启动方式和会话管理策略。用 systemd 托管的独立会话,客户端断开只是断开连接,会话本体还在,程序继续跑。这一点跟共享模式不同——共享模式下如果你把物理会话注销了,那什么都留不下。理解这个差异,你就能提前知道哪些操作会导致“跑了一晚上的任务没了”。

我自己的习惯是:所有需要长时间跑的任务,要么放在终端里用会话保持工具挂着,要么放在 VNC 会话里但确保会话是由 systemd 托管的。这两条路之外的做法,都可能在某个深夜给你一个惊喜。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询