xmz7891 8880e7033d 修「经中转几次后永久哑掉」——「按一下没反应」的真因
== 现象 ==

用户报:
  * 局域网能搜到电脑,IP/端口/配对码都对,连上后**没有任何反应**
  * 走中转「已配对」,但点按钮没反应

局域网那一半已经修过(preferLan)。这次抓到的是**经中转**那一半,
而且比预想的严重得多。

== 证据链(插桩复现出来的)==

被控端日志里直接抛了未捕获异常,线程当场死:

    Exception in thread client-relay:
      server.py:407  session.send(reply)
      relay.py:166   self._link.send_to_client(data)
      relay.py:357   self._post("/v1/agent/send", ...)
      relay.py:766   raise RelayError(f"请求失败: {last_error}")
    lanremote.relay.RelayError: 请求失败: Idle

之后的状态:
  * client-relay 线程没了 —— 电脑再也不往手表回任何东西
  * relay-link 线程还活着、还在长轮询 —— 中转那边看着「设备在线」
  * **手表看到的就是「连上了,但按一下毫无反应」**

写入仓库的测试 _tests/test_relay_local.py 一直是这个形状:
前面几项 PASS、跑到经中转发命令就卡死。之前没人深挖。

== 根因 1:一条 HTTP/1.1 连接被两个线程同时用 ==

_post() 里锁只包住「取连接」那一段:

    with self._http_lock:
        connection = self._http      # 锁到这里就放了
    try:
        connection.request("POST", ...)   # 锁外发请求

于是两个线程会同时用同一条 connection:
  * relay-link 线程跑 /v1/agent/poll —— 长轮询,一条请求挂 180 秒
  * client-relay 线程同时 POST /v1/agent/send

HTTP/1.1 一条连接同一时刻只能有一个在途请求,第二个线程一进来
就撞上 http.client 状态机,抛 CannotSendRequest("Idle")。

_post 的「重试一次」也救不了:它 _drop_http() 之后,另一个线程
手里还攥着那条旧连接对象在发,照样坏。

修法:
  * **发命令用独立连接**(_post 加 dedicated 参数)。
    长轮询那条常年被占着,发命令不该去挤它,各走各的。
  * 两条连接各自一把锁**锁住整个「发请求 + 读响应」周期**,
    不再只锁取连接那一下。
  * 普通请求若发现缓存连接正忙(长轮询在途),临时另开一条用完即关。
  * _drop_http(dedicated=) 区分清哪条;finally 里一定归还占用
    (否则那条连接会被永久标记为忙)。

== 根因 2:回包失败会打死整个会话线程 ==

server.py 的 _client_loop 里:

    if reply is not None:
        session.send(reply)      # <- 不在任何 try 里
    ...
    except OSError:              # <- 外层只捕获 OSError
        pass

而中转链路上 send 抛的是 RelayError,直接冒出去把线程打死。
**死一次就永久哑**,代价太大。

修法:session.send() 单独兜住所有异常,失败就断开这个会话
(让手表重连),绝不让线程消失。外层也补一个兜底 except。

== 修复效果 ==

    修复前:pair OK -> ping OK -> ping 卡死 -> 线程崩溃,永久哑
    修复后:pair 21ms -> ping 82ms -> ping 4ms -> ping 6ms,零异常

_tests/test_relay_local.py:**之前完全卡死 -> 现在 21/21 全过**

这个测试之前一直是红的,说明「35 个阶段的回归全绿」这件事
本身就不成立过 —— 某个阶段卡住时看起来像环境问题而不是缺陷。

== 同批修掉验证阶段找到的其它问题 ==

1. **TTL 清理 + 关掉接入开关 = 设备永久锁死**(严重)
   register() 用 `device_id in self.devices` 判断老设备,但超时清理
   把设备从 devices 和 codes 都删了。电脑关机过夜(必然 > 6 小时)
   第二天开机就会被自己的开关挡住,而且**重启也救不回来**
   (清 codes 时没落盘,重启后又当孤儿清一遍,自我维系)。
   管理员在页面上还看不到这台设备,不知道该放开谁。
   修法:状态文件里单独存 knownDevices 设备历史,超时清理绝不动它;
   register 改查这份历史;清理后补 _save();delete_device 才真正忘记。

