265 lines
9.0 KiB
Python
265 lines
9.0 KiB
Python
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()
|