Files
ykxmz/_build/fix_session_steal.py
T
xmz7891 b705216e4a ykxmz 1.5.0锛氫慨澶嶆墜琛ㄧ TLS 璇佷功鏍¢獙 + MTU 淇缃戠粶绋冲畾鎬?
琚帶绔?- 淇 MTU 闂锛氬す MSS + 璺緞 MTU 鎺㈡祴锛堝叕缃戝彲鐢ㄧ巼 48% -> 100%锛?- 澶嶇敤 HTTP 杩炴帴锛堝厤鎺夋瘡娆℃柊寤?TLS 鐨?30% 鎻℃墜澶辫触锛?- 姣忓垎閽熷洖澶存壘灞€鍩熺綉锛岃嚜鍔ㄥ垏鍥炲揩 400 鍊嶇殑璺緞
- 鐢婚潰蹇冭烦甯э細闈欐鏃剁渷娴侀噺锛屽鎴风浠嶇煡閬撴祦鏄椿鐨?- 閿佸睆鏃剁粰鍑哄彲鎿嶄綔鐨勬彁绀?
鎵嬭〃绔?- 鍐呯疆 GTS Root R4 / GlobalSign Root CA锛屼慨 SSLHandshakeException
- 杩炴帴閿欒缈昏瘧鎴愪汉璇濓紙璇佷功/鏃堕棿/瑙f瀽/瓒呮椂鍒嗗埆鎻愮ず锛?- 缁忎腑杞繛涓婂悗鐢ㄦ湇鍔″櫒鎶ョ殑鍦板潃鍒锋柊宸查厤瀵硅褰?- 鏂板灞€鍩熺綉杩炴帴椤甸潰銆佹湇鍔″櫒寤惰繜妫€娴?
鏈嶅姟鍣ㄧ
- 鏀寔 chunked 璇锋眰浣擄紙淇瘡 16 绉掍竴娆$殑杩炴帴閿欎贡涓庡ぇ闈㈢Н 502锛?- 缃戠粶璋冧紭鑴氭湰 net-tune.sh锛堝紑鏈鸿嚜鍔ㄥ簲鐢級
- 绠$悊椤碉細鎵归噺鍒犻櫎銆佺綉缁滀綋妫€銆佷笅杞藉尯
- 闅ч亾鏀?HTTP/2 + 鍙岃繛鎺?
鏂囨。
- 璁稿彲鍗忚 / 瀹夎鍓嶅繀璇?/ 瀹夎鍚庨』鐭ワ紙浣滆€咃細灏忛害瀛愶級
2026-10-06 19:01:05 +08:00

