发布于 2026-10-29 19:53,标签:组网、尝试、娱乐、Mac、SSH
通过异地组网,把游戏本变成一台远程主机
开发一个拼接了TailScale、Sunshine、Moonlight等工具的一站式远程主机工具(娱乐性质)的探索过程与心得

Loading…
发布于 2026-10-29 19:53,标签:组网、尝试、娱乐、Mac、SSH
开发一个拼接了TailScale、Sunshine、Moonlight等工具的一站式远程主机工具(娱乐性质)的探索过程与心得

这篇博客里讲了我通过现成方案的拼接,将一些成熟的方案做进了一个一站式的控制面板,实现较低延迟的远程终端、远程桌面、性能监测等功能。当前社区市面上同类成熟方案众多,我的想法只是想把一些较为成熟的方案汇总到一个入口上,很多设计都是服务于我个人使用习惯,并不具有普适性,如果各位想要在自己的电脑上实现类似功能,可以参考我的思路,并选择社区上更成熟的方案。
本博客文章涉及的项目在github.com/InfiniteScope/rearwindow,Apache-2.0 License.
最近双十一,随手刷某宝的时候发现以前很想买的 MacBookAir 价格可以叠优惠到六千多;虽然今年618的时候也见过五千多 MacBookAir ,但是当时总以为还会再便宜一些;只不过之后的几个月,看着苹果涨价以及内存、硬盘涨价愈演愈烈,Air价格一路上到八千多,所以看到现在能降到六千多的 M5 16GB+512GB 的配置时还是没忍住下单了(这个全新机的价格已经比海鲜市场上大多数成色好点的二手机子还要便宜了;所以当时想着就算用着不喜欢,二手出了也不亏,更何况内存涨价并没有减退的趋势,就当买了个理财产品)。但是我自己在学校里主用着一台天选6Pro,我之前的项目文件、开发环境、图文资料都保存在我游戏本中;虽然重配一遍常用的开发环境也不算很麻烦,可是有些时候游戏本上还跑着 Agent 帮我改项目,而我的游戏本一般放在离我寝室比较远的实验室工位上,我还要赶着寝室楼晚上封楼赶回寝室,我就很想能让方便随身携带的 Air 能远程连接我的游戏本。
实现远程桌面的方式有很多种,向日葵、UU远程、Parsec……但是这些方案要么需要付费,要么延时较长;并且对于控制命令行中的 Agent 使用远程桌面有点小题大做,所以我就想到自己做一个聚合的解决方案平台,方便我通过一个入口就能实现远程终端、远程桌面、性能监测等功能。这个项目我命名为 rearwindow(github.com/InfiniteScope/rearwindow,Apache-2.0)。
想要以最低的延迟实现以上功能的标准方案就是 Tailscale,在主机端电脑和终端电脑上分别装上 Tailscale 并且登录同一账号便可实现两台电脑通过 Tailscale 直连。Tailscale 通过一个控制服务器分别告诉主机端和终端分别在哪儿;由于我的宽带并没有公网 IP,Tailscale 让终端与主机向彼此路由器的 NAT 发包,虽然彼此收到的第一个包都会被 NAT 拦截,但是也会在 NAT 留下一个映射记录,让终端和主机确认了各自的位置。
Tailscale 还有一个优点,如果通过控制服务器打洞失败,终端与主机无法直连时,它也提供了 DERP 中继服务器进行中转作为保底机制。在直连降级为中继时,DERP 转发的数据包也会经过加密,中继服务没有我的密钥就无法解析数据内容,很大程度上实现了数据传输的安全。所以我选择 Tailscale 作为我的异地组网实现方案。

最直接的想法是SSH。Windows 自带 OpenSSH Server,只是默认没开,在“可选功能”里装上、把服务设成自动启动,Mac 上 ssh 用户名@IP 就能连进来。真正好用的是 VSCode 的 Remote-SSH:连上之后编辑器界面跑在 Mac 上,代码和终端跑在游戏本上,改文件、跑命令、看日志,用起来和本地几乎没差别——因为它传的是编辑器的增量操作,不是屏幕画面,网络延迟高一点也基本感觉不到。
但这里有个问题:我日常要用的东西是散的。SSH 在一个终端窗口里,SFTP 在另一个软件里,性能监测、屏幕串流又是别的窗口。我想要实现“打开一个页面,所有东西都在里面”的体验。
于是就有了自己写的网页终端。后端用 node-pty 启动真正的命令行进程,前端用 xterm.js 渲染,和 VS Code 用的是同一套东西。

