import uuid import socket import time import logging import subprocess import platform from concurrent.futures import ThreadPoolExecutor from datetime import datetime, timedelta from threading import Event # Configure logging # logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) class NetworkException(Exception): """网络异常:连续10分钟无网络时抛出""" def __init__(self, message, mac_address=None, ip_address=None): self.mac_address = mac_address self.ip_address = ip_address super().__init__(message) # ========================================== # 1. 简单的网络检测函数(用于 main 循环) # ========================================== def get_mac_address(): """获取本机 MAC 地址""" mac = uuid.getnode() return ':'.join(('%012X' % mac)[i:i+2] for i in range(0, 12, 2)) def get_ip_address(): """获取本机 IP 地址""" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: s.connect(('10.255.255.255', 1)) IP = s.getsockname()[0] except Exception: IP = '127.0.0.1' finally: s.close() return IP def check_network(host="8.8.8.8", timeout=3): """ 简单的网络连接检测函数(同步,不抛异常) 使用 ping 命令检测网络连通性 :param host: 目标主机,默认 8.8.8.8 (Google DNS) :param timeout: 超时时间(秒),默认3秒 :return: True 表示网络正常,False 表示网络异常 """ try: # 根据操作系统选择 ping 参数 if platform.system().lower() == "windows": # Windows: -n 次数, -w 超时(毫秒) cmd = ["ping", "-n", "1", "-w", str(timeout * 1000), host] else: # Linux/Mac: -c 次数, -W 超时(秒) cmd = ["ping", "-c", "1", "-W", str(timeout), host] result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) return result.returncode == 0 except Exception: return False def wait_for_network(check_interval=5, max_wait_time=None): """ 等待网络恢复(阻塞式) :param check_interval: 检查间隔(秒) :param max_wait_time: 最大等待时间(秒),None 表示无限等待 :return: True 表示网络已恢复,False 表示超时 """ mac = get_mac_address() ip = get_ip_address() start_time = datetime.now() logger.warning(f"网络连接失败,等待网络恢复... (MAC: {mac}, IP: {ip})") while True: if check_network(): logger.info(f"网络已恢复正常 (MAC: {mac}, IP: {ip})") return True if max_wait_time: elapsed = (datetime.now() - start_time).total_seconds() if elapsed >= max_wait_time: logger.error(f"等待网络恢复超时 ({max_wait_time}秒)") return False time.sleep(check_interval) # ========================================== # 2. 后台网络监控(用于 run 方法) # ========================================== class NetworkWatchdog: """后台网络监控,使用独立线程检测网络状态,断网10分钟后抛出异常""" def __init__(self, timeout_minutes=10, check_interval=600): """ 初始化后台网络监控 :param timeout_minutes: 连续无网络超时时间(分钟),默认10分钟 :param check_interval: 检查间隔(秒),默认5秒 """ self.mac = get_mac_address() self.ip = get_ip_address() self.timeout_minutes = timeout_minutes self.check_interval = check_interval self.network_lost_time = None # 记录网络断开的开始时间 self._stop_event = Event() self._executor = ThreadPoolExecutor(max_workers=1) self._future = None self._exception = None # 存储监控线程中发生的异常 def _monitor_loop(self): """后台监控循环(在独立线程中运行)""" logger.info(f"网络监控后台线程已启动 (超时: {self.timeout_minutes}分钟)") try: while not self._stop_event.is_set(): self.ip = get_ip_address() if check_network(): if self.network_lost_time is not None: logger.info(f"网络已恢复正常 (MAC: {self.mac}, IP: {self.ip})") self.network_lost_time = None self._stop_event.wait(self.check_interval) else: current_time = datetime.now() if self.network_lost_time is None: self.network_lost_time = current_time logger.warning(f"检测到网络断开 (MAC: {self.mac}, IP: {self.ip}),开始计时...") disconnected_duration = current_time - self.network_lost_time if disconnected_duration >= timedelta(minutes=self.timeout_minutes): error_msg = f"网络连续断开超过 {self.timeout_minutes} 分钟" logger.error(f"{error_msg} (MAC: {self.mac}, IP: {self.ip})") self._exception = NetworkException( error_msg, mac_address=self.mac, ip_address=self.ip ) break logger.warning( f"网络仍然断开,已持续 {disconnected_duration.total_seconds():.0f} 秒 " f"(MAC: {self.mac}, IP: {self.ip})" ) self._stop_event.wait(5) except Exception as e: logger.error(f"网络监控线程出现异常: {e}") self._exception = e finally: logger.info("网络监控后台线程已停止") def start(self): """启动后台监控""" if self._future is not None: logger.warning("网络监控已经在运行中") return self._stop_event.clear() self._exception = None self._future = self._executor.submit(self._monitor_loop) def stop(self): """停止后台监控""" if self._future is None: return logger.info("正在停止网络监控...") self._stop_event.set() # 等待线程结束 try: self._future.result(timeout=10) except Exception as e: logger.error(f"等待监控线程结束时出错: {e}") self._future = None def check_and_raise(self): """ 检查监控线程是否检测到网络异常,如果有则抛出 :raises NetworkException: 当检测到网络连续断开超过指定时间时抛出 """ if self._exception is not None: raise self._exception def shutdown(self): """停止监控并关闭线程池""" self.stop() self._executor.shutdown(wait=False) logger.info("网络监控资源已清理") # ========================================== # 3. 兼容旧版本的 NetworkMonitor 类 # ========================================== class NetworkMonitor: """ 兼容旧版本的 NetworkMonitor 类 内部使用 NetworkWatchdog 实现 """ def __init__(self, timeout_minutes=10): self.watchdog = NetworkWatchdog(timeout_minutes=timeout_minutes) self.mac = self.watchdog.mac self.ip = self.watchdog.ip def check_network_with_timeout(self): """兼容方法:检查并抛出异常""" self.watchdog.check_and_raise() def shutdown(self): """兼容方法:关闭资源""" self.watchdog.shutdown() if __name__ == "__main__": print("=== 测试1: 简单网络检测 ===") mac = get_mac_address() ip = get_ip_address() print(f"MAC: {mac}, IP: {ip}") if check_network(): print("✓ 网络连接正常") else: print("✗ 网络连接失败") print("\n=== 测试2: 后台网络监控 (30秒超时) ===") watchdog = NetworkWatchdog(timeout_minutes=0.5, check_interval=2) # 30秒超时,2秒检查一次 try: watchdog.start() print("后台监控已启动,模拟任务执行中...") # 模拟任务执行 for i in range(20): time.sleep(2) print(f"[{i+1}] 任务执行中...") # 检查是否有网络异常 try: watchdog.check_and_raise() except NetworkException as e: print(f"\n✗ 捕获到网络异常: {e}") print(f" MAC: {e.mac_address}, IP: {e.ip_address}") break else: print("\n✓ 任务执行完成,无网络异常") finally: watchdog.shutdown()