2. **管理页 JS 语法错误 -> 所有按钮失效**(严重)
   admin.py 里 ADMIN_JS 是普通三引号字符串,我在 Python 源码里写的
   `\n` 被变成真实换行,拼进 JS 就是字符串里的裸换行 ->
   整段 <script> 不执行 -> 改码/删除/批量删除/全选全变幽灵按键。
   元凶是我加改码功能那次提交。改成 `\\n`。

3. **--relay / --relay-status 崩溃**(严重)
   RelayLink.start(url) 的 url 是必填参数,我把默认地址改空白时
   run_agent.py 两处调用没跟着改。一处被 try 吞成「测试失败:missing
   1 required positional argument」(用户以为地址填错了),
   一处直接报栈。两处都补上 url。

4. 非法 deviceId(控制字符 / 路径分隔符)现在被拒(白名单:
   字母数字 + - _ . :)

5. enable_relay() 第二次调用不再谎报成功(加 RelayLink.running 属性,
   返回值反映真实运行状态;不再用「对象建过没有」判断)

6. snapshot() 清孤儿码后补 _save(),磁盘和内存不再脱节

== 局域网全面优化(task-2,另一个智能体完成)==

4 个真问题:
  * **网段判断写死 /24** —— 在 /16 网络上 10.8.1.5 和 10.8.2.5
    被判成不同网络,明明能直连却去走中转还费流量。
    改成按网卡真实掩码比较。
  * **广播只发一个包** —— 丢一个就整轮搜不到。改成 5 个时间点重发。
  * **只探一个网段** —— 多网卡手表搜不到另一网段的电脑。改成全探。
  * **走中转之后再也回不到局域网** —— 出门再回家永远挂中转。
    新增定期回切(先便宜地 TCP ping,通了才动真格,
    不破坏正在工作的中转连接)。

实测:局域网握手 2ms、50/50 = 100%、中位 0ms。
(跨网络那半之前拿不到数字,就是被上面那个线程崩溃挡住的 ——
  现已修复。)

顺带:instance.py 单实例锁端口加 YKXMZ_LOCK_PORT 覆盖(默认不变)。
用户自己那台被控端正跑在这台机器上占着 8767/TCP + 8766/UDP,
是这次排查里好几个「测试怎么都不通」的根因。

== 服务端一键部署脚本(task-1,另一个智能体完成)==

relay/deploy.sh + relay/deploy-tune.sh,随服务端 zip 发布:
  * 自动检查并安装 cloudflared
  * 部署程序、装 4 个 systemd 服务、网络调优、配隧道、自检打公网地址
  * **不传 --hostname 且没有 TUNNEL_TOKEN 时明确提示要填**,
    不会静默用一个写死的域名(用户明确说过这个中转站是他自己用的,
    不对外开放,绝不能把私人域名烧成默认值)
  * 重跑 = 升级,已有数据/配对码/管理密码/凭据都不动

顺带修了发布包一直缺 site_shots/ 的老 bug
(package_remote.py 用 iterdir()+is_file() 收文件,目录进不去,
新机器装完官网截图全是 404)。

== 默认中转地址改成空白 ==

用户要求:软件默认不带任何中转地址,由用户自己填。
四处硬编码全部清空(server.py / MainActivity.java / run_agent.py /
relay.py 的注释和文档示例),全仓已无非注释的硬编码地址。

== 文档同步(task-5,另一个智能体完成)==

README / BEFORE-INSTALL / AFTER-INSTALL / 打包模板:
  * 新增「软件里不带任何中转地址」告示,讲清跨网络要自己部署
  * 修正 Android 版本(写的是 9.0/API28,实际 API 24 / Android 7.0)
  * 删掉「地址已经内置好了」(现在是错的)
  * AGENT_README 原来把作者自己的中转地址当成「内置地址」发给用户,已重写
  * RELAY_README 原来写「两条隧道同时跑会互相争抢」,
    和真机/CHANGELOG 矛盾;按实测改成「两种协议同时在跑(故意的)」
2026-10-07 17:59:39 +08:00
2026-10-07 15:44:49 +08:00
2026-10-06 23:08:07 +08:00

ykxmz · 教室电脑手表控制

老师在讲台上抬一下手腕,就能把教室里那台电脑管了。

