一台 Mac 上 ToDesk 突然连不上了。远程连过来,要么黑屏,要么提示「被控设备采集画面异常」,要么干脆停在锁屏界面点不动。系统设置里屏幕录制的开关明明是开着的。
排查下来是两个问题叠加:ToDesk 的服务进程重复了,以及本地 IPC 通道断了。这篇把定位过程和修复脚本完整记下来。
先看结构:ToDesk 其实是三个进程
很多人以为 ToDesk 就是一个 App。实际上它在 macOS 上分成三个角色,权限和职责完全不同。搞清楚这个分工,后面的现象就都能解释了。
关键在于最上面那个 ToDesk_Service:它必须以 root 身份运行在 system 域。因为要采集登录窗口、锁屏界面这些系统层画面,还要注入键鼠事件——这些都是普通用户权限拿不到的。
出问题时的两个症状
症状一:两个 Service 并存。一个 root 的,一个你自己用户名下的。用户态那个抢到了会话,但它没有 root 权限,采不到锁屏层,于是画面是黑的。root 那个反而空转。
症状二:设备代码空白。客户端界面显示「正在重新连接本地服务」,设备代码和临时密码都不显示。这说明本地 IPC 断了。
四层排查顺序
从外到内,每一层都有明确的判据。跳步容易误判——比如系统设置里权限开关是开的,就以为权限没问题,其实问题在进程层。
第一层:网络层
如果你的机器开着代理软件,ToDesk 的连接可能被劫持。症状是一直显示「正在连接」,但浏览器上网正常。
networksetup -getwebproxy "Wi-Fi"
# Enabled: Yes 就是被劫持
networksetup -setwebproxystate "Wi-Fi" off
networksetup -setsecurewebproxystate "Wi-Fi" off
ToDesk 走国内直连就够了,不需要全局代理模式。关掉系统代理后确认 Enabled: No。
第二层:进程层(最常见)
这是这次的真正原因。一条命令就能判断:
ps -axo pid,user,command | grep ToDesk_Service
正常情况只输出一行,且 user 是 root。如果出现第二行、user 是你自己的用户名,就是冲突了。
为什么会这样?之前在别处执行过停止 ToDesk 的操作,把 system 域的 root 服务 bootout 掉了。之后再打开客户端,它自己补建了一个用户态的服务顶上——两个进程抢同一个会话,谁都干不成活。
第三层:IPC 层
客户端和服务之间通过本地端口 35600 通信。查一下这个端口的状态:
lsof -nP -iTCP:35600
CLOSED 表示断开,客户端就会显示「正在重新连接本地服务」;ESTABLISHED 才是正常。
第四层:账号层
如果左侧显示「立即登录」,设备代码和临时密码都不出现,那就是登录态丢了。这一层没法用命令行查——ToDesk 的登录凭据存在系统钥匙串里,不在本地配置文件里(配置文件只有十几个键,全是窗口位置和更新设置)。看客户端界面最准。
修复:为什么普通重启治不好
这点值得单独说。发现双 Service 后,直接 pkill 再打开客户端是没用的——客户端会立刻补建一个用户态服务,回到原样。
也试过 launchctl kickstart 重启那个 root 服务,直接报错:
Could not kickstart service "com.youqu.todesk.service": 1: Operation not permitted
system 域的服务需要管理员权限。正确的顺序是先 bootout 掉 system 域的旧服务,再重新 bootstrap:
# 1. 杀光所有 ToDesk 进程
pkill -f "ToDesk.app/Contents/MacOS"
pkill -f "ToDesk_XPCHelper"
sleep 3
# 2. 清掉用户域的作业,防止它复活
launchctl bootout gui/$(id -u)/com.youqu.todesk.service
launchctl bootout gui/$(id -u)/com.youqu.todesk.desktop
# 3. 重新装载 root 服务(需要管理员密码)
sudo launchctl bootout system/com.youqu.todesk.service
sudo launchctl bootstrap system /Library/LaunchDaemons/com.youqu.todesk.service.plist
sudo launchctl kickstart -k system/com.youqu.todesk.service
# 4. 最后才启动客户端
open -a ToDesk
做完之后 ps 里应该只剩一个 root 的 Service,lsof -nP -iTCP:35600 的状态从 CLOSED 变成 ESTABLISHED,客户端界面会显示设备代码和临时密码。
排查时容易被误导的几个点
| 看起来像原因 | 实际情况 |
|---|---|
| 系统设置 → 屏幕录制里 ToDesk 开关是开的 | 权限确实有,但用户态 Service 用不上。要看进程属主,不是看开关。 |
| 配置文件里找不到隐私屏设置 | 隐私屏、设备锁定这类设置存在账号级服务端,本地配置文件本来就没有。 |
| 读 TCC.db 查权限 | SIP 保护,普通用户读不了。截图看系统设置页面更可靠。 |
| 重启客户端 | 只重启客户端不碰 root 服务,双 Service 会立刻回到原样。 |
还有一种情况:客户端每隔几分钟就重启
上面讲的是「连不上」。但如果现象是ToDesk 老是重连——客户端进程的 PID 每隔几分钟就换一个,那多半是另一套问题。
第一个判据很简单,把两个进程的运行时长放在一起看:
ps -axo pid,user,etime,command | grep "ToDesk.app/Contents/MacOS"
如果 ToDesk_Service 已经跑了十几个小时,而客户端 ToDesk 的 etime 只有几分钟——说明客户端在反复重启,而不是网络在抖。
最常见的原因:另一个远程控制工具在抢采集通道
macOS 的屏幕采集最终都走 ScreenCaptureKit 这一条通道。第二个远程控制工具即使完全空闲、CPU 显示 0%,也可能占着采集句柄不放。
ToDesk 拿到一个已经失效的缓冲区地址,就会直接崩在 CVDisplayLink 线程上——崩溃类型是 SIGSEGV,KERN_INVALID_ADDRESS。表现就是客户端被反复拉起。
查一下有没有第二个远程工具在跑:
ps -axo pid,%cpu,command | grep -iE "UURemote|TeamViewer|AnyDesk|SunLogin|RustDesk"
不能只看 CPU。这类工具空闲时 CPU 是 0%,但它照样持有采集权限,还持有 PreventUserIdleSystemSleep 防睡眠断言——两个工具同时开着,还会一起阻止显示器休眠。
处理办法就是留一个。要卸载那个不用的话,注意它有 root LaunchDaemon 守护,光删 App 是不够的:
# 先停守护,否则删完立刻被拉起
sudo launchctl bootout system/com.netease.uuremote.daemon
launchctl bootout gui/$(id -u)/com.netease.uuremote.agent
sudo rm -f /Library/LaunchDaemons/com.netease.uuremote.daemon.plist \
/Library/LaunchAgents/com.netease.uuremote.agent.plist
sudo rm -rf /Applications/UURemote.app
# 删了 App 之后进程仍驻留在内存里,要补一刀
pkill -9 -f UURemote
另一个诱因:锁屏与解锁
ToDesk 在屏幕锁定和解锁的转换时点会重建 ToDesk_Session。如果这一步正好撞上别的采集工具占着通道,重建就失败。
查锁屏历史:
tail -30 ~/Library/Logs/ToDesk/ToDesk_XPCHelper_502_$(date +%Y%m%d).log
日志里的 client connection invalidated 如果密集重复出现,就是重启循环的直接证据。
还有网络环境这个变量
手机热点(172.20.10.x 网段)下,ToDesk 的 P2P 打洞比家宽困难得多,重连频率本身就高。判据:
route -n get default | grep gateway
# 172.20.10.1 → iPhone 个人热点
# 192.168.x.1 → 普通路由器
所以排查顺序是:先排除进程冲突(这个能彻底解决),再看是不是网络环境(这个只能等换网)。
小结
遇到 macOS 上远程控制连不上、黑屏、采集异常,按这个顺序查:网络代理 → Service 数量与属主 → 35600 端口状态 → 登录态。
其中第二层(Service 属主必须是 root)最容易忽略,因为界面上的权限开关看起来完全正常。一条 ps 命令就能确认。