217 lines
8.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""修:被顶替的会话抢走了本该给新会话的数据。
证据(Java 客户端打点):
[dbg] attach ok session=ad9283e7 <- 新手表接入
[dbg] send 122 bytes -> 200 <- 新手表发 auth
[dbg] poll -> 200 bytes=53 session=ad9283e7 <- 新手表其实收到了
[dbg] poll -> 200 bytes=50 session=a7c11203 <- 但旧手表也收到了一份
根因:
device.to_client 是**按设备共享**的一个缓冲区。
旧会话被顶掉后,它那个 40 秒的长轮询还挂在 device.event 上。
新数据一来,谁先醒谁拿走 —— 旧会话就把它截胡了。
修法:
1. Session 各自带一个 event
2. 顶替旧会话时,把它的 event set() 一下 —— 它的长轮询立刻醒来
3. client_take 每轮先看 session.replaced,是就抛 _Replaced
4. 接口层把 _Replaced 翻成 409 + replaced,客户端据此断开(不抢)
5. 数据只唤醒**当前**会话(device.active_session)
"""
from __future__ import annotations
import sys
from pathlib import Path
RELAY = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\relay\relay.py")
# ---------------- 1. Device 记住当前会话对象 ----------------
OLD_SLOTS = ''' "event", "bytes_in", "bytes_out", "current_session")'''
NEW_SLOTS = ''' "event", "bytes_in", "bytes_out", "current_session",
"active_session")'''
OLD_INIT = ''' self.current_session = ""'''
NEW_INIT = ''' self.current_session = ""
# 当前挂在这台设备上的 Session 对象。数据来了只唤醒它,
# 不要让已经被顶掉的旧会话把数据截胡。
self.active_session = None'''
# ---------------- 2. push_to_client 只唤醒当前会话 ----------------
OLD_PUSH = ''' def push_to_client(self, data: bytes) -> None:
if not data:
return
self.bytes_out += len(data)
self._append(self.to_client, data)
self.event.set()'''
NEW_PUSH = ''' def push_to_client(self, data: bytes) -> None:
if not data:
return
self.bytes_out += len(data)
self._append(self.to_client, data)
self.event.set()
# 同时叫醒当前那个会话的长轮询(旧会话已经被替换,不该再拿数据)
session = self.active_session
if session is not None:
session.event.set()'''
# ---------------- 3. Session 带自己的 event + replaced ----------------
OLD_SESSION = ''' __slots__ = ("token", "device_id", "code", "name", "ip",
"created", "last_seen")
def __init__(self, device_id: str, code: str, name: str, ip: str) -> None:
self.token = secrets.token_hex(24)
self.device_id = device_id
self.code = code
self.name = name
self.ip = ip
self.created = time.time()
self.last_seen = time.time()'''
NEW_SESSION = ''' __slots__ = ("token", "device_id", "code", "name", "ip",
"created", "last_seen", "event", "replaced")
def __init__(self, device_id: str, code: str, name: str, ip: str) -> None:
self.token = secrets.token_hex(24)
self.device_id = device_id
self.code = code
self.name = name
self.ip = ip
self.created = time.time()
self.last_seen = time.time()
# 自己的唤醒事件。每个会话独立等待,避免被顶掉的旧会话截胡数据。
self.event = threading.Event()
self.replaced = False'''
# ---------------- 4. attach:踢的时候唤醒旧会话 ----------------
OLD_KICK = ''' if stale.device_id == device_id:
self.sessions.pop(stale_token, None)
self.retired[stale_token] = "replaced"
log(f"踢掉旧会话 {stale_token[:8]}…(同一设备只允许一个)")'''
NEW_KICK = ''' if stale.device_id == device_id:
self.sessions.pop(stale_token, None)
self.retired[stale_token] = "replaced"
stale.replaced = True
# 立刻叫醒它挂着的长轮询 —— 否则它会一直等下去,
# 等新数据来时把本该给新手表的数据抢走。
stale.event.set()
log(f"踢掉旧会话 {stale_token[:8]}…(同一设备只允许一个)")'''
OLD_BIND = ''' device.current_session = session.token
# 换会话了:把上一条留在队列里的半截数据清掉,
# 不然新手表会收到上一个手表没取走的残留字节。
device.to_client.clear()'''
NEW_BIND = ''' device.current_session = session.token
device.active_session = session
# 换会话了:把上一条留在队列里的半截数据清掉,
# 不然新手表会收到上一个手表没取走的残留字节。
device.to_client.clear()'''
# ---------------- 5. client_take:独立事件 + 让位 ----------------
OLD_TAKE = ''' def client_take(self, session: Session, wait: float) -> bytes:
"""手表取走「被控端发来的」数据。"""
with self.lock:
device = self.devices.get(session.device_id)
if device is None:
return b""
return self._take(device, device.to_client, wait, "client")'''
NEW_TAKE = ''' def client_take(self, session: Session, wait: float) -> bytes:
"""手表取走「被控端发来的」数据。
注意这里**不能**复用 _take:那个是按设备的事件等的,
会把被顶掉的旧会话和新会话混在一起等,导致数据被旧会话截胡。
每个会话等自己的 event,并且每轮先看自己是不是已经被顶替了。
"""
deadline = time.time() + wait
while True:
if session.replaced:
raise SessionReplaced()
with self.lock:
device = self.devices.get(session.device_id)
if device is None:
return b""
if device.to_client:
data = bytes(device.to_client)
device.to_client.clear()
device.event.clear()
session.event.clear()
return data
remaining = deadline - time.time()
if remaining <= 0:
return b""
session.event.wait(min(remaining, 0.5))
session.event.clear()'''
# ---------------- 6. _Replaced 异常 ----------------
OLD_EXC_ANCHOR = '''class Relay:'''
NEW_EXC = '''class SessionReplaced(Exception):
"""这个会话已经被新的手表顶替了 —— 它必须让位,不能继续取数据。"""
class Relay:'''
# ---------------- 7. 接口层翻成 409 ----------------
OLD_POLL = ''' data = RELAY.client_take(session, POLL_SECONDS)
self._send(200, data, "application/octet-stream")
return'''
NEW_POLL = ''' try:
data = RELAY.client_take(session, POLL_SECONDS)
except SessionReplaced:
self._json(409, {"ok": False, "replaced": True,
"error": "已被另一台手表接管"})
return
self._send(200, data, "application/octet-stream")
return'''
# ---------------- 8. push_to_agent 也唤醒对应会话(agent 侧不用改) ----------------
def main() -> int:
text = RELAY.read_text(encoding="utf-8")
ok = True
for old, new, tag in ((OLD_SLOTS, NEW_SLOTS, "Device 加 active_session"),
(OLD_INIT, NEW_INIT, "初始化 active_session"),
(OLD_PUSH, NEW_PUSH, "push 只唤醒当前会话"),
(OLD_SESSION, NEW_SESSION, "Session 带 event/replaced"),
(OLD_KICK, NEW_KICK, "踢的时候唤醒旧会话"),
(OLD_BIND, NEW_BIND, "绑定 active_session"),
(OLD_TAKE, NEW_TAKE, "client_take 独立等待 + 让位"),
(OLD_EXC_ANCHOR, NEW_EXC, "SessionReplaced 异常"),
(OLD_POLL, NEW_POLL, "poll 把让位翻成 409")):
if old not in text:
print(f" [失败] {tag} —— 锚点没找到")
ok = False
continue
text = text.replace(old, new, 1)
print(f" {tag}")
RELAY.write_text(text, encoding="utf-8")
import ast
try:
ast.parse(text)
print(" 语法 OK")
except SyntaxError as exc:
print(f" 语法错误: {exc}")
ok = False
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())