☰
OpenNOW核心协议参考:Qt与Rust核心间带版本JSON RPC通信完整指南
2026/10/4 8:05:29 网站建设 项目流程

OpenNOW核心协议参考:Qt与Rust核心间带版本JSON RPC通信完整指南

【免费下载链接】OpenNOWCustom GeForce Now Client Named OpenNOW项目地址: https://gitcode.com/gh_mirrors/op/OpenNOW

OpenNOW 是一款社区开发的开源 GeForce NOW 云游戏客户端。它的核心协议是一套运行在 Qt(C++)外壳与 Rust 应用核心之间的带版本 JSON RPC 通信机制:以换行分隔的 UTF-8 JSON 消息走标准输入/输出,靠core.hello握手完成协议版本协商,再用 request / response / event / cancel 四种消息类型驱动登录、商店、云游戏会话等全部功能。本文带你从零读懂这套协议的设计与实现。

一眼看懂架构:Qt 画界面,Rust 干重活

OpenNOW 桌面版被拆成两个独立进程:

角色技术负责内容
Shell(外壳)Qt Quick / C++所有窗口、场景图、视频项、输入
Core(核心)Rust 独立进程账号认证、商店目录、云匹配会话、设置持久化

两者的全部对话都由 Qt 侧的 CoreClient 类封装——它负责拉起 Rust 核心进程、写入消息、解析 stdout、管理超时与重试。

同一个应用还支持手柄驱动的"主机模式"布局,切换布局不需要换进程,UI 与核心的 JSON RPC 通信方式完全一致。

传输层:标准流上的换行 JSON

协议刻意选择了最简单的载体:

  • 通道:核心进程继承Qt 的 stdin/stdout,一行一条 JSON 消息;stderr 单独保留给脱敏诊断日志
  • 硬限制:单条消息上限1 MiB;未知类型、格式错误或超长的消息会直接让核心连接转入failed,而不是把外壳留在模糊状态
  • 常量即契约:Qt 侧把这三个边界写成了类常量,一眼可见:

CoreClient.h 中定义了CurrentProtocolVersion = 1、MaximumLineBytes = 1024 * 1024、MaximumQueuedEvents = 512;Rust 侧在 main.rs 中声明PROTOCOL_VERSION: i64 = 1。两端版本号必须在握手时互相验证。

握手与版本协商:core.hello 必须第一个到

核心协议的第一条规则:外壳的第一条请求永远是core.hello,在此之前不发任何产品请求。

{"type":"request","id":"1","method":"core.hello","params":{"protocolVersion":1,"shell":"qt","shellVersion":"0.5.4"}}

Qt 在核心进程started信号触发后立即发出这条握手请求,超时设为5 秒(见 CoreClient.cpp);Rust 侧的分派逻辑则直接比对版本号,不一致就返回incompatible_protocol错误(见 main.rs):

{"type":"response","id":"1","ok":true,"result":{"protocolVersion":1,"coreVersion":"1.0.0","capabilities":["settings","gfn.deviceAuth","catalog.storePages.v1","nativeStreamer.v7","osCredentialStore","mediaLibrary", "..."]}}

这个响应携带的capabilities 数组就是核心的"能力清单"——商店分页、本地目录、原生流媒体 v7、系统凭据库等都逐一列出,外壳可据此决定功能开关。

握手的失败面被设计得很窄但很明确:版本不匹配、5 秒超时、进程退出、数据非法,任何一种都会把传输层转入failed并附上不含凭据的诊断信息。

四种消息类型:request / response / event / cancel

握手的消息信封就是全部协议:

{"type":"request","id":"42","method":"settings.get","params":{}} {"type":"response","id":"42","ok":true,"result":{"settings":{}}} {"type":"response","id":"42","ok":false,"error":{"code":"invalid_setting","message":"…"}} {"type":"event","name":"settings.changed","payload":{"key":"fps","value":120}} {"type":"cancel","id":"42"}

各类型职责:

  1. request:外壳发起调用,id在核心进程内唯一;每个请求都有有界截止期(100 ms 到 5 分钟)
  2. response:按id对应回请求;ok:true带result,ok:false带error{code, message}
  3. event:核心主动推送(如settings.changed、updater.changed),通过最多512 条的队列批量投递,队列满时丢弃最旧事件并累加诊断计数器
  4. cancel:超时或被用户取消时,外壳发送取消;已取消的请求会抑制其后续响应

可靠性机制:busy 重试、指数退避与协作式取消

协议对"核心太忙"有一整套优雅降级策略:

  • 核心最多接受8 个 RPC worker(其中 4 个留给catalog.*、artwork.*、network.regions.ping等后台任务),超量请求直接返回busy,重复的活动 ID 同样被拒绝
  • Qt 客户端收到busy后不报错,而是保留同一 ID 和载荷,100 ms 后重试,延迟翻倍至多到 1 秒,且不延长原始截止期(退避逻辑见 CoreClient.cpp)
  • 取消只在协作检查点生效:商店翻页、区域测速循环会停下来,但已经在跑的 HTTP/DNS/TCP 不会被硬中断,其原有超时继续生效

这套机制保证 UI 永远不会因"核心忙"而卡死,也不会因盲目重试放大负载。

方法目录:所有功能都是 RPC

协议 1 版本已实现的方法覆盖全部产品能力,按前缀分组一目了然(完整清单见 docs/core-protocol.md):

前缀代表方法用途
auth.*auth.device.start/auth.accounts.switch设备码登录、多账号管理
catalog.*catalog.store.list/catalog.store.local商店游标分页、本地目录搜索
session.*session.create/session.claim/session.poll云游戏会话创建、断线重连
streamer.*streamer.prepare/streamer.start原生流媒体准备与启动
updater.*updater.check/updater.install应用自更新
settings.*/diagnostics.*settings.get/diagnostics.export设置读写与脱敏诊断导出

失败策略:清晰的 failed 状态与自动重启

核心协议刻意不做"将错就错":坏 JSON、未知消息类型、超尺寸行都会触发protocolFailure,一次性失败所有挂起请求;进程意外退出则走failAll+ 自动重启路径,重启预算耗尽前会持续尝试拉起核心。Qt 侧由此形成一个简单的状态机:

stopped → starting → handshaking → ready ↘ 协议/进程异常 ↗ failed(随后自动重试启动)

对普通用户来说,这意味着核心闪退后应用会自愈;对开发者来说,这意味着任何协议破坏都会快速失败,而不是留下一个半死不活的连接。

延伸阅读:3 个必读源码文件

想深入核心协议的细节,建议按顺序阅读:

  • 协议规范(行为契约的权威来源):docs/core-protocol.md
  • Qt 客户端实现(进程管理、握手、超时、退避):opennow-qt/src/core/CoreClient.cpp
  • Rust 核心分派(版本校验、方法路由、capabilities):native/opennow-core/src/main.rs

小结:OpenNOW 的核心协议用"换行 JSON + 版本号握手 + 有界队列 + busy 退避"这套朴素而严谨的组合,在 Qt 外壳与 Rust 核心之间建立了一条可测试、可恢复、可演进的 RPC 通道——协议版本 1 的所有扩展(商店分页、本地目录、更新器)都以附加能力方式声明,不破坏握手契约,这正是它值得借鉴的地方。

【免费下载链接】OpenNOWCustom GeForce Now Client Named OpenNOW项目地址: https://gitcode.com/gh_mirrors/op/OpenNOW

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询