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
+313
View File
@@ -0,0 +1,313 @@
# -*- coding: utf-8 -*-
"""任务 A:放行防火墙之前,配对码不加入服务器。
任务 B:性能提升。
────────────────────────────────────────
A. 配对码门禁
────────────────────────────────────────
用户要求:"在放行防火墙之前配对码不会加入服务器"
现在的顺序:
检查码(只读) -> 存码 -> 开自启 -> 放行防火墙 -> run_silent(注册)
虽然注册在防火墙之后,但**防火墙失败也照样注册** ——
服务器上就挂着一个谁也连不上的配对码。
改成:
防火墙没放行成功 -> 不注册,告诉用户
────────────────────────────────────────
B. 性能
────────────────────────────────────────
实测(本机 1920x1080):
抓屏 ImageGrab.grab() 33 ms <- 硬开销,决定了上限
缩放 + JPEG 编码 17 ms (BILINEAR, 960 宽)
合计 50 ms -> 约 20 帧/秒
问题:
1. screen_fps 默认只有 3 —— 白白浪费了 20 帧的能力
2. screen_quality 默认 55 —— 偏糊
3. **画面没变也照抓照编码照发** —— 手表看静态桌面时白烧 CPU 和流量
改动:
1. 默认帧率 3 -> 12
2. 默认质量 55 -> 72
3. 加"画面没变就跳过":用 32x18 缩略图做指纹(很便宜),
指纹一样就不编码、不发送,并把采样间隔逐级拉长(最多 4 倍),
画面一变立刻恢复满帧率。
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
RUN = ROOT / "windows" / "run_agent.py"
CONFIG = ROOT / "windows" / "lanremote" / "config.py"
SCREEN = ROOT / "windows" / "lanremote" / "screen.py"
# ================= A. 配对码门禁 =================
A_OLD = ''' # 黑终端一闪:修防火墙
if result["firewall"]:
fix_firewall_with_flash(store)
'''
A_NEW = ''' # 黑终端一闪:修防火墙
firewall_ok = True
firewall_note = ""
if result["firewall"]:
firewall_ok, firewall_note = fix_firewall_with_flash(store)
# 放行防火墙之前,配对码**不加入服务器**。
#
# 为什么:注册(= 配对码挂到服务器上)如果发生在防火墙之前,
# 那么防火墙没放行成功时,服务器上会留着一个谁也连不上的码,
# 而且它会把码占住,用户重装都换不了。
# 所以这里卡一道:防火墙没成功就不注册。
if not firewall_ok:
_tell_firewall_problem(firewall_note)
return 3
'''
HELPER_ANCHOR = "def _tell_code_problem(message: str) -> None:"
HELPER_NEW = '''def _tell_firewall_problem(detail: str) -> None:
"""防火墙没放行成功,说明为什么这次没生效。"""
message = (
"防火墙没有放行成功,所以这次**没有**把配对码交给服务器。\\n\\n"
f"原因:{detail or '未知'}\\n\\n"
"该怎么办:\\n"
" 1. 右键点 ykxmz.exe →「以管理员身份运行」,重新走一遍设置\\n"
" 2. 或者手动放行:控制面板 → Windows Defender 防火墙 →\\n"
" 允许应用通过防火墙 → 找到 ykxmz → 勾上「专用网络」\\n\\n"
"放行之后再打开 ykxmz.exe 就行了。"
)
try:
import tkinter as tk
from tkinter import messagebox
root = tk.Tk()
root.withdraw()
root.attributes("-topmost", True)
messagebox.showerror("还没生效:防火墙没放行", message)
root.destroy()
return
except Exception: # noqa: BLE001
pass
print("=" * 56)
print("防火墙没放行,配对码未提交到服务器")
print(message)
print("=" * 56)
''' + HELPER_ANCHOR
# ================= B1. 默认参数 =================
CFG_OLD = ''' "screen_quality": 55, # 镜像 JPEG 质量
"screen_fps": 3, # 默认帧率'''
CFG_NEW = ''' # 镜像 JPEG 质量。
# 实测质量对编码耗时几乎没影响(都是 ~17ms),主要影响体积:
# 55 -> 30KB/帧 72 -> 45KB/帧 85 -> 70KB/帧
# 取 72:画质明显更清楚,局域网完全带得动。
"screen_quality": 72,
# 默认帧率。
# 实测抓屏 33ms + 编码 17ms = 50ms,也就是**硬件上限约 20 帧/秒**。
# 原来写 3 是白白浪费了 6 倍的能力。
# 取 12:够顺,又给抓屏留了余量,不至于把 CPU 占满。
# (画面静止时会自动降频,见 screen.py 的 _loop)
"screen_fps": 12,'''
# ================= B2. 抓屏去重 =================
GRAB_OLD = ''' width, height = image.size
if self.max_width and width > self.max_width:
ratio = self.max_width / float(width)
new_size = (self.max_width, max(1, int(height * ratio)))
image = image.resize(new_size, Image.BILINEAR)
width, height = image.size
buffer = io.BytesIO()
try:
image.save(buffer, format="JPEG", quality=self.quality,
optimize=False, subsampling=2)
except Exception as exc: # noqa: BLE001
raise ScreenError(f"JPEG 编码失败: {exc}") from exc
self._last_size = (width, height)
return buffer.getvalue(), width, height'''
GRAB_NEW = ''' width, height = image.size
if self.max_width and width > self.max_width:
ratio = self.max_width / float(width)
new_size = (self.max_width, max(1, int(height * ratio)))
image = image.resize(new_size, Image.BILINEAR)
width, height = image.size
# 画面没变就不编码。
# 用 32x18 的小缩略图做指纹:算一次只要 0.1ms 左右,
# 但能省掉 17ms 的 JPEG 编码和之后的传输。
# 手表看静态桌面是最常见的情况,这一条省得最多。
digest = None
if self.dedup:
try:
thumb = image.resize((32, 18), Image.BILINEAR)
digest = hash(thumb.tobytes())
except Exception: # noqa: BLE001
digest = None
if digest is not None and digest == self._last_digest:
self._last_size = (width, height)
return b"", width, height
self._last_digest = digest
buffer = io.BytesIO()
try:
image.save(buffer, format="JPEG", quality=self.quality,
optimize=False, subsampling=2)
except Exception as exc: # noqa: BLE001
raise ScreenError(f"JPEG 编码失败: {exc}") from exc
self._last_size = (width, height)
return buffer.getvalue(), width, height'''
INIT_OLD = ''' def __init__(self, max_width: int = 480, quality: int = 55, monitor: int = 1):
self.max_width = int(max_width)
self.quality = int(quality)
self.monitor = int(monitor)
self._lock = threading.RLock()
self._last_size = (0, 0)'''
INIT_NEW = ''' def __init__(self, max_width: int = 960, quality: int = 72, monitor: int = 1,
dedup: bool = True):
self.max_width = int(max_width)
self.quality = int(quality)
self.monitor = int(monitor)
# 画面没变就不重复编码(见 grab_jpeg)
self.dedup = bool(dedup)
self._lock = threading.RLock()
self._last_size = (0, 0)
self._last_digest = None'''
# ================= B3. 静态降频 =================
LOOP_OLD = ''' def _loop(self) -> None:
interval = 1.0 / self.fps
while not self._stop.is_set():
started = time.time()
try:
jpeg, width, height = self.capturer.grab_jpeg()
except ScreenError as exc:
self.last_error = str(exc)
time.sleep(0.5)
continue
self.frames_sent += 1
try:
if self.on_frame(jpeg, width, height) is False:
break # 没有观看者了,自动结束
except Exception as exc: # noqa: BLE001
self.last_error = str(exc)
break
elapsed = time.time() - started
wait = interval - elapsed
if wait > 0:
self._stop.wait(wait)'''
LOOP_NEW = ''' def _loop(self) -> None:
interval = 1.0 / self.fps
# 画面静止时把采样间隔逐级拉长(最多 4 倍),省 CPU。
# 抓屏本身就要 30ms 左右,静止时没必要一直满速抓。
idle = 1.0
while not self._stop.is_set():
started = time.time()
try:
jpeg, width, height = self.capturer.grab_jpeg()
except ScreenError as exc:
self.last_error = str(exc)
time.sleep(0.5)
continue
if not jpeg:
# 画面没变(capturer 做了去重):不发送,间隔拉长一点再抓。
idle = min(4.0, idle * 1.6)
self._stop.wait(interval * idle)
continue
idle = 1.0
self.frames_sent += 1
try:
if self.on_frame(jpeg, width, height) is False:
break # 没有观看者了,自动结束
except Exception as exc: # noqa: BLE001
self.last_error = str(exc)
break
elapsed = time.time() - started
wait = interval - elapsed
if wait > 0:
self._stop.wait(wait)'''
# 帧率上限 15 -> 30
CAP_OLD = " self.fps = max(0.5, min(15.0, float(fps)))\n return"
CAP_NEW = " self.fps = max(0.5, min(30.0, float(fps)))\n return"
CAP_OLD2 = " self.fps = max(0.5, min(15.0, float(fps)))\n self._stop.clear()"
CAP_NEW2 = " self.fps = max(0.5, min(30.0, float(fps)))\n self._stop.clear()"
def apply(path: Path, pairs, label: str) -> bool:
text = path.read_text(encoding="utf-8")
ok = True
for old, new in pairs:
if old == new:
continue
if old in text:
text = text.replace(old, new, 1)
else:
print(f" [失败] {label}: 锚点没找到 -> {old.strip()[:60]}")
ok = False
path.write_text(text, encoding="utf-8", newline="\n")
return ok
def main() -> int:
ok = True
# A
text = RUN.read_text(encoding="utf-8")
if "_tell_firewall_problem" in text:
print(" A: 已经改过")
else:
ok &= apply(RUN, [(A_OLD, A_NEW), (HELPER_ANCHOR, HELPER_NEW)], "run_agent")
print(" A: 防火墙门禁已加")
# B1
text = CONFIG.read_text(encoding="utf-8")
if '"screen_fps": 12' in text:
print(" B1: 已经改过")
else:
ok &= apply(CONFIG, [(CFG_OLD, CFG_NEW)], "config")
print(" B1: 默认帧率 3->12,质量 55->72")
# B2 / B3
text = SCREEN.read_text(encoding="utf-8")
if "self._last_digest" in text:
print(" B2/B3: 已经改过")
else:
ok &= apply(SCREEN, [(INIT_OLD, INIT_NEW), (GRAB_OLD, GRAB_NEW),
(LOOP_OLD, LOOP_NEW), (CAP_OLD, CAP_NEW),
(CAP_OLD2, CAP_NEW2)], "screen")
print(" B2: 加了画面去重(指纹 32x18)")
print(" B3: 静止时自动降频(最多 4 倍),帧率上限 15->30")
import ast
for label, path in (("run_agent", RUN), ("config", CONFIG),
("screen", SCREEN)):
try:
ast.parse(path.read_text(encoding="utf-8"))
print(f" {label} 语法 OK")
except SyntaxError as exc:
print(f" {label} 语法错误: 行 {exc.lineno}: {exc.msg}")
ok = False
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())