运维日常里最容易被忽略的一段开销,不在"连上去之后",而在"连上去之前":找 IP、确认端口是不是默认、想起账号是本地还是域、密码存在哪、连上要不要先解锁。单次十几秒,一天几十次就是整块时间;更麻烦的是这套知识只存在某个人脑子里,休假就断档。
一年前我决定把它变成一次调用:从一张记录上双击,直接落到桌面。落地过程中真正的难点不是界面,而是四种客户端根本不接受同一套参数------有的能带密码,有的只能靠键盘敲;有的用 IP 定位,有的用 ID。这篇把四种客户端的启动方式、凭据传递路径、以及"哪一层该做什么"整理出来,都是能直接抄的部分。

一、四个客户端的启动方式,差别比想象中大一倍
先把事实摆出来。下表是各客户端"从命令行拉起并直达目标"的最小能力集:
| 客户端 | 可执行文件 | 直达目标的参数 | 能否带凭据 | 定位方式 |
|---|---|---|---|---|
| Radmin Viewer 3.x | radmin.exe |
/connect:IP:PORT |
❌ 不行,只能交互输入 | IP + 端口 |
| RealVNC Viewer | vncviewer.exe |
host::port(或 -passwd 文件) |
⚠️ 只能传加密密码文件 | IP + 端口(或显示号) |
| MsTSC(Windows 自带 RDP) | mstsc.exe |
/v:host:port,或直接打开 .rdp |
⚠️ 靠 cmdkey 预置凭据 |
IP(+ 端口) |
| RustDesk | rustdesk.exe |
--id <ID> --password <pwd> |
✅ 较新版本支持 | ID / 别名 |
四个结论,写代码前先记住:
1. 只有 RustDesk 用 ID 定位。 其余三个都是 IP。这一条决定了你不能用"一张 IP 表"套所有客户端------RustDesk 那批机器必须额外维护 ID 与别名的映射,而且 ID 会因重装、换配置而重新生成。
2. 只有 RustDesk 能在命令行里明文给密码。 这在自动化上很方便,也意味着调用日志不能记完整命令行,否则密码会进日志。这一点比功能本身重要。
3. RealVNC 的 -passwd 要的是加密后的文件 ,不是明文密码。它的格式和 vncpasswd 生成的一致,可以自己按算法生成,也可以让用户预先生成一次。别试图直接传明文。
4. RDP 是唯一"参数最干净但凭据最绕"的。 /v: 只能带地址,凭据走 cmdkey /generic:TERMSRV/<host> /user:<u> /pass:<p> 预置到当前用户凭据库,再 mstsc 就会静默使用。副作用是凭据会留在本机凭据管理器里,用完要 cmdkey /delete。
bat
:: RDP:预置凭据后直连(注意用完删除)
cmdkey /generic:TERMSRV/10.10.1.21 /user:corp\ops /pass:********
mstsc /v:10.10.1.21:3389
cmdkey /delete:TERMSRV/10.10.1.21
二、把参数抽成一层:一条记录 = 一次连接的完整输入
既然参数形态各异,正确的做法不是给每个客户端写一套界面,而是中间加一层与客户端无关的记录,客户端只当执行器。
一条记录我最后收敛成这些字段,多一个都嫌多:
json
{
"no": 12,
"name": "生产-DB01",
"client": "MsTSC",
"host": "10.10.1.21",
"port": 3389,
"user": "corp\\ops",
"secret": "<DPAPI 密文>",
"domain": "corp",
"colorDepth": 24,
"onConnect": "unlock",
"fullscreen": true
}
几个字段的取舍,都是踩过才定下来的:
name 不用 IP。 IP 会变,名字不该变。我统一写成「环境-用途-编号」,生产-DB01、测试-APP01,扫一眼就知道是哪台。
client 只允许固定几个枚举值。 一旦允许「自定义任意程序」,适配器就没法收敛------最后又退化成「从记录里启动一个 exe」,等于没做。枚举的代价是不能接小众工具,收益是每条分支都能测到。
port 必须显式存,不要依赖默认值推测。 VNC 有 5900/5901/5902 多个显示号;Radmin 在内网常被改成非 4899;RDP 改过端口就必须写下来,否则下次连不上。
onConnect 单独成一列,别塞进备注。 同一台机器在不同场景下需求相反:日常运维希望连上就到桌面,合规机器要求保持锁定。我用了三个取值:unlock / lockThenUnlock / 空(不处理)。
secret 只存密文,且加密与当前用户绑定。 Windows 上用 DPAPI(CryptProtectData)按用户加密,密文落盘。好处是数据文件被拷到别的机器也解不开;代价是换机要重新录入。
有了这层,适配器就只剩一件事:把记录翻译成命令行 + 处理登录后的状态。

