前面文章分别说了移动云电脑公众版DD WIN11专业版
和
用EasyTier 自建共享节点组网远控云电脑
但是使用下来,云电脑一直关机,不能随时链接的云电脑意义在哪里呢?于是
研究了一下保活协议
目前只研究了公众版H3C(新华三)云电脑桌面协议,ZTE没有机器没有研究
在研究之前,我查找了github一些信息,发现有几个项目说通过desktopUptime控制面查询。它能证明 Token 有效、桌面存在、API 可访问,但不能证明客户端与云桌面之间存在 Display/SPICE 数据面连接。因此它不能作为“云电脑不会因无人连接而关机”的充分条件。
于是乎,开始自己动手
最初的保活方式是调用官方 H3C Session 程序。它确实能够建立桌面会话,但也会同时启动官方二进制、IPC 服务和远程桌面相关能力,效果接近真正打开一次移动云电脑
这种方式存在几个问题:
- 资源占用高:会启动额外进程,并进入完整 Display 会话流程。
- 部署复杂:依赖 Windows EXE、DLL、运行目录和厂商 SDK。
- 不适合 Docker:容器中不应为了保活再运行完整 GUI 或 Windows 会话程序。
- 故障难解释:只知道“官方程序断开了”,不清楚失败发生在认证、分配、上线还是心跳阶段。
因此目标被重新定义为:
不渲染桌面,只完成服务端认为“终端已认证、云电脑已上线、客户端仍然存活”所需要的最小协议流程。
用“酒店入住”理解整个协议
可以把云电脑连接过程理解成酒店入住:
| 协议动作 | 酒店类比 | 作用 |
|---|---|---|
| 移动云 HTTP 登录 | 在平台取得订单和身份证明 | 获取 AccessToken、设备和云电脑信息 |
| 获取桌面连接参数 | 查看酒店地址 | 得到 H3C Broker 地址、桌面池、机器标识等 |
WhoAreYou | 确认前台身份 | 判断目标端口确实是 H3C Broker |
queryDeviceConfigInfo | 询问入住规则 | 获取服务端设备和认证配置 |
RegisterDevice | 登记终端 | 服务端记录当前终端身份 |
CheckPassword | 核验入住凭据 | 确认用户和桌面凭据 |
ApplyForVm | 申请房间 | 从桌面池申请或定位云电脑 |
OperateForVm | 办理正式入住 | 让目标云电脑进入连接状态 |
ReportSuccess | 告知前台入住成功 | 建立服务端认可的在线状态 |
Heartbeat | 定期报平安 | 防止服务端认为客户端已经离线 |
最终有效的路线是:
官方成功会话
→ 收集 IPC、Verbose 日志和网络证据
→ 找出 Broker 地址、TLS 参数和 RPC 名称
→ 加载 protobuf descriptor
→ 用 Python 重放只读 RPC
→ 分阶段还原认证状态机
→ 对照官方成功字段修正 Apply/Operate
→ 验证 ReportSuccess 和 Heartbeat
→ 加入重连、退避、脱敏报告和 Docker第一层:业务控制面证据
先通过移动云 HTTP API 获得:
- 当前账号是否登录成功。
- 目标云电脑是否存在。
- 桌面实例、桌面池、机器 UUID 等信息。
customLoginParams或同类字段中的 Broker 地址。
如果控制面信息已经错误,后面的 H3C 协议不会成功。
第二层:网络证据
按顺序验证:
- 本机到 Broker 的路由是否存在。
- TCP 端口是否可连接。
- TLS 握手是否成功。
- gRPC
WhoAreYou是否返回预期服务名称。
TCP 成功只代表端口可达,不代表 TLS、gRPC 和业务认证成功。
本项目研究中曾出现物理机能够访问 Broker,而云电脑内部系统不能访问 Broker 的情况。这说明保活程序必须运行在具备正确网络路由的位置;Docker 也必须继承或配置相同的网络可达性。
第三层:协议结构证据
通过官方 protobuf descriptor 可以确认:
- RPC 路径。
- 请求和响应消息类型。
- 字段名称。
- 字段的数据类型和嵌套关系。
descriptor 能告诉我们“报文长什么样”,但不能单独告诉我们“业务上应该填什么值”。业务值仍需通过官方成功日志和对照实验确认。
第四层:成功会话证据
成功 trace 的价值最高。对每个 RPC 建议记录:
| 项目 | 说明 |
|---|---|
| RPC 名称 | 例如ApplyForVm |
| 请求时间 | 用于确定调用顺序 |
| 请求字段 | 必须脱敏,只保留结构和值的类型 |
| 响应字段 | 重点观察下一阶段需要复用的值 |
| 服务端状态 | 成功、gRPC 错误或业务错误码 |
| 结论 | 哪些字段是固定值,哪些来自前一步响应 |
最终确认的 H3C 状态机
当前生产代码使用的状态机为:
flowchart LR
A["WhoAreYou"] --> B["queryDeviceConfigInfo"]
B --> C["RegisterDevice"]
C --> D["CheckPassword"]
D --> E["ApplyForVm"]
E --> F["OperateForVm"]
F --> G["ReportSuccess"]
G --> H["Unary Heartbeat"]
H -->|"成功"| H
H -->|"失败"| I["关闭旧通道"]
I --> J["指数退避"]
J --> A注意事项:
终端身份必须前后一致
服务端并不是只在 RegisterDevice 阶段检查设备。后续 CheckPassword、ApplyForVm、OperateForVm 和心跳仍会复用终端身份。
当前实现统一维护:
local_ipmacdevice_iddevice_namecomputer_typeprotocol_typeclient_type
官方成功行为表明,当前 H3C 链路中 device_id 应与规范化后的 MAC 保持一致,protocol_type 使用 vdp。如果只修改注册阶段,而后续 RPC 又换回另一组身份,常见结果就是“未注册”或“无效请求”。
1. Apply 和 Operate 不能混为一谈
ApplyForVm 是申请或定位云电脑,OperateForVm 才是正式执行连接操作。
已确认的关键原则:
| 阶段 | 关键规则 |
|---|---|
ApplyForVm | 请求中不提前发送最终vm_id |
ApplyForVm | id 使用桌面池标识,pool_name 使用桌面池名称 |
ApplyForVm | port="0",start_domain=1,token_flag=false |
| Apply 响应 | 从响应中取得真正的vm_id、主机 IP 或密码信息 |
OperateForVm | 复用 Apply 返回的vm_id |
OperateForVm | op_type=3,force_flag=2 |
OperateForVm | port="0",start_domain=1,refresh_flag="1" |
ReportSuccess | 使用上线后 VM 地址,并使用refresh_flag="0" |
Heartbeat | 复用已认证上下文,携带vm_id,refresh_flag="0" |
这些字段不是通过枚举猜出来的,而是通过官方成功 trace 与失败请求对比得到的。
2. 典型错误码如何理解
2.1 80004: For input string
通常表示服务端尝试把某字段解析为数字,但客户端发送了文字。例如把产品名、客户端名或协议名放进了期望数字的字段。
处理方法:
- 先查 descriptor 中该字段的 protobuf 类型。
- 再对照官方请求中同一字段的实际值。
- 不要仅根据字段英文名称猜含义。
2.2 80002: 无效请求
这是最常见也最宽泛的业务错误。它可能表示:
- RPC 顺序错误。
- 终端身份前后不一致。
- 字段类型正确但业务组合错误。
- 把后续阶段的值提前放进当前请求。
- 缺少前一步服务端生成的上下文。
正确做法是退回到最后一个成功步骤,对照成功 trace,而不是同时改五六个参数。
2.3 80068: 设备未注册
说明后续请求使用的身份没有被服务端识别为刚才注册的设备。重点检查:
- 注册和认证阶段的
device_id是否相同。 - MAC、IP、设备类型是否发生变化。
- 是否误把
register_id当成device_id。 - 是否在不同主机或不同网络环境中切换了身份。
2.4 80619: 当前设备已作为 VDI 类型设备使用
说明服务端已经保存了当前终端的注册类型。此时不应继续大量重复注册。应确认官方客户端使用的设备类型,必要时由有权限的管理人员解除旧注册。
2.5 gRPC UNKNOWN 或空错误
优先检查:
- TLS CA 文件是否正确。
- gRPC authority 是否与证书匹配。
- descriptor 是否与服务端版本匹配。
- metadata,例如语言字段是否正确。
- RPC 超时和网络中断。
空错误不等于“随便换业务字段”。