放课件、放音频、投个图、看看有没有在跑无关程序、下课关机 —— 不用走回讲台去点鼠标。

名字来自"控制手表"的拼音首字母,读作"控制拇指"也行。


先看这个:你是哪一类人

这份文档不短,但不用全看。挑自己那一节就行。

你是谁 从这里开始
老师 三步装好 → 上课时怎么用
电教 / 学校信息中心 一个学校怎么铺开 → 批量管教室
开发者 自己编译 → 通信协议
普通用户(家里自己用) 三步装好 → 连不上怎么办

这东西是干嘛的

一句话:用手表控制电脑。

为什么用手表而不是手机?因为老师上课时手上是空的,手机在兜里,掏手机的动作比抬手大得多。手表抬一下就行,还不打断讲课。

它不只能放 PPT。教室里那些事它基本都管得着:

  • 课件全屏投到教室大屏
  • 放听力音频(电脑音箱比手表响多了)
  • 看看学生机是不是在打游戏,直接把游戏进程结了
  • 课堂专注模式,下课一键恢复
  • 下课关机、锁屏
  • 喊一声"上课了"让电脑播音(TTS)

电脑那边不用装 Python,一个 EXE 双击就跑,绿色免安装,不留注册表。


三件套

一套完整的部署是三个程序,装在三个地方:

装在哪 是什么 文件
教室电脑(Windows) 被控端 ykxmz-setup-1.8.2.exe
小服务器(随身 WiFi / 云主机 / 树莓派) 中转站 ykxmz-server-1.8.2.zip
手表(安卓) 控制端 ykxmz-watch-1.8.2.apk

只在一个教室用? 那第三件(中转站)可以先不装,手表和电脑连同一个 WiFi 就能直连。

要在办公室管教室? 那必须自己部署一台中转站(见 跨网络)。

⚠️ 软件里不带任何中转地址

这一点先讲清楚,免得白折腾:

这个软件默认是"空的" —— 没有任何内置的中转服务器地址, 也没有官方服务器可以用,作者不对外提供中转服务。

  • 同一个 WiFi 下用(教室里):不需要中转,装完就能连,什么地址都不用填。

  • 要跨网络用(办公室管教室):你得自己部署一台中转站。 服务端压缩包里有一键脚本,一条命令装好:

    bash deploy.sh
    

    装完它会打印一个属于你自己的公网地址,把它填到电脑和手表上。 详见 装中转站。

为什么不做一个"填个公共地址就能用"?因为中转站的带宽是实打实的成本, 谁用谁出;而且中转站能看到经过它的字节流, 自己搭一台,数据才真正只在你自己手里。


三步装好

第一步:电脑上装被控端

双击 ykxmz-setup-1.8.2.exe,按提示装完(默认装到 C:\Program Files\ykxmz)。

第一次运行会弹出窗口,填两样:

  • 6 位数字 —— 自己定,好记就行,比如 789113
  • 两个勾:设置开机自启、放行防火墙(放行那个要过一次管理员确认)

点确定,屏幕会黑闪一下(在加防火墙规则),然后窗口消失,程序转到后台跑。

窗口关了程序还在跑,这是故意的 —— 上课时讲台上不该有个窗口杵着。 想再打开窗口,再双击一次程序图标就行,不会起第二个进程。 想彻底退出,窗口上点「退出」。

不想用安装包的话,ykxmz-agent-1.8.2.zip 是绿色版: 解压出来直接跑 ykxmz.exe,整个文件夹拷走就能换台机器用,不写注册表。

第二步:手表上装 App

把 ykxmz-watch-1.8.2.apk 传到手表装上。手表可能提示"未知来源",去设置里允许一下。

打开 App,首页有四个入口:

┌──────────────────────────┐
│      控制手表             │
│  和电脑连同一个 WiFi,不用输 IP │
├──────────────────────────┤
│   中转设置(跨网络用)      │  ← 办公室管教室才需要
│   局域网连接(同一个 WiFi)  │  ← 教室里用这个
│   输入配对码连接            │  ← 跨网络连的时候用
├──────────────────────────┤
│  服务器:还没测   检查服务器 │
└──────────────────────────┘
│  已配对的电脑(点一下直连)  │
│  DESKTOP-XXXX             │
└──────────────────────────┘