csharp
interface IRemoteClient
{
string Name { get; }
// 返回启动参数;凭据由实现自行决定如何传递
ProcessStartInfo Build(Record rec, string plainSecret);
// 连接后是否需要额外动作(等窗口、发密码、解锁)
void AfterLaunch(Record rec, string plainSecret, Process proc);
}
每个实现里最脏的部分是 AfterLaunch,也就是下一节。
三、凭据怎么送进去:四种路径的成本对照
| 路径 | 适用 | 稳定性 | 主要风险 |
|---|---|---|---|
| 命令行参数 | RustDesk | 高 | 进程列表/日志泄露明文 |
| 加密密码文件 | RealVNC | 高 | 文件本身要管理好 |
| 系统凭据库(cmdkey) | RDP | 高 | 凭据残留,需清理 |
| 模拟键盘输入 | Radmin、以及所有没有上述通道的场景 | 低 | 焦点跑偏,密码敲进别处 |
模拟键盘是最后手段,不是首选。 SendInput 的按键会进入当前前台窗口------如果连接瞬间弹了个更新提示、或者远端还在加载桌面导致焦点切换,密码就敲进了错误的地方。缓解办法是轮询等目标窗口出现并置前台再发键,但跨进程窗口句柄不可靠,只能靠进程名 + 窗口标题匹配,匹配不上就必须退化成人工资。
还有一个容易忽略的点:模拟键盘无法可靠区分"密码框还没出来"和"密码框出来了但焦点在别处"。我的做法是发键前先校验一次当前焦点控件是否是可编辑控件,不满足就重试,重试三次仍失败则放弃自动填充、把窗口留给人工------宁可少做一步,也不要把密码打进错的窗口。
四、连上之后:两类"锁屏"不能混为一谈
这是全网教程最容易写错的地方。远端看到的"锁屏"其实有两种,处理路径完全不同:
A. RDP 的登录界面。 RDP 连上去是新建会话,看到的是 Windows 登录框,本质是"还没登录"。这一步不存在"解锁",只有"登录"------凭据对了就直接进桌面,凭据错了就是反复失败。所以 RDP 的自动化重点是凭据传递,不是解锁。
B. 镜像式远控看到的控制台锁屏。 Radmin / VNC 是镜像当前控制台画面,如果控制台已登录但被锁,你看到的是锁屏界面,本质是"已登录、待解锁"。这一种才涉及"解锁动作"。
而用户态进程没法凭空解锁一个已锁的会话------解除锁屏需要向 Winlogon 桌面发送安全序列(Ctrl+Alt+Del 语义),这在设计上就是只允许 SYSTEM 或具备相应特权的上下文来做。所以现实中的三条路:
- 要求以管理员身份运行 ,并在需要时借助
SendSAS触发安全序列(依赖系统策略与服务状态,不是所有环境都放行); - 装一个以 SYSTEM 运行的小服务代发安全序列(绿色版做不到,安装版可以);
- 放弃自动解锁,只做"连上并聚焦窗口",把解锁交给人工。
前两条各有落地成本,第三条最诚实。我的判断是:这是一种"要么明确支持、要么明确说不"的能力,最不该做的是"有时能成有时不能"------运维会因此不再信任整个工具。
五、批量探测:TCP 通不等于机器活着
几十台机器要标"能不能连",最省事的做法是对端口做 TCP connect,超时算不可达。两个必须提前想清楚的问题:
1. 防火墙丢包和关机,在 TCP 层面长得一模一样。 前者是"在线但不可达",后者是真离线。所以结果只能叫「端口可达 / 不可达」,不能叫「在线 / 离线」------一旦界面写成"在线",用户就会拿它当监控用,然后在不该报警的时候报警。
2. 并发不是越大越好。 高延迟链路上几十个并发会互相拖慢,超时阈值被挤爆,误判率反而上升。我最后用 20-30 并发、单连接超时 3-5 秒,100 台机器十几秒扫完,误判在可接受范围。
csharp
// 只测可达性,不测"存活"
using var client = new TcpClient();
var task = client.ConnectAsync(host, port);
if (await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(4))) != task)
return Reachability.Timeout; // 不代表离线
return task.IsCompletedSuccessfully
? Reachability.Reachable
: Reachability.Refused; // 端口关了,但主机大概率活着
注意 Refused 和 Timeout 要分开:前者说明主机回了 RST(活着,只是端口没开),后者才是"说不清"。
六、六个实测出来的坑
1. MsTSC 的 /w: /h: 不是所有版本都认。 指定远程分辨率在部分系统上被忽略并回退默认。更稳的是把分辨率写进 .rdp 文件,再 mstsc <文件路径>。
2. RealVNC 一个服务端可以挂多个显示号。 记录里只写 IP、端口按 5900 猜,遇到 5901/5902 的环境必然连错,而且错得很安静------连上的是另一个会话。
3. RustDesk 的 ID 会变。 重装、换配置文件后 ID 可能重新生成。记录必须支持"ID 失效后重新绑定",否则一条记录直接作废。
4. 密码框的焦点位置跟客户端强相关。 同一台机器,Radmin 的密码框和 VNC 的密码框位置、Tab 顺序完全不同。"发完密码直接回车"这种写法跨客户端一定崩,必须每个适配器单独校准。
5. 中文路径和空格会打掉命令行参数。 客户端装在 C:\Program Files\... 下,拼接命令行时少一层引号,参数就被拆成两截;路径含中文时,部分客户端按 ANSI 解析会直接报"文件不存在"。这类问题在英文环境测不出来,一到客户现场就复现。
6. 探测结果的措辞会决定用户怎么用它。 写"在线/离线",用户当监控;写"可达/不可达",用户就知道它只是连接前的预检。同一个功能,两个词,两种误用方式。
七、什么时候不值得这么做
反过来说更省时间:
- 只连两三台机器:用客户端自带的连接簿最快,多一层记录纯属负担。
- 只用 RDP 一种:Windows 自带的远程桌面连接管理器够用,不必造轮子。
- 环境强制堡垒机 + 强审计:走堡垒机,任何客户端侧的自动填充都是合规风险。
- 团队多人共用同一批机器:带凭据的记录是给"一个人管一批"设计的;多人协作应该把凭据收进统一密码库,工具里只存引用。
还有一条必须自己认得清:自动登录本质上降低了安全性。密码从"人脑记忆"变成"本机可解密的密文",风险面变了。我的做法是允许按记录单独关闭这个能力,涉密机器一律走人工输入------把选择权留在记录上,而不是留在程序里。
八、最后
这套东西没有多高明,无非是把参数抽出来、按客户端做适配。真正花时间的是四家客户端各自的脾气:谁的参数管用、谁的密码框在哪、谁的会话怎么解锁。这些没有文档,只能一台一台试。
我自己按这个结构写了一版 Windows 客户端在用,界面就是第一张截图那样:一张记录表,双击落到桌面。写它不是为了替代谁------这四种客户端各自都很好,只是放在一起用的时候,缺一个把它们串起来的那一层。
写这篇的动机很简单:如果你也在"几种远控混着用、机器几十台、每天连很多次"的环境里,先把参数整理成一层数据这件事本身就有收益------哪怕最后是用 Excel 加几个批处理脚本实现的。工具是次要的,把"连接前那几步"从人脑搬到文件里,才是关键的那一步。