同时我做了页面的缓存,浏览器上切换到不同栏目不会清空命令行中的内容;把网页关掉、笔记本合上、人回寝室,命令还在主机上运行,会话会被接回去,之前的输出也能回看,使用体验上相对比较舒适。
很多场景下需要看网页的渲染效果、调图形界面等,还需要一个远程桌面。
为什么没选主流的现成方案?向日葵和 ToDesk 的免费版限速明显,画质压缩得很厉害;Parsec 其实很不错,配置也简单,但它免费版在打洞失败时会走自己的中继服务器,出境之后延迟就不太可控了,同时它的免费版锁定 60 帧且不方便自定义串流分辨率。
最后选的是 Sunshine + Moonlight。这个组合基本是自建串流的事实标准:Moonlight 是当年 NVIDIA GameStream 协议的第三方客户端,GameStream 被 NVIDIA 关停之后,社区用 Sunshine 补上了主机端,现在 Moonlight 官网本身就推荐用 Sunshine 做主机。
延迟方面我实测下来:两台电脑在同一个 Wi-Fi 下时往返 12ms 左右;把 Air 带到外面走公网直连,延迟上到 50 到 110ms,这很大程度上取决于网络环境。但不论在什么场景下,它确实达到了我可接受的延迟,甚至用来远程打一些单机游戏也还体验不错。

这里有个挺反直觉的结论:延迟的大头往往不在编解码,而在路径。同样一套 Moonlight,走局域网和走公网能差好几倍;而当初 Parsec 给我的体感更慢,事后想想大概率不是它编码慢,是它当时没打洞成功、走了中继。所以“哪个软件延迟低”这种问题其实没什么意义,得看那天的链路是怎么走的。
使用 Moonlight + Sunshine 的方案需要解决无头状态下的屏幕捕获问题。Sunshine 捕获的是当前活动显示输出上的画面;然而笔记本合上盖子之后,系统会认为没有显示器了,Sunshine 抓不到画面,连上去就是黑的。这是 Windows 的显示模型决定的,一旦没有活动的显示输出,就没有可捕获的桌面。
比较简单的解决办法是装一个虚拟显示器驱动。我用的是 MTT 的 Virtual Display Driver,装完系统里会多出一块“假屏幕”,再在它的配置里补一条 2560×1600(默认列表里只有 2560×1440,是 16:9 的,由于我内屏是 16:10,不加这一条会导致屏幕有黑边)。如果觉得驱动方案不够稳定,也可以去网上花几块钱买个 HDMI 欺骗器,使用时插上后让电脑认为你还有一块活动的屏幕;优点是使用方便且连接稳定,但是一旦买来就不方便修改屏幕分辨率等参数,需要取舍。
虚拟屏幕装好后,还得在 Sunshine 的配置里指定虚拟屏的输出名(output_name,值是虚拟屏的设备 GUID),并把显示设备的处理方式设成“串流时只保留这一块、断开后恢复”,对应目录里C:\Program Files\Sunshine\config\sunshine.conf的配置就是:
output_name = {虚拟屏幕的GUID}
dd_configuration_option = ensure_only_display
dd_config_revert_on_disconnect = enabled做完这些,它在串流开始时会自动把虚拟屏切成唯一显示器,退出后自动切回来;平时用两块真实屏幕,一连 Moonlight 物理屏就熄掉,不用每次手动去显示设置里切,使用体验不赖。

等我用了这套方案后又查到了有个叫 Apollo 的项目,是 Sunshine 的分支,把虚拟显示器直接做了进去,还能按客户端自动匹配分辨率和刷新率,串流时创建、断开时移除。直接使用这个方案的话确实更方便,但是它目前主要靠一位开发者维护,更新节奏比上游的 Sunshine 慢不少,维护上存在一定的不确定性。
说是性能监测,其实主要是用来确定主机它还活着没有。人在寝室,想知道实验室那台机器是在跑编译、还是在摸鱼、还是已经热到降频、还是压根断网了。这些信息如果每次都要 SSH 进去敲命令看,效率太低。

