Files
ykxmz/_tests/java/JavaRelayTest.java
xmz7891 b62ac865ea ykxmz 1.5.0:修复手表端 TLS 证书校验 + MTU 修复网络稳定性
被控端
- 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%)
- 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败)
- 每分钟回头找局域网,自动切回快 400 倍的路径
- 画面心跳帧:静止时省流量,客户端仍知道流是活的
- 锁屏时给出可操作的提示

手表端
- 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException
- 连接错误翻译成人话(证书/时间/解析/超时分别提示)
- 经中转连上后用服务器报的地址刷新已配对记录
- 新增局域网连接页面、服务器延迟检测

服务器端
- 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502)
- 网络调优脚本 net-tune.sh(开机自动应用)
- 管理页:批量删除、网络体检、下载区
- 隧道改 HTTP/2 + 双连接

文档
- 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
2026-10-06 19:01:05 +08:00

148 lines
6.5 KiB
Java

import com.lanremote.watch.Json;
import com.lanremote.watch.RelayTransport;
import com.lanremote.watch.RemoteClient;
import java.util.List;
import java.util.Map;
/**
* 手表端经中转服务器的联调测试。
*
* 这条链路才是真正要交付的:
*
* Java 手表 --HTTPS 长轮询--> 中转服务器 <--HTTPS-- Python 被控端
*
* 用法: java JavaRelayTest <中转地址> <配对码>
*/
public class JavaRelayTest {
private static int passed = 0;
private static int failed = 0;
private static void check(String name, boolean condition) {
check(name, condition, "");
}
private static void check(String name, boolean condition, String detail) {
if (condition) {
passed++;
System.out.println(" [PASS] " + name);
} else {
failed++;
System.out.println(" [FAIL] " + name + " " + detail);
}
}
public static void main(String[] args) throws Exception {
String url = args.length > 0 ? args[0] : "http://127.0.0.1:8090";
String code = args.length > 1 ? args[1] : "135790";
System.out.println("中转地址: " + url + " 配对码: " + code);
System.out.println();
// ---------------- 1. 传输层自己 ----------------
System.out.println("一、RelayTransport 构造");
RelayTransport probe = new RelayTransport(url, code, "中转测试手表");
check("地址归一化去掉了末尾斜杠",
probe.getBaseUrl().equals(url.replaceAll("/+$", "")),
probe.getBaseUrl());
check("初始状态是未连接", !probe.isClosed());
// ---------------- 2. 经中转握手 ----------------
System.out.println();
System.out.println("二、经中转连被控端(握手)");
RemoteClient client = new RemoteClient();
client.setDevice("watch-relay-test", "中转测试手表");
Map<String, Object> reply =
client.connectViaRelay(url, code, null, new Silent(), 30000);
check("握手返回 ok", Json.bool(reply, "ok", false), String.valueOf(reply));
check("拿到设备令牌", Json.str(reply, "token", "").length() == 32,
Json.str(reply, "token", ""));
check("拿到被控端名字",
Json.str(reply, "serverName", "").length() > 0,
Json.str(reply, "serverName", ""));
check("客户端认为已连接", client.isConnected());
check("传输方式是中转", "中转".equals(client.transportName()),
client.transportName());
System.out.println(" 被控端: " + Json.str(reply, "serverName", "?"));
String token = Json.str(reply, "token", "");
// ---------------- 3. 命令往返 ----------------
System.out.println();
System.out.println("三、经中转发命令");
long started = System.currentTimeMillis();
Map<String, Object> pong = client.call(Json.obj("cmd", "info"), 30000);
long cost = System.currentTimeMillis() - started;
check("info 返回 ok", Json.bool(pong, "ok", false), String.valueOf(pong));
System.out.println(" 往返 " + cost + " ms");
Map<String, Object> features =
client.call(Json.obj("cmd", "feature_list"), 30000);
List<Object> items = Json.list(features, "features");
check("功能列表经中转拿到", Json.bool(features, "ok", false)
&& items.size() > 100, items.size() + " 个");
Map<String, Object> ran = client.call(Json.obj(
"cmd", "feature_run", "name", "world_clock",
"params", Json.obj()), 30000);
check("执行功能经中转成功", Json.bool(ran, "ok", false),
String.valueOf(ran));
check("功能有真实输出",
Json.str(ran, "note", "").contains("北京"),
Json.str(ran, "note", ""));
// ---------------- 4. 鉴权仍然生效 ----------------
System.out.println();
System.out.println("四、中转只是管道,鉴权仍由被控端做");
RemoteClient bad = new RemoteClient();
bad.setDevice("watch-bad", "坏手表");
Map<String, Object> refused = bad.connectViaRelay(
url, "000000", null, new Silent(), 30000);
check("错的配对码连不上", !Json.bool(refused, "ok", false),
String.valueOf(refused));
check("给出了原因", Json.str(refused, "error", "").length() > 0,
Json.str(refused, "error", ""));
System.out.println(" 原因: " + Json.str(refused, "error", "?"));
bad.close();
// ---------------- 5. 令牌免配对重连 ----------------
System.out.println();
System.out.println("五、用已保存的令牌重连(下次打开不用再输码)");
// 先断开第一条连接 —— 这才是实际用法(手表关了再打开)
client.close();
check("断开后 isConnected 为 false", !client.isConnected());
Thread.sleep(1500);
RemoteClient again = new RemoteClient();
again.setDevice("watch-relay-test", "中转测试手表");
Map<String, Object> authed =
again.connectViaRelay(url, code, token, new Silent(), 30000);
check("令牌登录成功", Json.bool(authed, "ok", false),
String.valueOf(authed));
check("新连接能发命令",
Json.bool(again.call(Json.obj("cmd", "ping"), 30000), "ok", false));
again.close();
// ---------------- 6. 断开干净 ----------------
System.out.println();
System.out.println("六、断开处理");
client.close();
check("关闭后 isConnected 为 false", !client.isConnected());
System.out.println();
System.out.println("======================================================");
System.out.println("通过 " + passed + " 项,失败 " + failed + " 项");
System.exit(failed == 0 ? 0 : 1);
}
/** 事件回调:这个测试不关心事件。 */
static class Silent implements RemoteClient.Listener {
@Override
public void onEvent(String event, Map<String, Object> message) {
}
@Override
public void onDisconnected(String reason) {
System.out.println(" (断开: " + reason + ")");
}
}
}