From 486c84efb6fc2557b7d52b1c193bdd2052f8ae25 Mon Sep 17 00:00:00 2001 From: milk <53408947@qq.com> Date: Tue, 4 Nov 2025 20:33:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AE=BE=E5=A4=87=E6=95=B0?= =?UTF-8?q?=E9=87=8F=E9=99=90=E5=88=B6=E3=80=82=E4=BF=AE=E5=A4=8D=E8=AF=AF?= =?UTF-8?q?=E6=9D=80iproxy=E9=80=BB=E8=BE=91=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Module/DeviceInfo.py | 181 +++++++++++++----- Module/FlaskService.py | 1 - Module/__pycache__/DeviceInfo.cpython-312.pyc | Bin 31309 -> 36091 bytes .../__pycache__/FlaskService.cpython-312.pyc | Bin 40296 -> 40374 bytes Module/__pycache__/Main.cpython-312.pyc | Bin 3700 -> 3700 bytes Utils/__pycache__/LogManager.cpython-312.pyc | Bin 14663 -> 14663 bytes .../__pycache__/ThreadManager.cpython-312.pyc | Bin 15186 -> 15625 bytes script/ScriptManager.py | 5 + .../__pycache__/ScriptManager.cpython-312.pyc | Bin 69108 -> 70039 bytes 9 files changed, 134 insertions(+), 53 deletions(-) diff --git a/Module/DeviceInfo.py b/Module/DeviceInfo.py index d24773d..6c90ab2 100644 --- a/Module/DeviceInfo.py +++ b/Module/DeviceInfo.py @@ -129,6 +129,8 @@ class DeviceInfo: self._last_seen: Dict[str, float] = {} self._manager = FlaskSubprocessManager.get_instance() self._iproxy_path = self._find_iproxy() + self._check_fail: Dict[str, int] = {} + self.MAX_DEVICES = 6 LogManager.info("DeviceInfo 初始化完成", udid="system") print("[Init] DeviceInfo 初始化完成") @@ -137,64 +139,81 @@ class DeviceInfo: # =============== 核心:端口连通性检测(HTTP 方式) ================= def _is_local_port_open(self, port: int, udid: str, timeout: float = 5) -> bool: """ - 使用 HTTP 方式检测:向 http://127.0.0.1:port/ 发送一次 HEAD 请求。 - 只要建立连接并收到合法的 HTTP 响应(任意 1xx~5xx 状态码),即认为 HTTP 可达。 - 遇到连接失败、超时、协议不对等异常,视为不可用。 + 使用 HTTP 方式检测:对 http://127.0.0.1:port/status 发送 GET。 + ✅ 1xx~5xx 任意状态码都视作“HTTP 可达”(WDA 常返回 200/404/401)。 + ✅ 超时改为默认 5 秒,更抗抖。 """ if not isinstance(port, int) or port <= 0 or port > 65535: LogManager.error("端口不可用(非法端口号)", udid=udid) return False try: - # HEAD 更轻;若后端对 HEAD 不友好,可改为 "GET", "/" conn = http.client.HTTPConnection("127.0.0.1", int(port), timeout=timeout) - conn.request("HEAD", "/") + conn.request("GET", "/status") resp = conn.getresponse() + _ = resp.read(128) status = resp.status - # 读到响应即可关闭 conn.close() - # 任何合法 HTTP 状态码都说明“HTTP 服务在监听且可交互”,包括 404/401/403/5xx if 100 <= status <= 599: return True else: LogManager.error(f"HTTP状态码异常: {status}", udid=udid) return False - except Exception as e: - # 连接被拒绝、超时、不是HTTP协议正确响应(比如返回了非HTTP的字节流)都会到这里 LogManager.error(f"HTTP检测失败:{e}", udid=udid) return False # =============== 一轮检查:发现不通就移除 ================= def check_iproxy_ports(self, connect_timeout: float = 3) -> None: + """ + 周期性健康检查 iproxy -> WDA HTTP 可达性。 + ✅ 改为“连续失败 3 次才移除”,大幅降低抖动下的误删。 + """ + # 给系统和 WDA 一点缓冲时间 time.sleep(20) + + FAIL_THRESHOLD = 3 # 连续失败 N 次才视为离线 + INTERVAL_SEC = 10 # 巡检间隔 + while True: snapshot = list(self._models.items()) # [(deviceId, DeviceModel), ...] for device_id, model in snapshot: try: - # 只处理在线且端口合法的设备 + # 只处理在线的 iOS(type==1) if model.type != 1: continue port = int(model.screenPort) if port <= 0 or port > 65535: continue - ok = self._is_local_port_open(port, timeout=connect_timeout, udid=device_id) - if not ok: - print(f"[iproxy-check] 端口不可连,移除设备 deviceId={device_id} port={port}") + ok = self._is_local_port_open(port, udid=device_id, timeout=connect_timeout) + if ok: + # 成功即清零失败计数 try: - self._remove_device(device_id) # 这里面可安全地改 self._models + self._check_fail[device_id] = 0 + except Exception: + pass + # 可选:打印心跳日志过于刷屏,这里省略 + continue + + # 记录失败计数 + cnt = self._check_fail.get(device_id, 0) + 1 + self._check_fail[device_id] = cnt + print(f"[iproxy-check] FAIL #{cnt} deviceId={device_id} port={port}") + + if cnt >= FAIL_THRESHOLD: + print(f"[iproxy-check] 连续失败{cnt}次,移除设备 deviceId={device_id} port={port}") + # 清掉计数并移除 + self._check_fail.pop(device_id, None) + try: + self._remove_device(device_id) except Exception as e: print(f"[iproxy-check] _remove_device 异常 deviceId={device_id}: {e}") - else: - # 心跳日志按需开启,避免刷屏 - # print(f"[iproxy-check] OK deviceId={device_id} port={port}") - pass except Exception as e: print(f"[iproxy-check] 单设备检查异常: {e}") - # 8秒间隔 - time.sleep(10) + + time.sleep(INTERVAL_SEC) def listen(self): LogManager.method_info("进入主循环", "listen", udid="system") @@ -216,16 +235,27 @@ class DeviceInfo: with self._lock: known = set(self._models.keys()) + current_online_count = sum(1 for m in self._models.values() if getattr(m, "type", 2) == 1) - for udid in online - known: - if (now - self._first_seen.get(udid, now)) >= self.ADD_STABLE_SEC: - print(f"[Add] 检测到新设备: {udid}") - try: - self._add_device(udid) - except Exception as e: - LogManager.method_error(f"新增失败:{e}", "listen", udid=udid) - print(f"[Add] 新增失败 {udid}: {e}") + # ==== 限流新增(最多 6 台)==== + candidates = list(online - known) + stable_candidates = [udid for udid in candidates + if (now - self._first_seen.get(udid, now)) >= self.ADD_STABLE_SEC] + # 谁先出现谁优先 + stable_candidates.sort(key=lambda u: self._first_seen.get(u, now)) + capacity = max(0, self.MAX_DEVICES - current_online_count) + to_add = stable_candidates[:capacity] if capacity > 0 else [] + + for udid in to_add: + print(f"[Add] 检测到新设备: {udid}") + try: + self._add_device(udid) + except Exception as e: + LogManager.method_error(f"新增失败:{e}", "listen", udid=udid) + print(f"[Add] 新增失败 {udid}: {e}") + + # ==== 处理离线 ==== for udid in list(known): if udid in online: continue @@ -265,10 +295,19 @@ class DeviceInfo: print(f"[WDA] /status@{local_port} 等待超时 {udid}") return False - def _add_device(self, udid: str): print(f"[Add] 开始新增设备 {udid}") + # ====== 上限保护:并发安全的首次检查 ====== + with self._lock: + if udid in self._models: + print(f"[Add] 设备已存在,跳过 {udid}") + return + current_online_count = sum(1 for m in self._models.values() if getattr(m, "type", 2) == 1) + if current_online_count >= self.MAX_DEVICES: + print(f"[Add] 已达设备上限 {self.MAX_DEVICES},忽略新增 {udid}") + return + if not self._trusted(udid): print(f"[Add] 未信任设备 {udid}, 跳过") return @@ -294,22 +333,25 @@ class DeviceInfo: return print(f"[WDA] WDA 就绪,准备获取屏幕信息 {udid}") - # 给 WDA 一点稳定时间,避免刚 ready 就查询卡住 time.sleep(0.5) - # 带超时的屏幕信息获取,避免卡死在 USBClient 调用里 w, h, s = self._screen_info_with_timeout(udid, timeout=3.5) if not (w and h and s): - # 再做几次快速重试(带超时) for i in range(4): print(f"[Screen] 第{i + 1}次获取失败, 重试中... {udid}") time.sleep(0.6) w, h, s = self._screen_info_with_timeout(udid, timeout=3.5) if w and h and s: break - if not (w and h and s): print(f"[Screen] 屏幕信息仍为空,继续添加 {udid}") + # ====== 上限保护:在分配端口前做二次检查(防并发越界)====== + with self._lock: + current_online_count = sum(1 for m in self._models.values() if getattr(m, "type", 2) == 1) + if current_online_count >= self.MAX_DEVICES: + print(f"[Add](二次检查)已达设备上限 {self.MAX_DEVICES},忽略新增 {udid}") + return + port = self._alloc_port() print(f"[iproxy] 准备启动 iproxy 映射 {port}->{wdaScreenPort}") proc = self._start_iproxy(udid, local_port=port) @@ -331,43 +373,51 @@ class DeviceInfo: def _remove_device(self, udid: str): """ - 移除设备及其转发,通知上层。 - 幂等:重复调用不会出错。 + 移除设备及其转发,通知上层(幂等)。 + ✅ 同时释放 self._reserved_ports 中可能残留的保留端口。 + ✅ 同时清理 _iproxy / _port_by_udid。 """ print(f"[Remove] 正在移除设备 {udid}") - # --- 1. 锁内执行所有轻量字典操作 --- + # --- 1. 锁内取出并清空字典 --- with self._lock: model = self._models.pop(udid, None) proc = self._iproxy.pop(udid, None) - self._port_by_udid.pop(udid, None) - self._first_seen.pop(udid, None) - self._last_seen.pop(udid, None) + port = self._port_by_udid.pop(udid, None) - # --- 2. 锁外执行重操作 --- - # 杀进程 + # --- 2. 杀进程 --- try: self._kill(proc) except Exception as e: print(f"[Remove] 杀进程异常 {udid}: {e}") - # 准备下线模型(model 可能为 None) - if model is None: - model = DeviceModel( - deviceId=udid, screenPort=-1, width=0, height=0, scale=0.0, type=2 - ) + # --- 3. 释放“保留端口”(如果还在集合里)--- + if isinstance(port, int) and port > 0: + try: + self._release_port(port) + except Exception as e: + print(f"[Remove] 释放保留端口异常 {udid}: {e}") + + # --- 4. 构造下线模型并通知 --- + if model is None: + model = DeviceModel(deviceId=udid, screenPort=-1, width=0, height=0, scale=0.0, type=2) - # 标记状态为离线 model.type = 2 model.ready = False model.screenPort = -1 - # 通知上层 try: self._manager_send(model) except Exception as e: print(f"[Remove] 通知上层异常 {udid}: {e}") + # --- 5. 清理失败计数(健康检查用)--- + try: + if hasattr(self, "_check_fail"): + self._check_fail.pop(udid, None) + except Exception: + pass + print(f"[Remove] 设备移除完成 {udid}") def _trusted(self, udid: str) -> bool: @@ -478,6 +528,10 @@ class DeviceInfo: return False def _spawn_iproxy(self, udid: str, local_port: int, remote_port: int) -> Optional[subprocess.Popen]: + """ + 启动 iproxy 子进程。 + ✅ 将 stdout/stderr 写入 log/iproxy/{udid}_{port}.log,便于追查“端口被占用/被拦截/崩溃”等原因。 + """ creationflags = 0 startupinfo = None if os.name == "nt": @@ -487,29 +541,52 @@ class DeviceInfo: si.dwFlags |= subprocess.STARTF_USESHOWWINDOW si.wShowWindow = 0 startupinfo = si + cmd = [self._iproxy_path, "-u", udid, str(local_port), str(remote_port)] + + # 日志文件 + log_dir = Path("log/iproxy") try: - print(f"[iproxy] 启动进程 {cmd}") + log_dir.mkdir(parents=True, exist_ok=True) + except Exception: + pass + log_path = log_dir / f"{udid}_{local_port}.log" + + try: + print(f"[iproxy] 启动进程 {cmd} (log={log_path})") + logfile = open(log_path, "ab", buffering=0) return subprocess.Popen( cmd, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, + stdout=logfile, + stderr=logfile, creationflags=creationflags, startupinfo=startupinfo, ) except Exception as e: print(f"[iproxy] 创建进程失败: {e}") - return None + LogManager.error(f"[iproxy] 创建进程失败: {e}", "system") def _start_iproxy(self, udid: str, local_port: int) -> Optional[subprocess.Popen]: + """ + 启动 iproxy 并等待本地端口监听成功。 + ✅ 监听成功后,立刻释放 self._reserved_ports 对应的“保留”,避免保留池越攒越多。 + """ proc = self._spawn_iproxy(udid, local_port=local_port, remote_port=wdaScreenPort) if not proc: print(f"[iproxy] 启动失败 {udid}") return None + if not self._wait_until_listening(local_port, 3.0): self._kill(proc) print(f"[iproxy] 未监听, 已杀死 {udid}") return None + + # ✅ 监听成功,释放“保留端口” + try: + self._release_port(local_port) + except Exception as e: + print(f"[iproxy] 释放保留端口异常: {e}") + print(f"[iproxy] 启动成功 port={local_port} {udid}") return proc diff --git a/Module/FlaskService.py b/Module/FlaskService.py index 42063ff..b0a1427 100644 --- a/Module/FlaskService.py +++ b/Module/FlaskService.py @@ -667,7 +667,6 @@ def delete_last_message(): def stopAllTask(): idList = request.get_json() code, msg = ThreadManager.batch_stop(idList) - time.sleep(2) return ResultData(code, [], msg).toJson() # 切换账号 diff --git a/Module/__pycache__/DeviceInfo.cpython-312.pyc b/Module/__pycache__/DeviceInfo.cpython-312.pyc index 0aeb4c2d4dc7d49f8abd5e557c853f396f9d3b9e..83c8d140c3f584813d2c15d7acaa1f569af169e8 100644 GIT binary patch delta 11717 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 UZn~m#fA|^&nSYf*D*5#P0Ejelt^fc4 diff --git a/Module/__pycache__/FlaskService.cpython-312.pyc b/Module/__pycache__/FlaskService.cpython-312.pyc index 8be2a43b3d90b5c148e2df79383f9f0e968e0497..85ffd31e38274c4fb631df4035739de8dd02145a 100644 GIT binary patch delta 1677 zcmZ8hYfO_@82--p_3OP9icl3?E6T7kK{p+ELGePRT;(!lP6T2LfpDp(ttcxn6gCoX zGw(JdbCLZUsZPz8=;F34#$h!67~mzYiPNbg#$=|8+uU@zy>C(Hc9K4Q&U?=Lp7TA= zIp@wP@x+&6%mtH4FJLo&rfY0&J07#h6|2LpgunZ(i2DT{48CSl+Fg4XI0~$0gIgC8 zDMJuCW&ae&Et{gLMy~cJL22PMSXG$Jv`|-QV;blwOt7khs-P5t z8S0=es14Ct*qE@ZMkV+i6Fot2n-0f>7}OM{fVn8%r1!IDBTX4lH$Of}A9@;L|H*Qj z5641vQMy&%rLPd&LMQ&Wvvy&t^rq0x9C2`{sKFdIwR$}6N?zCMYp(NqKwW&A8Q@&8 zosB`R(`w+wxR}p?UR`pc%Vsj$zLgu)M$ z*(7z-OC*z3M)_4iO}**``L+J(ke;3l=h2{4Ts_(*1fSqdX)xMigTnF{ri1n6wp85< zV!x0vEHiZ zwbZ#v@IAo@LDUdJ$xbcwyb+dfVD$q^?b;KE^KU3$q$iUQ*t(K6V>;SIy6w4p8%ttl-R#0;W5Dzf}db{V>~3* zoAr3qg`epgw!`SkNy^Il6iJT3{zIjyafLeWHqP$Q1^o4Mvgi038@L2tx6QVaWC0_> z_`vq@{e$Bl9vk2NIll!@+P+}-;G}mi+P->oYK{d9@py!=zSYxSS#k+Z*rF`!wupD|iMP1xDj$39i!-aW(EB^c~WfP)KT;E(q*m@6293u%jV0~{(&&o0+P=e|t#KsmBcD{8{B zui4}A!YL@w_u|aQRPk-Bn4rw~V2s6Hr;K=lNd#9w{^;*+dYL%g3Vwrt4lJkR$?0Hn zI%=HG7AIFZImyW?PS$V|o0CACZWCWi@D@QQK{o;IBP{Vi>yW^6sN!sdhuS4AZSFv` zXDLTTdiVb(ny0g@`;s8ejflu1PB{8;qB(UUwjd%Ri>y(O41C8-63d{8b0Q)#uph9( I;K6hM0Jnt5P5=M^ delta 1596 zcmY*Ze@s(X6n^);zS5Qgg|-kVgTYiRKcsO}7**#G5GV+=z{ZduvBk&`%WW$YTT#G` z#QAGSX69Jp_Qw*1Y%VX^4BJ0uVoa7zL5gsE}ofGpch-uJ$H?m6e3 z``vTjmtTu_#>Lq0Vq$axj?!k$=G$Fg#1>WPG&q&;c~B1dwTqSBioFc14omWSK?uoy z3whHRoqGu(HMBUgV7)^R6{$ussD`>+Q({mKs)AB5s$De}Ju1$*hBPqgNQZ#a6k~lB zEV%V!F%Vx>0N*?HP+w(&drp1we^%YGV{!BG9XY^qQXtp$95cW~)h4i4pJqCEQvE!X zlo_F|J_Tym84|8x?kXk-zE*@?LPV7YeBCG%H@swaOy)VR=GsnLPc@yBFXYtxDY3*l z*xRthVku1%CepNJ8PY^nep#w?HjyDen<|%O0IOX9zZ7Z3h^W|VS21|6?&XEeD2B?P zG{231#gP5U^R|u@I8{hDij5l^=S)Gt*D@Slyt|z zgm+t1f`8%MOc31BWBO4BI#(u)JB>^OKRDAZno-;z4fa1zcg$EUXk0l-nw=V#;1)vo z?qNN`TT+wI!>n;o=~@j3T_*JuhVqMW!ev!oB6=CFx!y?7@(nb(LU5Jf0<11e((!8K zJ}m-t)-Oo-2}u`eT>MPq4EUrzC`NQ(YpB&WT*z5N(w3sMov9`HB};`fOSPrTq%-#X zQkyi9&5%#nAeziY~&nQoK_> zJ%iKbMrFy4n@nDb#y6G3&{r(xE*5`W zGTE0P+Gm$m&j!%?tHjnI)LSK9LX$FrwFGpTc{wpso>xFxe*tqqWB+1ZEm{Ytu>CN( z#snk%pNiuGe7q|cyaPor{+bD{zo>)qLcJl#HVHv-zTG?G@M5Rxtl~Ou#uQc!{5X)B zyc5%hWnZVqi9=0e(C zh`y%c^mpW1G{B9Z1#TaTXAhK_LmE-$E;4EkSLz0k%uwnbnA)s#AD&`{pDD~pU?R8- zuJ@nz(VvOa)#23goZ6gIQ*&x(PEE?GJvlW7r)J=E)SQl%(_wKEnAa0D5;PNp2>J-9 zj{i-^b~#S9LPnBy|%jLRY- evPh-!=;(PCr(#RUGcO_{i^PKW12gU diff --git a/Module/__pycache__/Main.cpython-312.pyc b/Module/__pycache__/Main.cpython-312.pyc index bdb6698db734f82ecb1fb70bf1b6cabd951f6234..da69ffd9b60e28b219000f23cf60bf82710be3c3 100644 GIT binary patch delta 20 acmew&^F@aHG%qg~0}#Z#=iJDh$p-*JxCNpB delta 20 acmew&^F@aHG%qg~0}xbZaBSqx3 delta 20 acmX?Jbi9cBG%qg~0}x2taBSqZu>=4?^#uI@ diff --git a/Utils/__pycache__/ThreadManager.cpython-312.pyc b/Utils/__pycache__/ThreadManager.cpython-312.pyc index d2834ff77d5c5c85c9699fca34b1fec960f618e8..460469374c2e079d32040529f48c08f7a3d820cf 100644 GIT binary patch delta 1117 zcmYLHT})eL7=F+HPkY)y4{d2FJ*6E`JDIMu%*~CElsQAd3+DyNxF8&ubcpkm{ix^2 z^kTAEW1BBo+x>*17qToSs26726$xw?8nfu84Vj51W49JGnPD;U`@q7NoacMa^Pcy4 z-tWu#>CDCV9N#O740!!ID~+R$uN}MNj=MBK0ZxEL~>;lM8*`!IQZ739TIk=sA_9%G zgLB3Jjx!{I7;bj9Z+0psr0Z*w?d<6W;uhR?vuvgf=fq5#zBQ@cw5L62nD?<*?m=^V zf=+QKdb9Y4=I^4it=TtOFlj^3C0#b@t8kqjqhE)2Z6h>=FVEu6LKvp(wde>K`z;y$ zYYX|gtDv){j5}C;`%3Y#|37MAdpHUPK?h6zK+aKFaf_krLBDkG95V=Ksi+-2>pbi* zLOA5al@eLEy3tFCr04OSJFA!GR=@k^$xLqb((LMo*PeX!dDMX}B!c}b{P3w$qm#Pf z#%+c~z)7H)KnsB|fma9|#Sm4D-GraRu)>cGk57#1D}phaIXy9zLH83c)Elj&PNv8x z7&J{oTr#1l_;1LJV>l1L*STi+O1(NGJXV79Jr9(QD%jB*$}7WaSm_Z_h#Pry_uVrJ`QKR z6GbDCm0Y2EBd&#v5|1%P0ZhYL&}iJ?$<1O^su?Cx7u^e8bcYi_Ln?#k3T1rFlT(l8 zY26N>B1{Jh5NNE1hWK5LsO&uHrmtq`s32ZWWmPVh)EW{%>`7!vV$lijW`Dw+WWsRFSfh20BJ!R{>k|Fhab0Zq6p1SfWP{LpC=PLDF} zFhZ3X@n7!|+)`DQvwG#h+Ve+0UM#LHTwPs!xi%U4{^m)UfWuWzcOaW22nafu2z=o> z2L`&&DP_h$iLQbMGf@|Fh>6V0)9nY(bo{AoeTkGOdT|B)mVLJ4C!v;BR)UQn=SPM3QG3O^` z$7k97L3T$vqbM>Q_u{wJc}h~Y|4H%Wcza7E9Z597wLREELQk?yS(^b diff --git a/script/ScriptManager.py b/script/ScriptManager.py index 52d5809..fb14d83 100644 --- a/script/ScriptManager.py +++ b/script/ScriptManager.py @@ -224,6 +224,11 @@ class ScriptManager(): self.comment_flow(filePath, session, udid, recomend_cx, recomend_cy) event.wait(timeout=2) + + home = AiUtils.findHomeButton(session) + if not home: + raise Exception("没有找到首页按钮,重置") + videoTime = random.randint(15, 30) for _ in range(videoTime): if event.is_set(): diff --git a/script/__pycache__/ScriptManager.cpython-312.pyc b/script/__pycache__/ScriptManager.cpython-312.pyc index 6ba832bcc115bfb5ce06a5f98c5242d95056aaea..f52717fdae225adf5dd2fe60a2b72257cc0769a7 100644 GIT binary patch delta 7126 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 delta 6497 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