教室里(手表和电脑连同一个 WiFi):点「局域网连接」→「自动搜索电脑」→ 点搜到的电脑 → 输第一步那 6 位数字。

不用输 IP。 App 往局域网广播一个包,电脑收到会自报家门,所以电脑上装了几块虚拟网卡、IP 是多少都不用管。

第三步:以后不用再输了

配对一次之后令牌存在手表里,下次打开 App 自动就连上,数字都不用再输。

多台电脑就多配对几次,列表里点哪个连哪个。长按删除配对。


上课时怎么用

连上之后手表上是一排标签页,左右滑动切换。下面挑上课最常用的讲。

教室

这一页是给上课准备的。

按钮 干什么
课堂专注模式 改 hosts,把常见游戏站点指到本地。学生机打开就是空白页
关闭游戏窗口 扫一遍常见游戏进程名,找到就结束掉
看有哪些限制 列出当前开了哪些限制,别自己忘了
一键恢复全部 把这节课开的所有限制撤掉,下课必点

屏蔽是改 hosts 文件,不是装驱动,所以:

  • 学生自己删掉 hosts 里那几行就绕过去了 —— 它挡的是"随手点开",不是防作弊
  • 换个游戏站就得重新加,这个列表是写死的常见站点
  • 重启电脑有的会保留、有的不会,取决于你们学校的还原卡

屏幕

  • 实时屏幕镜像 —— 手表上看教室里那台电脑在显示什么。默认 4 fps,在手表上太小看不清,主要是"看一眼有没有人乱动"
  • 一键截屏 —— 存到手表相册

⚠️ 屏幕只要被看,电脑右上角就有一条红色提示条,关不掉。 这是刻意做的:教室里那台电脑是公家的,谁在看必须让人知道。 电脑前面的人随时可以点提示条上的「停止共享」把你掐断。

媒体 / 音频

放课件自带的音频,或者直接放电脑上的音乐。音量、静音、上一曲下一曲都在这一页。

放音到电脑走的是系统自带的播放器,不会弹出一个播放器窗口盖住课件。

投影

投图到电脑 —— 手表上选一张图,全屏显示在教室大屏上。点一下或者按 Esc 关掉。

考试时贴个"考试中,请勿喧哗"的图,比在黑板写快。

系统

关机、重启、锁屏、睡眠,都有二次确认。定时关机可以设"30 分钟后关",下课忘了也不怕。

另外这一页最上面有三个按钮:

  • 回退上一步 —— 刚才手滑点错了,撤销
  • 回退全部 —— 把能撤的都撤了
  • 看能回退什么 —— 列出还能撤哪些

能撤的是各种开关和画面。关机和删文件这类撤不了,也不会出现在列表里,免得你以为按了就能回来。

其他几页

  • 文件 —— 往电脑传文件、从电脑拿文件(课件来回倒腾用)
  • 语音 —— 让电脑念一段字。"上课了,请回到座位"这种
  • 剪贴板 —— 手表上的字发到电脑剪贴板,或者把电脑复制的字拿到手表
  • 通知 —— 在电脑上弹个提示
  • 效率 —— 待办清单
  • 娱乐 —— 掷骰子、抽奖、抛硬币(课堂互动随机点名能用)

跨网络:在办公室管教室

教室的电脑和你在的办公室不是一个网,直连不可能。这时候要装中转站。

原理说白了就是:两边都主动连到那台小服务器上,服务器按配对码把两条线接起来。

   手表  ──┐                    ┌── 教室电脑
           ├── 中转站(公网) ──┤
   办公室 ──┘                    └── 你手机

中转站只是水管子,它不认识里面流的是什么。 配对、令牌校验、权限判断全在电脑那边做。所以就算中转机被人拿走了,也进不了你的电脑。

装中转站

中转站就是一台能上外网的 Linux 小机器(随身 WiFi、树莓派、云主机都行, Debian 12 / Ubuntu 实测可用,512 MB 内存就够)。

把 ykxmz-server-1.8.2.zip 解压到那台机器上,然后一条命令:

bash deploy.sh

它会自己做完这些,不用你一步步敲:

  1. 装 cloudflared(已经装了就用现成的)
  2. 把中转程序装到 /root/lanremote-relay/
  3. 网络调优(keepalive、MSS、MTU、开机加速)
  4. 注册 systemd 服务,设成开机自启
  5. 配好 Cloudflare 隧道
  6. 启动、自检,然后把公网地址打印出来

