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 + 双连接

文档
- 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
This commit is contained in:
xmz7891
2026-10-06 19:01:05 +08:00
commit b62ac865ea
3195 changed files with 203292 additions and 0 deletions
+231
View File
@@ -0,0 +1,231 @@
# -*- coding: utf-8 -*-
"""让 RemoteClient 支持中转(跨网络)。
改动很小,因为把"怎么传字节"抽成了 Transport:
SocketTransport —— 原来的局域网直连
RelayTransport —— 走中转服务器
RemoteClient 里协议相关的代码(readLoop / call / awaitReply / send)
一行都不用动。
新增 connectViaRelay(baseUrl, code, token, listener, timeout):
和原有 connect(host, port, ...) 走同一套握手流程。
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
CLIENT = ROOT / "android" / "src" / "com" / "lanremote" / "watch" / "RemoteClient.java"
JSON = ROOT / "android" / "src" / "com" / "lanremote" / "watch" / "Json.java"
# ---------------- 1. Json.escape ----------------
JSON_ANCHOR = " public static String write(Object value) {"
JSON_NEW = ''' /**
* 把字符串转义成能安全塞进 JSON 字符串字面量的形式。
* 只处理引号、反斜杠和控制字符 —— 中文不用转,JSON 本身是 UTF-8。
*/
public static String escape(String text) {
if (text == null) {
return "";
}
StringBuilder out = new StringBuilder(text.length() + 16);
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
switch (ch) {
case '"':
out.append("\\\\\\"");
break;
case '\\\\':
out.append("\\\\\\\\");
break;
case '\\n':
out.append("\\\\n");
break;
case '\\r':
out.append("\\\\r");
break;
case '\\t':
out.append("\\\\t");
break;
default:
if (ch < 0x20) {
out.append(String.format("\\\\u%04x", (int) ch));
} else {
out.append(ch);
}
break;
}
}
return out.toString();
}
''' + JSON_ANCHOR
# ---------------- 2. RemoteClient ----------------
FIELD_OLD = ''' private Socket socket;
private BufferedReader reader;'''
FIELD_NEW = ''' /**
* 传输层。局域网直连时是 SocketTransport,跨网络时是 RelayTransport。
* 抽出来之后协议逻辑完全不用管数据是走 TCP 还是走中转的 HTTPS。
*/
private Transport transport;
private BufferedReader reader;'''
CONNECTED_OLD = ''' public boolean isConnected() {
return running && socket != null && !socket.isClosed();
}'''
CONNECTED_NEW = ''' public boolean isConnected() {
return running && transport != null && !transport.isClosed();
}
/** 当前传输方式的描述,界面上用来显示"直连"还是"经中转"。 */
public String transportName() {
if (transport == null) {
return "";
}
return transport instanceof RelayTransport ? "中转" : "直连";
}
public RelayTransport relayTransport() {
return transport instanceof RelayTransport
? (RelayTransport) transport : null;
}'''
CONNECT_OLD = ''' Socket sock = new Socket();
sock.connect(new InetSocketAddress(host, port), timeoutMs);
sock.setSoTimeout(0);
sock.setTcpNoDelay(true);
this.socket = sock;
this.reader = new BufferedReader(new InputStreamReader(sock.getInputStream(), UTF8), 8192);
this.writer = sock.getOutputStream();
Thread thread = new Thread(new Runnable() {'''
CONNECT_NEW = ''' Socket sock = new Socket();
sock.connect(new InetSocketAddress(host, port), timeoutMs);
sock.setSoTimeout(0);
sock.setTcpNoDelay(true);
return startTransport(new Transport.SocketTransport(sock), code, token,
listener, timeoutMs);
}
/**
* 经中转服务器连接(跨网络)。
*
* 手表没法直连电脑(两边都在 NAT 后面),所以两边都主动连出去,
* 由中转按配对码把两条连接对上。中转只转发字节、不认识协议内容,
* 所以电脑那边的配对和令牌校验照样生效。
*/
public Map<String, Object> connectViaRelay(String baseUrl, String code,
String token, Listener listener,
int timeoutMs) throws IOException {
close();
RelayTransport relay = new RelayTransport(baseUrl, code, deviceName);
if (!relay.attach()) {
String reason = relay.getLastError();
relay.close();
throw new IOException(reason == null || reason.isEmpty()
? "连不上中转服务器" : reason);
}
return startTransport(relay, code, token, listener, timeoutMs);
}
/** 握手 + 起读取线程。局域网和中转共用这一段。 */
private Map<String, Object> startTransport(Transport channel, String code,
String token, Listener listener,
int timeoutMs) throws IOException {
this.listener = listener;
this.running = true;
this.replies.clear();
this.transport = channel;
this.reader = new BufferedReader(
new InputStreamReader(channel.input(), UTF8), 8192);
this.writer = channel.output();
Thread thread = new Thread(new Runnable() {'''
CLOSE_OLD = ''' public void close() {
running = false;
try {
if (socket != null) {
socket.close();
}
} catch (IOException ignored) {
// 关闭时的异常无需处理
}
socket = null;
reader = null;
writer = null;
replies.clear();
}'''
CLOSE_NEW = ''' public void close() {
running = false;
try {
if (transport != null) {
transport.close();
}
} catch (Exception ignored) {
// 关闭时的异常无需处理
}
transport = null;
reader = null;
writer = null;
replies.clear();
}'''
def main() -> int:
ok = True
# ---- Json.escape ----
text = JSON.read_text(encoding="utf-8")
if "public static String escape(" in text:
print(" Json.escape 已存在")
elif JSON_ANCHOR not in text:
print(" [失败] Json 锚点没找到")
ok = False
else:
text = text.replace(JSON_ANCHOR, JSON_NEW, 1)
JSON.write_text(text, encoding="utf-8")
print(" Json.escape 已加")
# ---- RemoteClient ----
text = CLIENT.read_text(encoding="utf-8")
if "connectViaRelay" in text:
print(" RemoteClient 已经改过")
else:
for old, new, tag in ((FIELD_OLD, FIELD_NEW, "字段换成 Transport"),
(CONNECTED_OLD, CONNECTED_NEW, "isConnected 用 Transport"),
(CONNECT_OLD, CONNECT_NEW, "connect 拆出 startTransport + connectViaRelay"),
(CLOSE_OLD, CLOSE_NEW, "close 关 Transport")):
if old not in text:
print(f" [失败] {tag} —— 锚点没找到")
ok = False
continue
text = text.replace(old, new, 1)
print(f" {tag}")
CLIENT.write_text(text, encoding="utf-8")
# 检查还有没有裸用 socket 的地方
text = CLIENT.read_text(encoding="utf-8")
leftovers = [line.strip() for line in text.splitlines()
if "socket" in line and "Transport" not in line
and "Socket" not in line and "//" not in line]
if leftovers:
print(f" [注意] 还有裸用 socket 的行:{leftovers}")
else:
print(" 没有残留的裸 socket 引用")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())