合并代码

This commit is contained in:
2025-09-16 15:31:55 +08:00
parent b91aa99048
commit a0e1f9ef8d
3 changed files with 88 additions and 27 deletions

View File

@@ -174,57 +174,54 @@ class Deviceinfo(object):
try:
self.manager.send(model.toDict())
except Exception as e:
LogManager.warning(f"发送上线事件失败:{e}", model.deviceId)
LogManager.info(f"设备上线,当前在线数:{len(self.deviceModelList)}", model.deviceId)
LogManager.warning(f"{model.deviceId} 发送上线事件失败:{e}")
LogManager.info(f"{model.deviceId} 设备上线,当前在线数:{len(self.deviceModelList)}")
# 删除设备
def _remove_model(self, udid: str):
print("进入删除方法")
"""线程安全把设备从内存、端口、iproxy 完全抹掉"""
model = self._model_index.pop(udid, None)
print(model)
if not model:
print("没有找到model")
LogManager.error(f"没有找到需要删除的设备 {udid}")
return
model.type = 2
# 内存结构删除
# 1. 内存列表删除
try:
idx = self.deviceModelList.index(model)
self.deviceModelList.pop(idx)
self.deviceModelList.remove(model)
except ValueError:
print("有错误了")
pass
# 端口回收(关键)
# 2. 端口回收
self._free_port(model.screenPort)
# 清理 iproxy
survivors = [item for item in self.pidList if item.get("id") != udid]
# 3. 原子化清理 iproxy(确保进程彻底死)
to_kill, survivors = [], []
for item in self.pidList:
if item.get("id") == udid:
self._terminate_proc(item.get("target"))
self.pidList = survivors
(to_kill if item.get("id") == udid else survivors).append(item)
# Socket 发送(无锁)
for item in to_kill:
self._terminate_proc(item.get("target")) # 下面也给了加强版实现
self.pidList = survivors
LogManager.info(f"设备下线 {udid},当前在线数:{len(self.deviceModelList)}")
# 4. 发 Socket
retry = 3
while retry:
try:
self.manager.send(model.toDict())
print("删除了")
break
except Exception as e:
print("有问题了", e)
retry -= 1
LogManager.error(f"发送下线事件失败,剩余重试 {retry}{e}", udid)
LogManager.error(f"发送下线事件失败,{udid} 剩余重试 {retry}{e}")
time.sleep(0.2)
else:
LogManager.error("发送下线事件彻底失败,主动崩溃防止状态不一致", udid)
LogManager.error(f"发送下线事件彻底失败,{udid} 主动崩溃防止状态不一致")
os._exit(1)
LogManager.info(f"设备下线,当前在线数:{len(self.deviceModelList)}", udid)
# region ===================== 端口分配与回收 =====================
def _alloc_port(self) -> int:
print(self.screenProxy)
if self._port_pool:
port = self._port_pool.pop()
else:
@@ -289,17 +286,63 @@ class Deviceinfo(object):
return False
def relayDeviceScreenPort(self, udid: str, port: int) -> Optional[subprocess.Popen]:
"""启动 iproxy 前:端口若仍被占用则先杀掉占用者,再启动"""
if not self._spawn_iproxy:
LogManager.error("iproxy 启动器未就绪,无法建立端口映射", udid)
LogManager.error("iproxy 启动器未就绪", udid)
return None
# --- 新增:端口冲突检查 + 强制清理 ---
while self._port_in_use and self._is_port_open(port):
# 先查是哪个进程占用
pid = self._get_pid_by_port(port)
if pid and pid != os.getpid():
LogManager.warning(f"端口 {port} 仍被 PID {pid} 占用,尝试释放", udid)
self._kill_pid_gracefully(pid)
else:
break
# -------------------------------------
try:
p = self._spawn_iproxy(udid, port, 9100)
self._port_in_use.add(port)
LogManager.info(f"启动 iproxy 成功,本地 {port} -> 设备 9100", udid)
return p
except Exception as e:
LogManager.error(f"启动 iproxy 失败:{e}", udid)
return None
# ------------------- 新增三个小工具 -------------------
def _is_port_open(self, port: int) -> bool:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(("127.0.0.1", port)) == 0
def _get_pid_by_port(self, port: int) -> Optional[int]:
"""跨平台根据端口号查 PID失败返回 None"""
try:
if os.name == "nt":
cmd = ["netstat", "-ano", "-p", "tcp"]
out = subprocess.check_output(cmd, text=True)
for line in out.splitlines():
if f"127.0.0.1:{port}" in line and "LISTENING" in line:
return int(line.strip().split()[-1])
else:
cmd = ["lsof", "-t", f"-iTCP:{port}", "-sTCP:LISTEN"]
out = subprocess.check_output(cmd, text=True)
return int(out.strip().split()[0])
except Exception:
return None
def _kill_pid_gracefully(self, pid: int):
"""先 terminate 再 kill -9"""
try:
os.kill(pid, signal.SIGTERM)
time.sleep(1)
os.kill(pid, signal.SIGKILL)
except Exception:
pass
def _terminate_proc(self, p: Optional[subprocess.Popen]):
if not p or p.poll() is not None:
return
@@ -328,5 +371,4 @@ class Deviceinfo(object):
for p in candidates:
if p.exists():
return p
raise FileNotFoundError(f"iproxy not found, tried: {[str(c) for c in candidates]}")
# endregion
raise FileNotFoundError(f"iproxy not found, tried: {[str(c) for c in candidates]}")

View File

@@ -63,7 +63,6 @@ def start_socket_listener():
while True:
try:
conn, addr = s.accept()
LogManager.info(f"[INFO] Connection from {addr}")
except Exception as e:
LogManager.error(f"[ERROR] accept 失败: {e}")
continue

View File

@@ -1,4 +1,6 @@
import random
import re
import time
import tidevice
import wda
@@ -177,7 +179,25 @@ class ControlUtils(object):
session.tap(left_x, center_y)
# 点击一个随机范围
@classmethod
def tap_mini_cluster(cls, center_x: int, center_y: int, session, points=5, duration_ms=60):
"""
以 (center_x, center_y) 为中心,在 3×3 像素范围内摆 `points` 个邻近坐标,
一次性同时按下,持续 `duration_ms` 后抬起。
"""
# 1. 生成邻像素坐标±1 像素内)
cluster = []
for i in range(points):
dx = random.randint(-1, 1)
dy = random.randint(-1, 1)
cluster.append((center_x + dx, center_y + dy))
# 2. 随机持续时间 50100 ms
duration_s = random.randint(50, 100) / 1000.0
# 3. 下发多点触控
session.tap_hold(cluster, duration=duration_s)
# 检测五分钟前和当前的状态是否相同
# @classmethod