装完会打印一个属于你自己的公网地址,长这样:

https://relay.你的域名

记住这个地址,电脑和手表都要填它。忘了就在那台机器上跑 lanremote-url,或者浏览器打开 http://<小机器的IP>:8080/url 查。

三种常见情形

# ① 机器上已经有隧道凭据(/root/.cloudflared/*.json)
bash deploy.sh

# ② Cloudflare 给了一个隧道 Token(Zero Trust 后台复制的那串)
TUNNEL_TOKEN=eyJhIjoi... bash deploy.sh

# ③ 换域名 / 只装中转、不要隧道
bash deploy.sh --hostname relay.你的域名
bash deploy.sh --skip-tunnel

看全部参数:bash deploy.sh --help

用环境变量而不是 --token:写进命令行的 Token 会留在 shell 历史里。

重跑一遍 = 升级

deploy.sh 是幂等的:已经装过的机器再跑一次就是升级。 程序文件会被覆盖成新的,而设备数据、配对码、隧道凭据都不会被冲掉。

需要准备什么

  • 一台能上外网的 Linux 小机器(Debian 12 / Ubuntu,512 MB 内存够)
  • 一个 Cloudflare 账号 + 一个域名(隧道要用)
  • 不需要装 Python 第三方包 —— 服务端是纯标准库,不装任何依赖

告诉电脑中转地址

在电脑上(cmd 或 PowerShell):

ykxmz.exe --relay "192.168.1.50:8080;https://relay.你的域名"

两个地址用分号隔开,按顺序试,第一个通的就用。这样在教室里走局域网(快、稳),出了学校自动切隧道。

如果只在跨网络的场景用,也可以只填一个域名:

ykxmz.exe --relay https://relay.你的域名

看看当前用的是哪个地址:

ykxmz.exe --relay-status

告诉手表中转地址

手表首页点「中转设置」,填上面同一个地址。

然后回家首页点「输入配对码连接」,填电脑上那 6 位数字。

中转站的公网地址打开是什么

打开就是一个普通的官网页面 —— 介绍、截图、三个文件的下载按钮, 最新版的安装包挂在上面,点一下就能下。

安装包和截图这类静态文件由 Cloudflare 的边缘节点直接发送, 所以下载速度和那台小机器的带宽无关(实测比走隧道回源快几十倍)。

想看运行状态、排查问题、重启服务,看服务端压缩包里的 README-server.txt —— 那份文档是给运维的人看的,里面写了日常命令。


一个学校怎么铺开

如果是一个学校几十间教室,上面那套手工装会很累。这一节给电教。

被控端

ykxmz.exe 是绿色单文件,整个文件夹拷过去就能用。批量装有两种办法:

办法一:机房同传 / 还原卡

把文件夹放进母盘镜像,开机自启那条在程序里勾上就行。注意 data/ 目录里存着这台机器的配对码和令牌,母盘里别带上别的机器的 data/,否则一堆机器共用一个码。

办法二:脚本推