获取这些数据很常规:处理器和内存用 systeminformation 这个库,显卡直接调 nvidia-smi 解析输出,网络状态读 tailscale status --json,服务在不在跑用 Get-Service 查。没有引入 Prometheus 那一套,因为就一台机器,没必要。
为了方便在连不上时自查,我加了个巡检页面,一共 17 项检查,覆盖组网、SSH、Linux 子系统、电源策略和系统本身。这 17 项检查每一项都对应我真实踩过的坑。比如“只接受密钥登录”检查的是 sshd 配置里是不是还开着密码登录;“合上盖子不会断线”检查的是天选电源方案里的合盖动作——这个设置在我的机器上被华硕的电源方案藏了起来,powercfg 根本查不到,只能按系统内部的编号直接写。

对于每一个检测项,点开都能看到检测时读到的原始值,能自动修的都提供了一键修复的按钮,改之前会备份,改坏了能回滚。
对于遥控重启和关机我做了二次确认,让 rearwindow 先申请一个十秒内有效的确认令牌,再带着它执行;主要是为了避免误点后导致主机和终端断联。至于开机,Wake-on-LAN 在虚拟网络里是行不通的,魔术包只能在同一个局域网内广播,跨不过 Tailscale;这也是一个技术上的遗憾,想随时用得着,如果想要使用 rearwindow 就必须时刻保证电脑不处于睡眠或者关机状态。
以下是我开发时遇到的一些问题:
SSH 公钥的顺序。 如果先关掉密码登录,再去配公钥,就会把自己锁在外面。所以要先确保公钥已经配置再关闭密码登录。
Windows 的 SSH 对公钥文件很挑剔。 C:\ProgramData\ssh\administrators_authorized_keys 这个文件,权限放宽了不行(它会直接拒绝使用),带 BOM 也不行(整个文件被当成非法行,而用记事本保存默认就会加 BOM)。Windows 11 22H2 之后的后版本记事本在另存为中可以在编码菜单选择无 BOM 保存,或者直接使用 VS Code 保存;也可以使用PS脚本一行命令完成[IO.File]::WriteAllText("out.txt", (Get-Content "in.txt" -Raw), (New-Object Text.UTF8Encoding($false))) 。
Tailscale 的“在线”标记不可信。 在实验早期,有时候 rearwindow 界面显示“未连接”,但我却正用着它访问这个页面。原因是 Tailscale 是否在线的标记只统计控制服务器有没有收到心跳,心跳断了但数据面还通的情况很常见,比如笔记本刚唤醒、控制连接抖一下;后来的版本便不采用这个标记,用一个探测包实测网络情况。
合盖之后串流是黑的。 这条前面讲过,Sunshine + Moonlight 的方案依赖真实的屏幕活动,没有活动的显示输出就无法串流。
虚拟显示器会抢鼠标。 装完 VDD 之后桌面变成三块屏,虚拟屏贴在主屏右边,鼠标往右一划就跑进那块看不见的屏幕里了。解决办法是把虚拟屏幕拖到角落或者不常用的位置,让虚拟屏和主屏之间留出空隙;Windows 不允许光标跨过没有屏幕的空白区域,鼠标就过不去了。
后端服务不定时崩溃。 这个问题表现是服务莫名其妙退出,日志中提示多行 AttachConsole failed。这是因为服务是以无控制台的方式后台运行的,而 node-pty 在销毁终端会话时会 fork 一个子进程去枚举控制台里的进程,那个子进程必然失败,连带把主进程带下去;改成直接用 taskkill 结束整棵进程树之后就没事了。
整体来说,这是一个娱乐强于实用的尝试,但是这个过程中对各个功能进行社区成熟方案的调查以及功能整合时遇到的问题经验,对于想要实现类似效果的读者或有帮助;若对各位的自己的实现有所裨益,不胜荣幸。
另外值得一说的是,作为一个从两三岁就看着家长使用 WindowXP 到自己从小学开始有了自己的 Windows8.1 系统的电脑,再到上了大学后在 Windows11 上娱乐、学习、开发,现在突然开始深入使用 MacOS 生态,它带来了不少惊喜,之后有机会还会写一些我在 Mac 上工作娱乐的心得以及一些我在 Mac 平台上折腾的东西。敬请期待!O~O