# 把文件夹拷到目标机,然后静默设置配对码和自启
Copy-Item -Recurse "\\服务器\share\ykxmz" "C:\ykxmz"
Start-Process "C:\ykxmz\ykxmz.exe" -ArgumentList `
    '--code 789113', '--relay 192.168.1.50:8080;https://relay.你的域名', '--auto-start', '--silent'

--silent 不弹窗口,适合批量。静默模式下配对码一定要用参数指定,不然每台机器随机生成一个,你也不知道是多少。

配一次码,服务器就记住了

这是这套东西比较省心的一点:一台电脑第一次设的配对码,服务器会记住。

以后同一台电脑再报别的码(重装、误操作、母盘没清干净),服务器会自动改回原来那个。所以你不会因为手抖把一台机器的码搞乱,手表永远认得出它。

反过来,一个码不能同时属于两台电脑,服务器会拒绝后来那个。这也是为什么母盘里不能带别人的 data/。

网络规划

  • 中转站放在学校出口能上外网的地方,带宽不用大,它是控制通道不是视频通道
  • 走局域网时是手表直连电脑,完全不经过中转站,所以一个班几十只手表也不挤服务器
  • 教室之间如果是隔离的 VLAN,那手表和电脑得在同一个 VLAN 里,或者干脆都走中转

权限怎么收

被控端窗口上有几个权限开关,随时关,关了远程立刻执行不了:

  • 允许自定义命令
  • 允许电源操作
  • 允许屏幕镜像 / 截屏

给班主任的电脑可以把"自定义命令"和"电源操作"关掉,只留日常那几个按钮。


连不上怎么办

按这个顺序排查,九成问题在第二步。

第一步:程序在跑吗

电脑任务管理器里找 ykxmz.exe。没有就双击 EXE 启动。

第二步:防火墙放行了吗

被控端窗口上如果显示橙色提示"防火墙未放行",点旁边的一键修复,过一次管理员确认。

或者用目录里的 工具箱.bat → 选 2。

这一条是最常见的坑。很多学校的电脑装了第三方安全软件,它会把新程序的入站连接全挡了。 一键修复会加一条程序级的放行规则(不是端口级),这个才管用。

第三步:还是不行就跑网络自检

工具箱.bat → 选 3,然后到手表上点「检查服务器」。

看电脑窗口:

  • 出现 >>> 收到 TCP 连接 <<< → 包到电脑了,问题在电脑本机(程序没跑 / 端口被占)
  • 一个包都没收到 → 包没到电脑,八成是路由器开了**「AP 隔离」**(也叫客户端隔离),去路由器后台关掉

手表上会显示具体原因

App 把错误翻译成人话了,看到哪句就照做:

手表上显示 什么意思
证书校验失败 手表系统太旧,或者网络被人劫持
证书时间不对 把手表的日期时间调成自动获取,这个很常见
连接超时 网络慢,或者被防火墙挡了
电脑拒绝连接 电脑上 ykxmz 没开
域名解析不了 手表没联网,检查 WiFi
连不上中转 中转站挂了,或者地址填错了

首页那个「检查服务器」能测出到中转的延迟,快/一般/慢会用不同颜色标出来。


安全

教室里那台电脑是公家的东西,所以这套工具的安全设计偏"让人知道"而不是"藏起来"。

每次连接都要配对。 电脑上生成 6 位数字,只在窗口上显示,手表输对了才连得上。

配对之后发令牌。 32 位随机串存在手表上,之后靠它登录,不用每次输数字。

没通过握手,一条命令都不执行。 服务端直接拒。

屏幕被看,电脑上一定有提示条。 关不掉,没有开关。电脑前面的人随时能掐断。

权限开关实时生效。 窗口上关掉哪一项,远程立刻做不了那一项。

不写注册表。 数据全在程序目录下的 data/ 里,拔了硬盘拷走就能换台机器跑。

不在后台偷偷上传。 局域网模式下完全不联外网。跨网络模式只连你自己那台中转站。

这个工具不藏自己。 没有"任务管理器里看不见",没有"免杀对抗",没有伪装成系统进程。 被控端全程可见、可随时断开、权限可随时关掉。 因为管的是教室的电脑,藏起来只会让老师不放心。


自己编译

给开发者。三端都是零第三方依赖,不用 npm、不用 Gradle 拉一堆东西。

被控端(Windows)

Python 3.11 + tkinter + Pillow。打包用 PyInstaller。

windows\build_exe.ps1
# 产物:dist_exe\ykxmz\ykxmz.exe

控制端(安卓手表)

纯 Java,没有 Gradle,直接命令行编:

android\build_apk.ps1
# 流程:aapt2 → javac → d8 → zipalign → apksigner
# 产物:dist\ykxmz-watch-1.8.2.apk

最低支持 Android 7.0(API 24),targetSdk 34。

服务器端

Python 3 标准库,没有第三方包。

bash relay/deploy.sh

deploy.sh 是一键部署(装服务 + 调优 + 配隧道 + 自检); 只想调网络参数的话是 relay/deploy-tune.sh。 老的 relay/install.sh(匿名快速隧道)仍在包里,只为兼容以前装过的机器。

跑测试

_tests\verify_all.ps1

会跑编码审计、协议回归、打包验证、Java 编译、端到端,最后打一张汇总表。

打包三个成品

python _build\package_remote.py

出三个文件:ykxmz-agent-*.zip、ykxmz-server-*.zip、ykxmz-watch-*.apk。


通信协议

给要改代码或者做二次开发的人。

传输是 TCP 上的换行分隔 UTF-8 JSON,一行一个消息。

{"cmd": "handshake", "code": "789113", "deviceId": "..."}
{"ok": true, "token": "32位随机串", "serverName": "DESKTOP-XXXX"}

二进制数据(图片、文件、音频)走 Base64 分块传输,单次会话上限 256 MB。

跨网络时同样的消息套在 HTTP 长轮询里(单次挂 60 秒, 有数据立刻返回、不空等),中转站按配对码把两端字节对接,它不解析内容。

跨网络这条链路上还有几个实测调出来的设计,改代码前值得知道:

  • 隧道两种协议同时跑(QUIC + HTTP/2,合计 10 条连接)。 两种协议各有各的毛病 —— QUIC 尾部延迟好但建连容易超时, HTTP/2 建连可靠但丢包时容易被队头阻塞卡住。 同时跑就是互相兜底:一种犯病,另一种还在服务。
  • 长轮询 60 秒。隧道每次请求要多花 1~2 秒,这个开销是按次数收的; 拉到 60 秒把请求数降到 1/4,撞上丢包窗口的概率也降到 1/4。 命令响应速度不变(有数据立刻返回,不是等满 60 秒)。
  • 偶发失败不当场判离线。4G 链路上本来就有百分之几的偶发抖动、 一两秒自己恢复;连续失败 3 次才算真的离线。
  • 静态资源交给 Cloudflare 边缘缓存。下载走边缘节点, 不用穿隧道回那台小机器,快几十倍也更省流量。

Java 和 Python 两端都实现了同一套协议,_tests/java/ 下有互相打通的测试。


常见问题

问:手表能连多台电脑吗? 能。配对多台,列表里点哪个连哪个。

问:一台电脑能被几只手表连? 可以多只,屏幕镜像会显示"几台设备正在观看"。教室场景一般一只就够。

问:换台电脑要重新配对吗? 要。配对码跟着电脑走,不是跟着手表走。

问:电脑重装系统了,配对码会变吗? 不会。只要 data/ 目录还在,服务器就认原来那个码。母盘部署时注意别把 data/ 一起发出去。

问:走中转会不会很慢? 实测局域网 3 毫秒左右;跨网络要绕到中转站再回来,约 1~2 秒(取决于你那台小机器的线路)。所以同一个 WiFi 一定走局域网,程序会优先选它。

问:必须自己搭中转站吗?能用作者的服务器吗? 必须自己搭。软件不内置任何中转地址,作者也不对外提供中转服务。 跨网络的话,服务端 zip 里有 deploy.sh,一条命令装好(见 装中转站)。 只在同一个 WiFi 下用的话,压根不需要中转站。

问:中转站会不会看到我的数据? 它转发的是加密前的字节流,所以技术上能。这正是"自己搭一台"的理由 —— 搭在你自己手上,数据就只经过你自己的机器。

问:下载安装包为什么比以前快这么多? 最新版把静态文件交给 Cloudflare 的边缘节点缓存了。原来每次下载都要穿过隧道回到那台小机器上取,实测只有几 KB/s,15 MB 的安装包根本下不动;现在从离你最近的边缘节点直接发,实测快了四十多倍,也不占小机器的流量。

问:能在手机上用吗? 协议是通的,但界面是按手表的小屏幕设计的(大按钮、两列、可滚动),手机上会显得按钮特别大。要做手机版得重写布局。

问:收费吗? 看 LICENSE.txt。里面有使用红线,装之前看一眼。


出问题了

先看 连不上怎么办。

还不行的话,把这几样发过来,能定位得快很多:

  1. 手表上显示的那句错误(App 现在是中文的,直接念出来就行)
  2. 电脑上 工具箱.bat → 选 5 的日志内容
  3. 跨网络的话,把你那台中转站的公网地址发过来(不用给任何密码)

作者:小麦子 许可见 LICENSE.txt

S
Description
ykxmz 主仓库(自动从 Gitee 同步)
Readme AGPL-3.0
67 MiB
Languages
Python 71.2%
Shell 14.4%
Java 7.3%
HTML 5.4%
PowerShell 1.3%
Other 0.4%