streams.h问题汇总

本文介绍了如何解决在Visual C++.NET中找不到streams.h文件的问题,包括如何正确设置IncludeDirectories路径和编译DirectShow Base Classes库。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

streams.h问题汇总

找不到streams.h的解决方法:

streams.h这个头文件现在在%Platform SDK Root%/Samples/Multimedia/DirectShow/BaseClasses路径下,首先要把这个路径添加到VC的Include Directories下面。

注:需要下载streams.h的朋友们注意了,其实你根本不用下载。

最令我ft的是DirectShow的library,真不知道M$的人是不是脑子坏掉了,库居然要自己编译。一开始我翻遍了硬盘也没找到 strmbasd.lib,最后在BaseClasses目录下看到一个makefile,才知道是怎么回事。扯远了,打住。打开SDK的command line debug build environment ,进入%Platform SDK Root%/Samples/Multimedia/DirectShow/BaseClasses目录,二话不说nmake,生成一个 XP32_DEBUG目录(我选的是Set Windows XP 32-bit Build Environment (Debug)),strmbasd.lib赫然在目,这就是debug版的库,retail版的如法炮制,生成的库在XP32_RETAIL目录下,名 为strmbase.lib。

2.请问下面的问题是怎么回事儿(vc.net下,连接了Strmiids.lib   Quartz.lib   strmbasd.lib   uuid.lib           winmm.lib):  
是不是需要指定路径阿。。这些库文件在哪个目录阿,  
fatal   error   C1083:   无法打开包含文件:“streams.h”:   No   such   file   or   directory 

回答:

1   build   the   BaseClasses   project   in   DirectSDK/Samples/C++/DirectShow/BaseClasses  
2   add   the   baseclasses   dir   to   your   include   path

1、测试组新增存在BUG,新增的测试组还是测试组1,而且删除后图标中原本的测试组1选项(实际只删除了新增的假测试组1,原本的测试组1还在),也没了。说明新增测试组的这里,序号有问题。2、报错,严重BUG,阻塞运行Traceback (most recent call last): File "e:\VS Python\Iperf3 GUI Tool_DS.py", line 1095, in start_test server_port = group_widget.server_port_spin.value() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: 'QGroupBox' object has no attribute 'server_port_spin' 源代码: import sys import os import ctypes import json import subprocess import threading import time import psutil import pythoncom from datetime import datetime, timedelta from PyQt5.QtWidgets import ( QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QCheckBox, QComboBox, QSpinBox, QGroupBox, QScrollArea, QTextEdit, QFileDialog, QMessageBox, QDoubleSpinBox, QSizePolicy, QListWidget, QAbstractItemView, QListWidgetItem ) from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer from PyQt5.QtGui import QColor, QIcon import wmi # Matplotlib 导入 import matplotlib matplotlib.use('Qt5Agg') import matplotlib.pyplot as plt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure import matplotlib.dates as mdates from matplotlib import ticker """# 检查管理员权限并请求提升 def is_admin(): try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False if not is_admin(): # 请求管理员权限 ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1) sys.exit(0)""" # 设置中文字体支持 plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'SimSun', 'KaiTi'] # 设置中文字体 plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 # 网卡事件监听线程 class NicMonitorThread(QThread): nic_event = pyqtSignal(dict) # 发送事件:{'name': 网卡名, 'event': 'up' or 'down', 'time': 时间字符串} error_signal = pyqtSignal(str) # 错误信号 def __init__(self): super().__init__() self.running = True self.c = None self.watcher = None self.last_status = {} # 记录每个网卡的最后状态 def init_wmi(self): """初始化WMI连接""" try: pythoncom.CoInitialize() # 添加COM初始化 self.c = wmi.WMI() return True except Exception as e: self.error_signal.emit(f"无法初始化WMI连接: {str(e)}") return False def run(self): # 创建事件监听 if not self.init_wmi(): self.error_signal.emit("网卡事件监听器初始化失败") return try: # 获取当前网卡状态作为初始状态 for adapter in self.c.Win32_NetworkAdapter(NetConnectionStatus=2): self.last_status[adapter.Name] = 'up' for adapter in self.c.Win32_NetworkAdapter(NetConnectionStatus=7): self.last_status[adapter.Name] = 'down' # 创建事件监听器 self.watcher = self.c.watch_for( notification_type="__InstanceModificationEvent", wmi_class="Win32_NetworkAdapter", delay_secs=0.1, # 更短的延迟以提高响应速度 fields=["NetConnectionStatus", "Name"] ) while self.running: try: adapter = self.watcher() if adapter: name = adapter.Name status = adapter.NetConnectionStatus event_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] # 只处理状态变化 if status == 2 and self.last_status.get(name) != 'up': self.nic_event.emit({'name': name, 'event': 'up', 'time': event_time}) self.last_status[name] = 'up' elif status == 7 and self.last_status.get(name) != 'down': self.nic_event.emit({'name': name, 'event': 'down', 'time': event_time}) self.last_status[name] = 'down' except wmi.x_wmi_timed_out: # 超时是正常的,继续循环 pass except Exception as e: self.error_signal.emit(f"监听网卡事件出错: {str(e)}") # 尝试重新初始化WMI连接 time.sleep(1) if not self.init_wmi(): break except Exception as e: self.error_signal.emit(f"创建网卡监听器出错: {str(e)}") finally: pythoncom.CoUninitialize() # 清理COM资源 def stop(self): self.running = False if self.watcher: self.watcher.cancel() # iperf3 进程管理器 class IperfManager: def __init__(self, iperf_path=''): self.active_servers = {} self.active_clients = {} self.iperf_path = iperf_path or 'iperf3' def start_server(self, group_id, port, server_ip, nic_name=""): """启动iperf3服务器""" # 使用服务器IP地址作为绑定的IP地址 if not server_ip or not self.validate_ip(server_ip): raise ValueError(f"测试组{group_id}服务器IP地址无效: {server_ip}") command = f'"{self.iperf_path}" -s -p {port} -B {server_ip} -J --logfile server_{group_id}_{port}.log' proc = subprocess.Popen(command, shell=True) self.active_servers[(group_id, port)] = (proc, nic_name) # 保存网卡名称用于日志 return command def start_client(self, group_id, server_ip, port, duration, streams, client_ip, reverse=False, omit=5, nic_name=""): """启动iperf3客户端""" # 使用客户端IP地址作为绑定的IP地址 if not client_ip or not self.validate_ip(client_ip): raise ValueError(f"测试组{group_id}客户端IP地址无效: {client_ip}") if not server_ip or not self.validate_ip(server_ip): raise ValueError(f"测试组{group_id}服务器IP地址无效: {server_ip}") direction = "-R" if reverse else "" # 添加-B参数绑定客户端IP,-c参数连接服务器IP command = f'"{self.iperf_path}" -c {server_ip} -p {port} -t {duration} -P {streams} {direction} -O {omit} -B {client_ip} -J --logfile client_{group_id}_{port}.json' proc = subprocess.Popen(command, shell=True) self.active_clients[(group_id, port)] = (proc, nic_name) # 保存网卡名称用于日志 return command def validate_ip(self, ip_str): """验证IP地址格式""" if not ip_str: return False parts = ip_str.split('.') if len(parts) != 4: return False for part in parts: if not part.isdigit(): return False num = int(part) if num < 0 or num > 255: return False return True def stop_all(self): """停止所有iperf进程""" for key, (proc, _) in list(self.active_servers.items()) + list(self.active_clients.items()): try: parent = psutil.Process(proc.pid) for child in parent.children(recursive=True): child.kill() parent.kill() except Exception as e: print(f"停止进程时出错: {e}") self.active_servers = {} self.active_clients = {} # 实时数据收集线程 class DataCollectorThread(QThread): data_updated = pyqtSignal(dict) # {group_id: {timestamp: (up, down)}} test_completed = pyqtSignal(int, dict) # group_id, full_data test_timeout = pyqtSignal() # 测试超时信号 def __init__(self, manager, groups, parent=None): super().__init__(parent) self.manager = manager self.groups = groups self.running = True self.data = {} self.full_data = {} self.start_time = time.time() self.max_duration = 0 # 记录最长测试时间(秒) self.timeout_timer = None # 计算最长测试时间(用于超时检测) for config in groups.values(): if config['duration'] > self.max_duration: self.max_duration = config['duration'] def run(self): # 初始化数据结构 for group_id in self.groups: self.data[group_id] = {"up": {}, "down": {}} self.full_data[group_id] = {"up": {}, "down": {}} # 设置超时计时器(最长测试时间+5秒) self.timeout_timer = threading.Timer(self.max_duration + 5, self.handle_timeout) self.timeout_timer.start() # 主采集循环 while self.running: for group_id, config in self.groups.items(): # 处理上传日志 upload_log = f"client_{group_id}_{config['upload_port']}.json" if os.path.exists(upload_log): try: with open(upload_log, 'r') as f: data = json.load(f) # 解析上传数据 if 'intervals' in data: for interval in data['intervals']: ts = time.time() up = interval['sum']['bits_per_second'] / 1e6 # 转换为Mbps # 存储实时数据 self.data[group_id]["up"][ts] = up self.full_data[group_id]["up"][ts] = up except Exception as e: print(f"读取上传日志文件错误: {e}") # 处理下载日志(如果是双向测试) if config['direction'] in ["下载", "双向"]: download_log = f"client_{group_id}_{config['download_port']}.json" if os.path.exists(download_log): try: with open(download_log, 'r') as f: data = json.load(f) # 解析下载数据 if 'intervals' in data: for interval in data['intervals']: ts = time.time() down = interval['sum']['bits_per_second'] / 1e6 # 存储实时数据 self.data[group_id]["down"][ts] = down self.full_data[group_id]["down"][ts] = down except Exception as e: print(f"读取下载日志文件错误: {e}") # 发送更新信号 self.data_updated.emit(self.data) time.sleep(1) # 每秒更新一次 # 测试完成后发送完整数据 for group_id in self.groups: self.test_completed.emit(group_id, self.full_data[group_id]) def handle_timeout(self): """处理测试超时""" self.running = False self.test_timeout.emit() def stop(self): self.running = False if self.timeout_timer: self.timeout_timer.cancel() # Matplotlib 图表组件(支持中文) class MplCanvas(FigureCanvas): def __init__(self, parent=None, width=5, height=4, dpi=100): self.fig = Figure(figsize=(width, height), dpi=dpi, facecolor=(0.94, 0.97, 1.0)) # 淡蓝色背景 self.ax = self.fig.add_subplot(111) super().__init__(self.fig) self.setParent(parent) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.updateGeometry() # 初始化图表(中文) self.ax.set_xlabel('时间 (秒)') self.ax.set_ylabel('吞吐量 (Mbps)') self.ax.set_title('iPerf3 流量监控') self.ax.grid(True) self.ax.set_facecolor((0.98, 0.99, 1.0)) # 更淡的蓝色背景 self.relative_time = True # 默认使用相对时间 self.start_time = None self.end_time = None self.start_timestamp = None def update_plot(self, data, selected_groups=None): """更新图表数据""" self.ax.clear() # 颜色列表 colors = [ '#4169E1', # RoyalBlue '#4682B4', # SteelBlue '#6495ED', # CornflowerBlue '#1E90FF', # DodgerBlue '#00BFFF' # DeepSkyBlue ] max_value = 0 min_timestamp = float('inf') max_timestamp = float('-inf') for group_id, group_data in data.items(): if selected_groups is not None and group_id not in selected_groups: continue color = colors[(group_id-1) % len(colors)] # 上传数据(实线) if group_data["up"]: timestamps = sorted(group_data["up"].keys()) values = [group_data["up"][ts] for ts in timestamps] # 更新时间范围 if timestamps: min_timestamp = min(min_timestamp, min(timestamps)) max_timestamp = max(max_timestamp, max(timestamps)) if not self.relative_time and self.start_time: # 转换为绝对时间 abs_timestamps = [self.start_time + timedelta(seconds=ts - self.start_timestamp) for ts in timestamps] self.ax.plot(abs_timestamps, values, '-', color=color, label=f"组{group_id} 上传", linewidth=2) else: # 相对时间(从0开始) rel_timestamps = [ts - min_timestamp for ts in timestamps] self.ax.plot(rel_timestamps, values, '-', color=color, label=f"组{group_id} 上传", linewidth=2) if values: max_value = max(max_value, max(values)) # 下载数据(虚线) if group_data["down"]: timestamps = sorted(group_data["down"].keys()) values = [group_data["down"][ts] for ts in timestamps] # 更新时间范围 if timestamps: min_timestamp = min(min_timestamp, min(timestamps)) max_timestamp = max(max_timestamp, max(timestamps)) if not self.relative_time and self.start_time: abs_timestamps = [self.start_time + timedelta(seconds=ts - self.start_timestamp) for ts in timestamps] self.ax.plot(abs_timestamps, values, '--', color=color, label=f"组{group_id} 下载", linewidth=2) else: rel_timestamps = [ts - min_timestamp for ts in timestamps] self.ax.plot(rel_timestamps, values, '--', color=color, label=f"组{group_id} 下载", linewidth=2) if values: max_value = max(max_value, max(values)) # 设置坐标轴范围 if max_value > 0: self.ax.set_ylim(0, max_value * 1.2) # 添加图例(中文) self.ax.legend(loc='best', fontsize=9) # 设置标签和标题(中文) if self.relative_time: self.ax.set_xlabel('时间 (秒)') else: self.ax.set_xlabel('时间') # 设置时间格式 self.ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) self.ax.xaxis.set_major_locator(ticker.AutoLocator()) self.ax.set_ylabel('吞吐量 (Mbps)') self.ax.set_title('iPerf3 流量监控') self.ax.grid(True) self.draw() def switch_to_absolute_time(self, start_time, end_time): """切换到绝对时间模式""" self.relative_time = False self.start_time = start_time self.end_time = end_time # 记录测试开始时的第一个时间戳(作为相对时间的零点) if self.start_time: self.start_timestamp = time.time() - (datetime.now() - start_time).total_seconds() # 主应用窗口 class IperfVisualizer(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("iPerf3 流量可视化工具") self.setWindowIcon(QIcon('iperf_icon.ico')) # 设置软件图标 self.resize(1400, 900) # 应用配置 self.config = { 'iperf_path': '', 'auto_save_log': True, 'log_path': os.getcwd(), 'test_groups': {}, 'current_group_id': 1 } # 初始化管理器 self.manager = None self.data_collector = None self.nic_monitor = None self.nic_events = [] # 存储网卡事件 self.nic_summary = {} # 网卡事件汇总: {网卡名: {'down': 次数, 'up': 次数, 'last_status': 最后状态}} self.nic_map = {} # 网卡到测试组映射: {网卡名: [(group_id, port, role)]} # 设置UI self.init_ui() self.load_config() # 启动网卡事件监听 self.start_nic_monitor() def init_ui(self): # 主布局 main_widget = QWidget() main_layout = QVBoxLayout() main_widget.setLayout(main_layout) self.setCentralWidget(main_widget) # 标签页 self.tabs = QTabWidget() main_layout.addWidget(self.tabs) # 配置页 self.config_tab = QWidget() self.tabs.addTab(self.config_tab, "测试配置") self.setup_config_tab() # 图表页 self.chart_tab = QWidget() self.tabs.addTab(self.chart_tab, "流量图表") self.setup_chart_tab() # 信息页 self.info_tab = QWidget() self.tabs.addTab(self.info_tab, "测试信息") self.setup_info_tab() # 状态栏 self.statusBar().showMessage("就绪") def setup_config_tab(self): layout = QVBoxLayout() self.config_tab.setLayout(layout) # iperf路径配置 iperf_group = QGroupBox("iperf3 配置") iperf_layout = QVBoxLayout() path_layout = QHBoxLayout() path_layout.addWidget(QLabel("iperf3路径:")) self.iperf_path = QLineEdit(self.config['iperf_path']) path_layout.addWidget(self.iperf_path) self.iperf_browse_btn = QPushButton("浏览...") self.iperf_browse_btn.clicked.connect(self.browse_iperf_path) path_layout.addWidget(self.iperf_browse_btn) iperf_layout.addLayout(path_layout) # 测试按钮 test_btn_layout = QHBoxLayout() self.test_iperf_btn = QPushButton("测试路径") self.test_iperf_btn.clicked.connect(self.test_iperf_path) test_btn_layout.addWidget(self.test_iperf_btn) iperf_layout.addLayout(test_btn_layout) iperf_group.setLayout(iperf_layout) layout.addWidget(iperf_group) # 测试描述 desc_group = QGroupBox("测试描述") desc_layout = QVBoxLayout() self.test_desc = QLineEdit("iPerf3流量测试") desc_layout.addWidget(QLabel("测试描述:")) desc_layout.addWidget(self.test_desc) desc_group.setLayout(desc_layout) layout.addWidget(desc_group) # 日志设置 log_group = QGroupBox("日志设置") log_layout = QVBoxLayout() self.auto_save = QCheckBox("自动保存测试日志") self.auto_save.setChecked(True) log_layout.addWidget(self.auto_save) path_layout = QHBoxLayout() path_layout.addWidget(QLabel("保存路径:")) self.log_path = QLineEdit(os.getcwd()) path_layout.addWidget(self.log_path) self.browse_btn = QPushButton("浏览...") self.browse_btn.clicked.connect(self.browse_log_path) path_layout.addWidget(self.browse_btn) log_layout.addLayout(path_layout) log_group.setLayout(log_layout) layout.addWidget(log_group) # 测试组配置 self.test_groups_area = QScrollArea() self.test_groups_area.setWidgetResizable(True) self.test_groups_widget = QWidget() self.test_groups_layout = QVBoxLayout() self.test_groups_widget.setLayout(self.test_groups_layout) self.test_groups_area.setWidget(self.test_groups_widget) layout.addWidget(QLabel("测试组配置:")) layout.addWidget(self.test_groups_area) # 添加测试组按钮 self.add_group_btn = QPushButton("添加测试组") self.add_group_btn.clicked.connect(self.add_test_group) layout.addWidget(self.add_group_btn) # 操作按钮 btn_layout = QHBoxLayout() self.start_btn = QPushButton("开始测试") self.start_btn.clicked.connect(self.start_test) btn_layout.addWidget(self.start_btn) self.stop_btn = QPushButton("停止测试") self.stop_btn.clicked.connect(self.stop_test) self.stop_btn.setEnabled(False) btn_layout.addWidget(self.stop_btn) layout.addLayout(btn_layout) # 添加初始测试组 self.add_test_group() def setup_chart_tab(self): layout = QVBoxLayout() self.chart_tab.setLayout(layout) # 使用 Matplotlib 图表(支持中文) self.canvas = MplCanvas(self, width=10, height=6, dpi=100) layout.addWidget(self.canvas, 3) # 图表占3/4空间 # 测试组选择面板 group_select_layout = QHBoxLayout() # 测试组列表 group_list_layout = QVBoxLayout() group_list_layout.addWidget(QLabel("选择显示的测试组:")) self.group_list = QListWidget() self.group_list.setSelectionMode(QAbstractItemView.MultiSelection) # 多选 group_list_layout.addWidget(self.group_list) # 选择按钮 btn_layout = QHBoxLayout() self.select_all_btn = QPushButton("全选") self.select_all_btn.clicked.connect(self.select_all_groups) btn_layout.addWidget(self.select_all_btn) self.deselect_all_btn = QPushButton("全不选") self.deselect_all_btn.clicked.connect(self.deselect_all_groups) btn_layout.addWidget(self.deselect_all_btn) group_list_layout.addLayout(btn_layout) group_select_layout.addLayout(group_list_layout, 1) # 占1/4宽度 # 图表操作按钮 chart_btn_layout = QVBoxLayout() chart_btn_layout.addWidget(QLabel("图表操作:")) self.export_btn = QPushButton("导出图表") self.export_btn.clicked.connect(self.export_chart) chart_btn_layout.addWidget(self.export_btn) self.clear_btn = QPushButton("清除图表") self.clear_btn.clicked.connect(self.clear_chart) chart_btn_layout.addWidget(self.clear_btn) self.zoom_reset_btn = QPushButton("重置缩放") self.zoom_reset_btn.clicked.connect(self.reset_zoom) chart_btn_layout.addWidget(self.zoom_reset_btn) group_select_layout.addLayout(chart_btn_layout, 1) # 占1/4宽度 layout.addLayout(group_select_layout, 1) # 控制面板占1/4高度 # 初始化后更新组列表 self.update_group_list() def setup_info_tab(self): layout = QVBoxLayout() self.info_tab.setLayout(layout) # 执行日志 exec_group = QGroupBox("执行日志") exec_layout = QVBoxLayout() self.exec_log = QTextEdit() self.exec_log.setReadOnly(True) exec_layout.addWidget(self.exec_log) btn_layout = QHBoxLayout() self.clear_log_btn = QPushButton("清除日志") self.clear_log_btn.clicked.connect(lambda: self.exec_log.clear()) btn_layout.addWidget(self.clear_log_btn) self.export_log_btn = QPushButton("导出日志") self.export_log_btn.clicked.connect(self.export_log) btn_layout.addWidget(self.export_log_btn) exec_layout.addLayout(btn_layout) exec_group.setLayout(exec_layout) layout.addWidget(exec_group) # 网卡状态 nic_group = QGroupBox("网卡状态监控") nic_layout = QVBoxLayout() self.nic_status = QTextEdit() self.nic_status.setReadOnly(True) nic_layout.addWidget(self.nic_status) # 网卡事件摘要 self.nic_summary_text = QTextEdit() self.nic_summary_text.setReadOnly(True) nic_layout.addWidget(QLabel("网卡事件摘要:")) nic_layout.addWidget(self.nic_summary_text) # 网卡映射 self.nic_mapping_text = QTextEdit() self.nic_mapping_text.setReadOnly(True) nic_layout.addWidget(QLabel("网卡测试组映射:")) nic_layout.addWidget(self.nic_mapping_text) nic_btn_layout = QHBoxLayout() self.refresh_nic_btn = QPushButton("刷新状态") self.refresh_nic_btn.clicked.connect(self.update_nic_status) nic_btn_layout.addWidget(self.refresh_nic_btn) self.export_nic_btn = QPushButton("导出状态") self.export_nic_btn.clicked.connect(self.export_nic_status) nic_btn_layout.addWidget(self.export_nic_btn) self.clear_nic_btn = QPushButton("清除事件") self.clear_nic_btn.clicked.connect(self.clear_nic_events) nic_btn_layout.addWidget(self.clear_nic_btn) nic_layout.addLayout(nic_btn_layout) nic_group.setLayout(nic_layout) layout.addWidget(nic_group) # 初始更新网卡状态 self.update_nic_status(initial=True) def browse_iperf_path(self): """浏览并选择iperf3可执行文件""" file_path, _ = QFileDialog.getOpenFileName( self, "选择iperf3可执行文件", "", "可执行文件 (*.exe);;所有文件 (*)" ) if file_path: self.iperf_path.setText(file_path) self.config['iperf_path'] = file_path self.save_config() def test_iperf_path(self): """测试iperf3路径是否有效""" path = self.iperf_path.text() if not path: QMessageBox.warning(self, "警告", "请先选择iperf3路径") return try: result = subprocess.run( [path, '-v'], capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW ) if 'iperf 3.' in result.stdout: QMessageBox.information(self, "测试成功", f"iperf3版本信息:\n{result.stdout.splitlines()[0]}") self.config['iperf_path'] = path self.save_config() else: QMessageBox.critical(self, "错误", "选择的文件不是有效的iperf3可执行文件") except Exception as e: QMessageBox.critical(self, "错误", f"测试失败: {str(e)}") def start_nic_monitor(self): """启动网卡事件监听线程""" self.nic_monitor = NicMonitorThread() self.nic_monitor.nic_event.connect(self.handle_nic_event) self.nic_monitor.error_signal.connect(self.handle_nic_error) # 连接错误信号 self.nic_monitor.start() def handle_nic_error(self, error_msg): """处理网卡监听错误""" self.exec_log.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {error_msg}") # 如果无法使用事件监听,回退到轮询方式 if "无法初始化WMI连接" in error_msg or "创建网卡监听器出错" in error_msg: self.exec_log.append("将使用轮询方式监控网卡状态") self.start_polling_nic_status() def start_polling_nic_status(self): """启动轮询方式监控网卡状态""" self.last_nic_status = {} # 初始化状态记录 self.nic_poll_timer = QTimer() self.nic_poll_timer.timeout.connect(self.poll_nic_status) self.nic_poll_timer.start(1000) # 每秒轮询一次 def poll_nic_status(self): """轮询网卡状态""" try: c = wmi.WMI() # 获取所有网卡状态 for adapter in c.Win32_NetworkAdapter(): status = 'unknown' if adapter.NetConnectionStatus == 2: status = 'up' elif adapter.NetConnectionStatus == 7: status = 'down' # 只处理状态变化的网卡 if adapter.Name in self.last_nic_status and self.last_nic_status[adapter.Name] != status: event_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] self.handle_nic_event({'name': adapter.Name, 'event': status, 'time': event_time}) # 记录当前状态 self.last_nic_status[adapter.Name] = status except Exception as e: self.exec_log.append(f"轮询网卡状态出错: {str(e)}") def handle_nic_event(self, event): """处理网卡事件""" name = event['name'] event_type = event['event'] event_time = event['time'] # 检查网卡映射 affected_groups = [] if name in self.nic_map: for group_id, port, role in self.nic_map[name]: affected_groups.append(f"测试组{group_id}的{role}端口{port}") # 记录事件 event_msg = f"[{event_time}] 网卡 '{name}' 状态变化: {event_type}" if affected_groups: event_msg += f" (影响: {', '.join(affected_groups)})" self.nic_events.append(event_msg) # 更新汇总 if name not in self.nic_summary: self.nic_summary[name] = {'down': 0, 'up': 0, 'last_status': 'unknown'} if event_type == 'down': self.nic_summary[name]['down'] += 1 elif event_type == 'up': self.nic_summary[name]['up'] += 1 self.nic_summary[name]['last_status'] = event_type # 更新网卡状态显示 self.update_nic_status() def add_test_group(self): # 计算当前可用最小ID existing_ids = set(self.config['test_groups'].keys()) group_id = 1 while group_id in existing_ids: group_id += 1 if group_id > 30: QMessageBox.warning(self, "警告", "最多只能添加30个测试组") return group = QGroupBox(f"测试组 {group_id}") group.setProperty('group_id', group_id) # 设置属性用于标识 group_layout = QVBoxLayout() # 组名 name_layout = QHBoxLayout() name_layout.addWidget(QLabel("组名:")) group_name = QLineEdit(f"测试组{group_id}") name_layout.addWidget(group_name) group_layout.addLayout(name_layout) # Server配置 server_group = QGroupBox("Server配置") server_layout = QVBoxLayout() ip_layout = QHBoxLayout() ip_layout.addWidget(QLabel("IP地址*:")) server_ip = QLineEdit() server_ip.setPlaceholderText("必须填写服务器IP") ip_layout.addWidget(server_ip) server_layout.addLayout(ip_layout) port_layout = QHBoxLayout() port_layout.addWidget(QLabel("端口号:")) server_port = QSpinBox() server_port.setRange(1024, 65535) server_port.setValue(5201 + group_id - 1) port_layout.addWidget(server_port) server_layout.addLayout(port_layout) # 添加网卡选择 nic_layout = QHBoxLayout() nic_layout.addWidget(QLabel("绑定网卡:")) server_nic = QComboBox() server_nic.addItem("") # 空选项 # 获取系统网卡列表 try: c = wmi.WMI() for adapter in c.Win32_NetworkAdapter(NetConnectionStatus=2): # 2表示已连接 server_nic.addItem(adapter.Name) except Exception as e: print(f"获取网卡列表失败: {e}") nic_layout.addWidget(server_nic) server_layout.addLayout(nic_layout) server_group.setLayout(server_layout) group_layout.addWidget(server_group) # Client配置 client_group = QGroupBox("Client配置") client_layout = QVBoxLayout() c_ip_layout = QHBoxLayout() c_ip_layout.addWidget(QLabel("IP地址*:")) client_ip = QLineEdit() client_ip.setPlaceholderText("必须填写客户端IP") c_ip_layout.addWidget(client_ip) client_layout.addLayout(c_ip_layout) c_name_layout = QHBoxLayout() c_name_layout.addWidget(QLabel("客户端名称:")) client_name = QLineEdit(f"客户端{group_id}") c_name_layout.addWidget(client_name) client_layout.addLayout(c_name_layout) # 添加网卡选择 c_nic_layout = QHBoxLayout() c_nic_layout.addWidget(QLabel("绑定网卡:")) client_nic = QComboBox() client_nic.addItem("") # 空选项 # 获取系统网卡列表 try: c = wmi.WMI() for adapter in c.Win32_NetworkAdapter(NetConnectionStatus=2): # 2表示已连接 client_nic.addItem(adapter.Name) except Exception as e: print(f"获取网卡列表失败: {e}") c_nic_layout.addWidget(client_nic) client_layout.addLayout(c_nic_layout) client_group.setLayout(client_layout) group_layout.addWidget(client_group) # 测试参数 param_group = QGroupBox("测试参数") param_layout = QVBoxLayout() direction_layout = QHBoxLayout() direction_layout.addWidget(QLabel("测试方向:")) test_direction = QComboBox() test_direction.addItems(["上传", "下载", "双向"]) test_direction.currentIndexChanged.connect(lambda idx, gid=group_id: self.update_port_visibility(gid, idx)) direction_layout.addWidget(test_direction) param_layout.addLayout(direction_layout) # 下载端口配置(默认隐藏) download_port_layout = QHBoxLayout() download_port_layout.addWidget(QLabel("下载端口号:")) download_port = QSpinBox() download_port.setRange(1024, 65535) download_port.setValue(server_port.value() + 1) download_port_layout.addWidget(download_port) # 初始时隐藏下载端口配置(除非是双向测试) if test_direction.currentIndex() != 2: download_port_layout.itemAt(0).widget().hide() download_port_layout.itemAt(1).widget().hide() param_layout.addLayout(download_port_layout) time_layout = QHBoxLayout() time_layout.addWidget(QLabel("测试时长:")) test_time = QDoubleSpinBox() test_time.setRange(1, 10000) test_time.setValue(10) time_layout.addWidget(test_time) time_unit = QComboBox() time_unit.addItems(["秒", "分钟", "小时"]) time_layout.addWidget(time_unit) param_layout.addLayout(time_layout) omit_layout = QHBoxLayout() omit_layout.addWidget(QLabel("跳过前N秒:")) omit_seconds = QSpinBox() omit_seconds.setRange(0, 60) omit_seconds.setValue(5) omit_layout.addWidget(omit_seconds) param_layout.addLayout(omit_layout) streams_layout = QHBoxLayout() streams_layout.addWidget(QLabel("并行流数量:")) streams = QSpinBox() streams.setRange(1, 100) streams.setValue(1) streams_layout.addWidget(streams) param_layout.addLayout(streams_layout) auto_restart = QCheckBox("每6小时自动重跑") auto_restart.setChecked(True) param_layout.addWidget(auto_restart) error_restart = QCheckBox("异常自动重跑") error_restart.setChecked(True) param_layout.addWidget(error_restart) restart_count_layout = QHBoxLayout() restart_count_layout.addWidget(QLabel("重跑次数:")) restart_count = QSpinBox() restart_count.setRange(1, 10) restart_count.setValue(3) restart_count_layout.addWidget(restart_count) param_layout.addLayout(restart_count_layout) param_group.setLayout(param_layout) group_layout.addWidget(param_group) # 删除按钮 delete_btn = QPushButton("删除此组") delete_btn.clicked.connect(lambda: self.remove_test_group(group)) group_layout.addWidget(delete_btn) group.setLayout(group_layout) self.test_groups_layout.addWidget(group) # 将控件保存为group的属性,以便后续读取 group.server_ip_edit = server_ip group.client_ip_edit = client_ip # 保存配置(只保存控件值) self.config['test_groups'][group_id] = { 'name': group_name.text(), 'server_ip': server_ip.text(), # 确保保存的是文本内容 'server_port': server_port.value(), 'server_nic': server_nic.currentText(), # 保存网卡名称 'download_port': download_port.value(), 'client_ip': client_ip.text(), # 确保保存的是文本内容 'client_name': client_name.text(), 'client_nic': client_nic.currentText(), # 保存网卡名称 'direction': test_direction.currentIndex(), 'test_time': test_time.value(), 'time_unit': time_unit.currentIndex(), 'omit': omit_seconds.value(), 'streams': streams.value(), 'auto_restart': auto_restart.isChecked(), 'error_restart': error_restart.isChecked(), 'restart_count': restart_count.value() } # 更新组选择列表 if hasattr(self, 'group_list'): self.update_group_list() def update_port_visibility(self, group_id, direction_idx): """根据测试方向显示/隐藏下载端口配置""" # 找到对应的group_widget for child in self.test_groups_widget.children(): if isinstance(child, QGroupBox) and child.property('group_id') == group_id: # 找到下载端口控件 download_port_label = None download_port_widget = None for layout in child.findChildren(QHBoxLayout): if layout.itemAt(0) and layout.itemAt(0).widget() and layout.itemAt(0).widget().text() == "下载端口号:": download_port_label = layout.itemAt(0).widget() download_port_widget = layout.itemAt(1).widget() break if download_port_label and download_port_widget: # 双向测试时显示下载端口配置 if direction_idx == 2: # 双向 download_port_label.show() download_port_widget.show() else: download_port_label.hide() download_port_widget.hide() break def remove_test_group(self, group_widget): group_id = group_widget.property('group_id') if group_id: # 从布局中移除 self.test_groups_layout.removeWidget(group_widget) group_widget.deleteLater() # 从配置中移除 if group_id in self.config['test_groups']: del self.config['test_groups'][group_id] self.save_config() # 更新组选择列表 if hasattr(self, 'group_list'): self.update_group_list() def update_group_list(self): """更新测试组选择列表""" if not hasattr(self, 'group_list'): return self.group_list.clear() for group_id in self.config['test_groups'].keys(): item = QListWidgetItem(f"测试组 {group_id}") item.setData(Qt.UserRole, group_id) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) self.group_list.addItem(item) def select_all_groups(self): """选择所有测试组""" if not hasattr(self, 'group_list'): return for i in range(self.group_list.count()): item = self.group_list.item(i) item.setCheckState(Qt.Checked) self.update_chart() def deselect_all_groups(self): """取消选择所有测试组""" if not hasattr(self, 'group_list'): return for i in range(self.group_list.count()): item = self.group_list.item(i) item.setCheckState(Qt.Unchecked) self.update_chart() def browse_log_path(self): path = QFileDialog.getExistingDirectory(self, "选择日志保存路径") if path: self.log_path.setText(path) self.config['log_path'] = path self.save_config() def start_test(self): # 验证配置 if not self.config['iperf_path']: QMessageBox.critical(self, "错误", "请先配置iperf3路径") return # 创建管理器(使用配置的iperf路径) self.manager = IperfManager(self.config['iperf_path']) # 禁用UI控件 self.start_btn.setEnabled(False) self.add_group_btn.setEnabled(False) self.stop_btn.setEnabled(True) # 清除图表 self.clear_chart() # 记录开始时间 self.start_time = datetime.now() self.canvas.start_time = self.start_time self.canvas.start_timestamp = time.time() self.exec_log.append(f"[{self.start_time.strftime('%Y-%m-%d %H:%M:%S')}] 测试开始") # 收集测试组配置 - 直接从界面控件读取最新值 test_groups = {} self.nic_map = {} # 重置网卡映射 # 遍历所有测试组控件 for i in range(self.test_groups_layout.count()): group_widget = self.test_groups_layout.itemAt(i).widget() if not group_widget or not isinstance(group_widget, QGroupBox): continue group_id = group_widget.property('group_id') if not group_id: continue # 直接从控件读取值 server_ip = group_widget.server_ip_edit.text().strip() server_port = group_widget.server_port_spin.value() server_nic = group_widget.server_nic_combo.currentText() download_port = group_widget.download_port_spin.value() client_ip = group_widget.client_ip_edit.text().strip() client_name = group_widget.client_name_edit.text() client_nic = group_widget.client_nic_combo.currentText() test_direction_idx = group_widget.test_direction_combo.currentIndex() test_time_val = group_widget.test_time_spin.value() time_unit_idx = group_widget.time_unit_combo.currentIndex() omit = group_widget.omit_spin.value() streams = group_widget.streams_spin.value() # 计算测试时长(秒)- 转换为整数秒 if time_unit_idx == 1: # 分钟 duration = test_time_val * 60 elif time_unit_idx == 2: # 小时 duration = test_time_val * 3600 else: # 秒 duration = test_time_val duration = max(1, int(round(duration))) test_direction = ["上传", "下载", "双向"][test_direction_idx] # 调试信息 self.exec_log.append(f"测试组{group_id}配置: server_ip={server_ip}, client_ip={client_ip}") test_groups[group_id] = { 'server_ip': server_ip, 'server_port': server_port, 'client_ip': client_ip, 'direction': test_direction, 'duration': duration, # 使用整数秒 'streams': streams, 'omit': omit, 'upload_port': server_port, # 上传端口 'download_port': download_port # 下载端口 } # 记录网卡映射 if server_nic: if server_nic not in self.nic_map: self.nic_map[server_nic] = [] self.nic_map[server_nic].append((group_id, server_port, "Server")) if client_nic: if client_nic not in self.nic_map: self.nic_map[client_nic] = [] self.nic_map[client_nic].append((group_id, server_port, "Client")) # 启动服务器 for group_id, config in test_groups.items(): try: # 获取网卡名称 server_nic = "" for i in range(self.test_groups_layout.count()): group_widget = self.test_groups_layout.itemAt(i).widget() if group_widget.property('group_id') == group_id: server_nic = group_widget.server_nic_combo.currentText() break # 启动服务器 command = self.manager.start_server( group_id, config['server_port'], config['server_ip'], server_nic ) self.exec_log.append(f"[测试组{group_id}] 启动服务器: {command}") except ValueError as e: self.exec_log.append(f"[错误] 测试组{group_id}服务器启动失败: {str(e)}") QMessageBox.critical(self, "错误", f"测试组{group_id}服务器启动失败: {str(e)}") self.stop_test() return # 等待服务器启动 time.sleep(1) # 启动客户端 for group_id, config in test_groups.items(): try: # 获取网卡名称 client_nic = "" for i in range(self.test_groups_layout.count()): group_widget = self.test_groups_layout.itemAt(i).widget() if group_widget.property('group_id') == group_id: client_nic = group_widget.client_nic_combo.currentText() break # 启动客户端 reverse = config['direction'] in ["下载", "双向"] command = self.manager.start_client( group_id, config['server_ip'], config['server_port'], config['duration'], config['streams'], config['client_ip'], reverse, config['omit'], client_nic ) self.exec_log.append(f"[测试组{group_id}] 启动客户端: {command}") # 如果是双向测试,启动下载客户端 if config['direction'] == "双向": command = self.manager.start_client( group_id, config['server_ip'], config['download_port'], config['duration'], config['streams'], config['client_ip'], False, # 上传方向 config['omit'], client_nic ) self.exec_log.append(f"[测试组{group_id}] 启动下载客户端: {command}") except ValueError as e: self.exec_log.append(f"[错误] 测试组{group_id}客户端启动失败: {str(e)}") QMessageBox.critical(self, "错误", f"测试组{group_id}客户端启动失败: {str(e)}") self.stop_test() return # 启动数据收集线程 self.data_collector = DataCollectorThread(self.manager, test_groups) self.data_collector.data_updated.connect(self.update_chart) self.data_collector.test_completed.connect(self.test_finished) self.data_collector.test_timeout.connect(self.test_timeout_handler) self.data_collector.start() def stop_test(self): """停止所有测试""" if self.manager: self.manager.stop_all() if self.data_collector: self.data_collector.stop() self.exec_log.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 测试停止") # 启用UI控件 self.start_btn.setEnabled(True) self.add_group_btn.setEnabled(True) self.stop_btn.setEnabled(False) def test_finished(self, group_id, full_data): """测试完成处理""" self.exec_log.append(f"[测试组{group_id}] 测试完成") # 自动保存日志 if self.auto_save.isChecked(): self.save_log(group_id, full_data) def test_timeout_handler(self): """测试超时处理""" self.exec_log.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 测试超时") self.stop_test() def update_chart(self, data=None): """更新图表""" if not data: # 如果没有数据,显示空图表 self.canvas.ax.clear() self.canvas.ax.set_xlabel('时间 (秒)') self.canvas.ax.set_ylabel('吞吐量 (Mbps)') self.canvas.ax.set_title('iPerf3 流量监控') self.canvas.ax.grid(True) self.canvas.draw() return # 获取选中的测试组 selected_groups = set() for i in range(self.group_list.count()): item = self.group_list.item(i) if item.checkState() == Qt.Checked: selected_groups.add(item.data(Qt.UserRole)) # 更新图表 self.canvas.update_plot(data, selected_groups) def clear_chart(self): """清除图表数据""" self.canvas.ax.clear() self.canvas.ax.set_xlabel('时间 (秒)') self.canvas.ax.set_ylabel('吞吐量 (Mbps)') self.canvas.ax.set_title('iPerf3 流量监控') self.canvas.ax.grid(True) self.canvas.draw() def reset_zoom(self): """重置图表缩放""" self.canvas.ax.relim() self.canvas.ax.autoscale_view() self.canvas.draw() def export_chart(self): """导出图表为图片""" file_path, _ = QFileDialog.getSaveFileName( self, "保存图表", "", "PNG图片 (*.png);;所有文件 (*)" ) if file_path: self.canvas.fig.savefig(file_path, dpi=300) self.exec_log.append(f"图表已保存到: {file_path}") def export_log(self): """导出执行日志""" file_path, _ = QFileDialog.getSaveFileName( self, "保存日志", "", "文本文件 (*.txt);;所有文件 (*)" ) if file_path: with open(file_path, 'w', encoding='utf-8') as f: f.write(self.exec_log.toPlainText()) self.exec_log.append(f"日志已保存到: {file_path}") def update_nic_status(self, initial=False): """更新网卡状态显示""" # 网卡状态 self.nic_status.clear() try: c = wmi.WMI() for adapter in c.Win32_NetworkAdapter(): status = "未知" if adapter.NetConnectionStatus == 2: status = "已连接" elif adapter.NetConnectionStatus == 7: status = "已断开" self.nic_status.append(f"{adapter.Name}: {status}") except Exception as e: self.nic_status.append(f"获取网卡状态失败: {str(e)}") # 网卡事件摘要 self.nic_summary_text.clear() for nic, summary in self.nic_summary.items(): self.nic_summary_text.append(f"{nic}: 断开次数={summary['down']}, 连接次数={summary['up']}, 当前状态={summary['last_status']}") # 网卡映射 self.nic_mapping_text.clear() for nic, mappings in self.nic_map.items(): for group_id, port, role in mappings: self.nic_mapping_text.append(f"{nic} -> 测试组{group_id} {role}端口{port}") def export_nic_status(self): """导出网卡状态""" file_path, _ = QFileDialog.getSaveFileName( self, "保存网卡状态", "", "文本文件 (*.txt);;所有文件 (*)" ) if file_path: with open(file_path, 'w', encoding='utf-8') as f: f.write("=== 网卡状态 ===\n") f.write(self.nic_status.toPlainText() + "\n\n") f.write("=== 网卡事件摘要 ===\n") f.write(self.nic_summary_text.toPlainText() + "\n\n") f.write("=== 网卡测试组映射 ===\n") f.write(self.nic_mapping_text.toPlainText()) self.exec_log.append(f"网卡状态已保存到: {file_path}") def clear_nic_events(self): """清除网卡事件记录""" self.nic_events = [] self.nic_summary = {} self.update_nic_status() def save_log(self, group_id, full_data): """保存测试日志""" if not self.auto_save.isChecked(): return log_dir = self.log_path.text() if not os.path.exists(log_dir): os.makedirs(log_dir) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_path = os.path.join(log_dir, f"iperf_test_{group_id}_{timestamp}.json") with open(file_path, 'w', encoding='utf-8') as f: json.dump(full_data, f, indent=4) self.exec_log.append(f"[测试组{group_id}] 日志已保存到: {file_path}") def save_config(self): """保存配置到文件""" # 更新配置 self.config['iperf_path'] = self.iperf_path.text() self.config['log_path'] = self.log_path.text() # 保存到文件 with open('iperf_visualizer_config.json', 'w', encoding='utf-8') as f: json.dump(self.config, f, indent=4) def load_config(self): """从文件加载配置""" try: if os.path.exists('iperf_visualizer_config.json'): with open('iperf_visualizer_config.json', 'r', encoding='utf-8') as f: self.config = json.load(f) # 更新UI self.iperf_path.setText(self.config['iperf_path']) self.log_path.setText(self.config['log_path']) # 创建测试组 for group_id, group in self.config['test_groups'].items(): self.add_test_group_from_config(group_id, group) except Exception as e: print(f"加载配置失败: {str(e)}") def add_test_group_from_config(self, group_id, group_config): """从配置添加测试组""" group = QGroupBox(f"测试组 {group_id}") group.setProperty('group_id', group_id) group_layout = QVBoxLayout() # 组名 name_layout = QHBoxLayout() name_layout.addWidget(QLabel("组名:")) group_name = QLineEdit(group_config['name']) name_layout.addWidget(group_name) group_layout.addLayout(name_layout) # Server配置 server_group = QGroupBox("Server配置") server_layout = QVBoxLayout() ip_layout = QHBoxLayout() ip_layout.addWidget(QLabel("IP地址*:")) server_ip = QLineEdit(group_config['server_ip']) server_ip.setPlaceholderText("必须填写服务器IP") ip_layout.addWidget(server_ip) server_layout.addLayout(ip_layout) port_layout = QHBoxLayout() port_layout.addWidget(QLabel("端口号:")) server_port = QSpinBox() server_port.setRange(1024, 65535) server_port.setValue(group_config['server_port']) port_layout.addWidget(server_port) server_layout.addLayout(port_layout) # 添加网卡选择 nic_layout = QHBoxLayout() nic_layout.addWidget(QLabel("绑定网卡:")) server_nic = QComboBox() server_nic.addItem("") # 空选项 # 获取系统网卡列表 try: c = wmi.WMI() for adapter in c.Win32_NetworkAdapter(NetConnectionStatus=2): # 2表示已连接 server_nic.addItem(adapter.Name) if adapter.Name == group_config['server_nic']: server_nic.setCurrentText(adapter.Name) except Exception as e: print(f"获取网卡列表失败: {e}") nic_layout.addWidget(server_nic) server_layout.addLayout(nic_layout) server_group.setLayout(server_layout) group_layout.addWidget(server_group) # Client配置 client_group = QGroupBox("Client配置") client_layout = QVBoxLayout() c_ip_layout = QHBoxLayout() c_ip_layout.addWidget(QLabel("IP地址*:")) client_ip = QLineEdit(group_config['client_ip']) client_ip.setPlaceholderText("必须填写客户端IP") c_ip_layout.addWidget(client_ip) client_layout.addLayout(c_ip_layout) c_name_layout = QHBoxLayout() c_name_layout.addWidget(QLabel("客户端名称:")) client_name = QLineEdit(group_config['client_name']) c_name_layout.addWidget(client_name) client_layout.addLayout(c_name_layout) # 添加网卡选择 c_nic_layout = QHBoxLayout() c_nic_layout.addWidget(QLabel("绑定网卡:")) client_nic = QComboBox() client_nic.addItem("") # 空选项 # 获取系统网卡列表 try: c = wmi.WMI() for adapter in c.Win32_NetworkAdapter(NetConnectionStatus=2): # 2表示已连接 client_nic.addItem(adapter.Name) if adapter.Name == group_config['client_nic']: client_nic.setCurrentText(adapter.Name) except Exception as e: print(f"获取网卡列表失败: {e}") c_nic_layout.addWidget(client_nic) client_layout.addLayout(c_nic_layout) client_group.setLayout(client_layout) group_layout.addWidget(client_group) # 测试参数 param_group = QGroupBox("测试参数") param_layout = QVBoxLayout() direction_layout = QHBoxLayout() direction_layout.addWidget(QLabel("测试方向:")) test_direction = QComboBox() test_direction.addItems(["上传", "下载", "双向"]) test_direction.setCurrentIndex(group_config['direction']) test_direction.currentIndexChanged.connect(lambda idx, gid=group_id: self.update_port_visibility(gid, idx)) direction_layout.addWidget(test_direction) param_layout.addLayout(direction_layout) # 下载端口配置 download_port_layout = QHBoxLayout() download_port_layout.addWidget(QLabel("下载端口号:")) download_port = QSpinBox() download_port.setRange(1024, 65535) download_port.setValue(group_config['download_port']) download_port_layout.addWidget(download_port) # 初始时隐藏下载端口配置(除非是双向测试) if group_config['direction'] != 2: download_port_layout.itemAt(0).widget().hide() download_port_layout.itemAt(1).widget().hide() param_layout.addLayout(download_port_layout) time_layout = QHBoxLayout() time_layout.addWidget(QLabel("测试时长:")) test_time = QDoubleSpinBox() test_time.setRange(1, 10000) test_time.setValue(group_config['test_time']) time_layout.addWidget(test_time) time_unit = QComboBox() time_unit.addItems(["秒", "分钟", "小时"]) time_unit.setCurrentIndex(group_config['time_unit']) time_layout.addWidget(time_unit) param_layout.addLayout(time_layout) omit_layout = QHBoxLayout() omit_layout.addWidget(QLabel("跳过前N秒:")) omit_seconds = QSpinBox() omit_seconds.setRange(0, 60) omit_seconds.setValue(group_config['omit']) omit_layout.addWidget(omit_seconds) param_layout.addLayout(omit_layout) streams_layout = QHBoxLayout() streams_layout.addWidget(QLabel("并行流数量:")) streams = QSpinBox() streams.setRange(1, 100) streams.setValue(group_config['streams']) streams_layout.addWidget(streams) param_layout.addLayout(streams_layout) auto_restart = QCheckBox("每6小时自动重跑") auto_restart.setChecked(group_config['auto_restart']) param_layout.addWidget(auto_restart) error_restart = QCheckBox("异常自动重跑") error_restart.setChecked(group_config['error_restart']) param_layout.addWidget(error_restart) restart_count_layout = QHBoxLayout() restart_count_layout.addWidget(QLabel("重跑次数:")) restart_count = QSpinBox() restart_count.setRange(1, 10) restart_count.setValue(group_config['restart_count']) restart_count_layout.addWidget(restart_count) param_layout.addLayout(restart_count_layout) param_group.setLayout(param_layout) group_layout.addWidget(param_group) # 删除按钮 delete_btn = QPushButton("删除此组") delete_btn.clicked.connect(lambda: self.remove_test_group(group)) group_layout.addWidget(delete_btn) group.setLayout(group_layout) self.test_groups_layout.addWidget(group) # 将控件保存为group的属性,以便后续读取 group.server_ip_edit = server_ip group.client_ip_edit = client_ip def closeEvent(self, event): """关闭应用时保存配置""" self.save_config() if self.nic_monitor: self.nic_monitor.stop() event.accept() if __name__ == "__main__": app = QApplication(sys.argv) window = IperfVisualizer() window.show() sys.exit(app.exec_())
最新发布
08-14
帮忙分析,如下代码中,什么时候会提示“服务器IP地址无效”,如何解决,我在运行过程中有在窗口输入服务器IP。 import sys import os import ctypes import json import subprocess import threading import time import psutil import pythoncom from datetime import datetime, timedelta from PyQt5.QtWidgets import ( QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QCheckBox, QComboBox, QSpinBox, QGroupBox, QScrollArea, QTextEdit, QFileDialog, QMessageBox, QDoubleSpinBox, QSizePolicy, QListWidget, QAbstractItemView, QListWidgetItem ) from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer from PyQt5.QtGui import QColor, QIcon import wmi # Matplotlib 导入 import matplotlib matplotlib.use('Qt5Agg') import matplotlib.pyplot as plt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure import matplotlib.dates as mdates from matplotlib import ticker """# 检查管理员权限并请求提升 def is_admin(): try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False if not is_admin(): # 请求管理员权限 ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1) sys.exit(0)""" # 设置中文字体支持 plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'SimSun', 'KaiTi'] # 设置中文字体 plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 # 网卡事件监听线程 class NicMonitorThread(QThread): nic_event = pyqtSignal(dict) # 发送事件:{'name': 网卡名, 'event': 'up' or 'down', 'time': 时间字符串} error_signal = pyqtSignal(str) # 错误信号 def __init__(self): super().__init__() self.running = True self.c = None self.watcher = None self.last_status = {} # 记录每个网卡的最后状态 def init_wmi(self): """初始化WMI连接""" try: pythoncom.CoInitialize() # 添加COM初始化 self.c = wmi.WMI() return True except Exception as e: self.error_signal.emit(f"无法初始化WMI连接: {str(e)}") return False def run(self): # 创建事件监听 if not self.init_wmi(): self.error_signal.emit("网卡事件监听器初始化失败") return try: # 获取当前网卡状态作为初始状态 for adapter in self.c.Win32_NetworkAdapter(NetConnectionStatus=2): self.last_status[adapter.Name] = 'up' for adapter in self.c.Win32_NetworkAdapter(NetConnectionStatus=7): self.last_status[adapter.Name] = 'down' # 创建事件监听器 self.watcher = self.c.watch_for( notification_type="__InstanceModificationEvent", wmi_class="Win32_NetworkAdapter", delay_secs=0.1, # 更短的延迟以提高响应速度 fields=["NetConnectionStatus", "Name"] ) while self.running: try: adapter = self.watcher() if adapter: name = adapter.Name status = adapter.NetConnectionStatus event_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] # 只处理状态变化 if status == 2 and self.last_status.get(name) != 'up': self.nic_event.emit({'name': name, 'event': 'up', 'time': event_time}) self.last_status[name] = 'up' elif status == 7 and self.last_status.get(name) != 'down': self.nic_event.emit({'name': name, 'event': 'down', 'time': event_time}) self.last_status[name] = 'down' except wmi.x_wmi_timed_out: # 超时是正常的,继续循环 pass except Exception as e: self.error_signal.emit(f"监听网卡事件出错: {str(e)}") # 尝试重新初始化WMI连接 time.sleep(1) if not self.init_wmi(): break except Exception as e: self.error_signal.emit(f"创建网卡监听器出错: {str(e)}") finally: pythoncom.CoUninitialize() # 清理COM资源 def stop(self): self.running = False if self.watcher: self.watcher.cancel() # iperf3 进程管理器 class IperfManager: def __init__(self, iperf_path=''): self.active_servers = {} self.active_clients = {} self.iperf_path = iperf_path or 'iperf3' def start_server(self, group_id, port, server_ip, nic_name=""): """启动iperf3服务器""" # 使用服务器IP地址作为绑定的IP地址 if not server_ip or not self.validate_ip(server_ip): raise ValueError(f"测试组{group_id}服务器IP地址无效: {server_ip}") command = f'"{self.iperf_path}" -s -p {port} -B {server_ip} -J --logfile server_{group_id}_{port}.log' proc = subprocess.Popen(command, shell=True) self.active_servers[(group_id, port)] = (proc, nic_name) # 保存网卡名称用于日志 return command def start_client(self, group_id, server_ip, port, duration, streams, client_ip, reverse=False, omit=5, nic_name=""): """启动iperf3客户端""" # 使用客户端IP地址作为绑定的IP地址 if not client_ip or not self.validate_ip(client_ip): raise ValueError(f"测试组{group_id}客户端IP地址无效: {client_ip}") if not server_ip or not self.validate_ip(server_ip): raise ValueError(f"测试组{group_id}服务器IP地址无效: {server_ip}") direction = "-R" if reverse else "" # 添加-B参数绑定客户端IP,-c参数连接服务器IP command = f'"{self.iperf_path}" -c {server_ip} -p {port} -t {duration} -P {streams} {direction} -O {omit} -B {client_ip} -J --logfile client_{group_id}_{port}.json' proc = subprocess.Popen(command, shell=True) self.active_clients[(group_id, port)] = (proc, nic_name) # 保存网卡名称用于日志 return command def validate_ip(self, ip_str): """验证IP地址格式""" if not ip_str: return False parts = ip_str.split('.') if len(parts) != 4: return False for part in parts: if not part.isdigit(): return False num = int(part) if num < 0 or num > 255: return False return True def stop_all(self): """停止所有iperf进程""" for key, (proc, _) in list(self.active_servers.items()) + list(self.active_clients.items()): try: parent = psutil.Process(proc.pid) for child in parent.children(recursive=True): child.kill() parent.kill() except Exception as e: print(f"停止进程时出错: {e}") self.active_servers = {} self.active_clients = {} # 实时数据收集线程 class DataCollectorThread(QThread): data_updated = pyqtSignal(dict) # {group_id: {timestamp: (up, down)}} test_completed = pyqtSignal(int, dict) # group_id, full_data test_timeout = pyqtSignal() # 测试超时信号 def __init__(self, manager, groups, parent=None): super().__init__(parent) self.manager = manager self.groups = groups self.running = True self.data = {} self.full_data = {} self.start_time = time.time() self.max_duration = 0 # 记录最长测试时间(秒) self.timeout_timer = None # 计算最长测试时间(用于超时检测) for config in groups.values(): if config['duration'] > self.max_duration: self.max_duration = config['duration'] def run(self): # 初始化数据结构 for group_id in self.groups: self.data[group_id] = {"up": {}, "down": {}} self.full_data[group_id] = {"up": {}, "down": {}} # 设置超时计时器(最长测试时间+5秒) self.timeout_timer = threading.Timer(self.max_duration + 5, self.handle_timeout) self.timeout_timer.start() # 主采集循环 while self.running: for group_id, config in self.groups.items(): # 处理上传日志 upload_log = f"client_{group_id}_{config['upload_port']}.json" if os.path.exists(upload_log): try: with open(upload_log, 'r') as f: data = json.load(f) # 解析上传数据 if 'intervals' in data: for interval in data['intervals']: ts = time.time() up = interval['sum']['bits_per_second'] / 1e6 # 转换为Mbps # 存储实时数据 self.data[group_id]["up"][ts] = up self.full_data[group_id]["up"][ts] = up except Exception as e: print(f"读取上传日志文件错误: {e}") # 处理下载日志(如果是双向测试) if config['direction'] in ["下载", "双向"]: download_log = f"client_{group_id}_{config['download_port']}.json" if os.path.exists(download_log): try: with open(download_log, 'r') as f: data = json.load(f) # 解析下载数据 if 'intervals' in data: for interval in data['intervals']: ts = time.time() down = interval['sum']['bits_per_second'] / 1e6 # 存储实时数据 self.data[group_id]["down"][ts] = down self.full_data[group_id]["down"][ts] = down except Exception as e: print(f"读取下载日志文件错误: {e}") # 发送更新信号 self.data_updated.emit(self.data) time.sleep(1) # 每秒更新一次 # 测试完成后发送完整数据 for group_id in self.groups: self.test_completed.emit(group_id, self.full_data[group_id]) def handle_timeout(self): """处理测试超时""" self.running = False self.test_timeout.emit() def stop(self): self.running = False if self.timeout_timer: self.timeout_timer.cancel() # Matplotlib 图表组件(支持中文) class MplCanvas(FigureCanvas): def __init__(self, parent=None, width=5, height=4, dpi=100): self.fig = Figure(figsize=(width, height), dpi=dpi, facecolor=(0.94, 0.97, 1.0)) # 淡蓝色背景 self.ax = self.fig.add_subplot(111) super().__init__(self.fig) self.setParent(parent) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.updateGeometry() # 初始化图表(中文) self.ax.set_xlabel('时间 (秒)') self.ax.set_ylabel('吞吐量 (Mbps)') self.ax.set_title('iPerf3 流量监控') self.ax.grid(True) self.ax.set_facecolor((0.98, 0.99, 1.0)) # 更淡的蓝色背景 self.relative_time = True # 默认使用相对时间 self.start_time = None self.end_time = None self.start_timestamp = None def update_plot(self, data, selected_groups=None): """更新图表数据""" self.ax.clear() # 颜色列表 colors = [ '#4169E1', # RoyalBlue '#4682B4', # SteelBlue '#6495ED', # CornflowerBlue '#1E90FF', # DodgerBlue '#00BFFF' # DeepSkyBlue ] max_value = 0 min_timestamp = float('inf') max_timestamp = float('-inf') for group_id, group_data in data.items(): if selected_groups is not None and group_id not in selected_groups: continue color = colors[(group_id-1) % len(colors)] # 上传数据(实线) if group_data["up"]: timestamps = sorted(group_data["up"].keys()) values = [group_data["up"][ts] for ts in timestamps] # 更新时间范围 if timestamps: min_timestamp = min(min_timestamp, min(timestamps)) max_timestamp = max(max_timestamp, max(timestamps)) if not self.relative_time and self.start_time: # 转换为绝对时间 abs_timestamps = [self.start_time + timedelta(seconds=ts - self.start_timestamp) for ts in timestamps] self.ax.plot(abs_timestamps, values, '-', color=color, label=f"组{group_id} 上传", linewidth=2) else: # 相对时间(从0开始) rel_timestamps = [ts - min_timestamp for ts in timestamps] self.ax.plot(rel_timestamps, values, '-', color=color, label=f"组{group_id} 上传", linewidth=2) if values: max_value = max(max_value, max(values)) # 下载数据(虚线) if group_data["down"]: timestamps = sorted(group_data["down"].keys()) values = [group_data["down"][ts] for ts in timestamps] # 更新时间范围 if timestamps: min_timestamp = min(min_timestamp, min(timestamps)) max_timestamp = max(max_timestamp, max(timestamps)) if not self.relative_time and self.start_time: abs_timestamps = [self.start_time + timedelta(seconds=ts - self.start_timestamp) for ts in timestamps] self.ax.plot(abs_timestamps, values, '--', color=color, label=f"组{group_id} 下载", linewidth=2) else: rel_timestamps = [ts - min_timestamp for ts in timestamps] self.ax.plot(rel_timestamps, values, '--', color=color, label=f"组{group_id} 下载", linewidth=2) if values: max_value = max(max_value, max(values)) # 设置坐标轴范围 if max_value > 0: self.ax.set_ylim(0, max_value * 1.2) # 添加图例(中文) self.ax.legend(loc='best', fontsize=9) # 设置标签和标题(中文) if self.relative_time: self.ax.set_xlabel('时间 (秒)') else: self.ax.set_xlabel('时间') # 设置时间格式 self.ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) self.ax.xaxis.set_major_locator(ticker.AutoLocator()) self.ax.set_ylabel('吞吐量 (Mbps)') self.ax.set_title('iPerf3 流量监控') self.ax.grid(True) self.draw() def switch_to_absolute_time(self, start_time, end_time): """切换到绝对时间模式""" self.relative_time = False self.start_time = start_time self.end_time = end_time # 记录测试开始时的第一个时间戳(作为相对时间的零点) if self.start_time: self.start_timestamp = time.time() - (datetime.now() - start_time).total_seconds() # 主应用窗口 class IperfVisualizer(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("iPerf3 流量可视化工具") self.setWindowIcon(QIcon('iperf_icon.ico')) # 设置软件图标 self.resize(1400, 900) # 应用配置 self.config = { 'iperf_path': '', 'auto_save_log': True, 'log_path': os.getcwd(), 'test_groups': {}, 'current_group_id': 1 } # 初始化管理器 self.manager = None self.data_collector = None self.nic_monitor = None self.nic_events = [] # 存储网卡事件 self.nic_summary = {} # 网卡事件汇总: {网卡名: {'down': 次数, 'up': 次数, 'last_status': 最后状态}} self.nic_map = {} # 网卡到测试组映射: {网卡名: [(group_id, port, role)]} # 设置UI self.init_ui() self.load_config() # 启动网卡事件监听 self.start_nic_monitor() def init_ui(self): # 主布局 main_widget = QWidget() main_layout = QVBoxLayout() main_widget.setLayout(main_layout) self.setCentralWidget(main_widget) # 标签页 self.tabs = QTabWidget() main_layout.addWidget(self.tabs) # 配置页 self.config_tab = QWidget() self.tabs.addTab(self.config_tab, "测试配置") self.setup_config_tab() # 图表页 self.chart_tab = QWidget() self.tabs.addTab(self.chart_tab, "流量图表") self.setup_chart_tab() # 信息页 self.info_tab = QWidget() self.tabs.addTab(self.info_tab, "测试信息") self.setup_info_tab() # 状态栏 self.statusBar().showMessage("就绪") def setup_config_tab(self): layout = QVBoxLayout() self.config_tab.setLayout(layout) # iperf路径配置 iperf_group = QGroupBox("iperf3 配置") iperf_layout = QVBoxLayout() path_layout = QHBoxLayout() path_layout.addWidget(QLabel("iperf3路径:")) self.iperf_path = QLineEdit(self.config['iperf_path']) path_layout.addWidget(self.iperf_path) self.iperf_browse_btn = QPushButton("浏览...") self.iperf_browse_btn.clicked.connect(self.browse_iperf_path) path_layout.addWidget(self.iperf_browse_btn) iperf_layout.addLayout(path_layout) # 测试按钮 test_btn_layout = QHBoxLayout() self.test_iperf_btn = QPushButton("测试路径") self.test_iperf_btn.clicked.connect(self.test_iperf_path) test_btn_layout.addWidget(self.test_iperf_btn) iperf_layout.addLayout(test_btn_layout) iperf_group.setLayout(iperf_layout) layout.addWidget(iperf_group) # 测试描述 desc_group = QGroupBox("测试描述") desc_layout = QVBoxLayout() self.test_desc = QLineEdit("iPerf3流量测试") desc_layout.addWidget(QLabel("测试描述:")) desc_layout.addWidget(self.test_desc) desc_group.setLayout(desc_layout) layout.addWidget(desc_group) # 日志设置 log_group = QGroupBox("日志设置") log_layout = QVBoxLayout() self.auto_save = QCheckBox("自动保存测试日志") self.auto_save.setChecked(True) log_layout.addWidget(self.auto_save) path_layout = QHBoxLayout() path_layout.addWidget(QLabel("保存路径:")) self.log_path = QLineEdit(os.getcwd()) path_layout.addWidget(self.log_path) self.browse_btn = QPushButton("浏览...") self.browse_btn.clicked.connect(self.browse_log_path) path_layout.addWidget(self.browse_btn) log_layout.addLayout(path_layout) log_group.setLayout(log_layout) layout.addWidget(log_group) # 测试组配置 self.test_groups_area = QScrollArea() self.test_groups_area.setWidgetResizable(True) self.test_groups_widget = QWidget() self.test_groups_layout = QVBoxLayout() self.test_groups_widget.setLayout(self.test_groups_layout) self.test_groups_area.setWidget(self.test_groups_widget) layout.addWidget(QLabel("测试组配置:")) layout.addWidget(self.test_groups_area) # 添加测试组按钮 self.add_group_btn = QPushButton("添加测试") self.add_group_btn.clicked.connect(self.add_test_group) layout.addWidget(self.add_group_btn) # 操作按钮 btn_layout = QHBoxLayout() self.start_btn = QPushButton("开始测试") self.start_btn.clicked.connect(self.start_test) btn_layout.addWidget(self.start_btn) self.stop_btn = QPushButton("停止测试") self.stop_btn.clicked.connect(self.stop_test) self.stop_btn.setEnabled(False) btn_layout.addWidget(self.stop_btn) layout.addLayout(btn_layout) # 添加初始测试组 self.add_test_group() def setup_chart_tab(self): layout = QVBoxLayout() self.chart_tab.setLayout(layout) # 使用 Matplotlib 图表(支持中文) self.canvas = MplCanvas(self, width=10, height=6, dpi=100) layout.addWidget(self.canvas, 3) # 图表占3/4空间 # 测试组选择面板 group_select_layout = QHBoxLayout() # 测试组列表 group_list_layout = QVBoxLayout() group_list_layout.addWidget(QLabel("选择显示的测试组:")) self.group_list = QListWidget() self.group_list.setSelectionMode(QAbstractItemView.MultiSelection) # 多选 group_list_layout.addWidget(self.group_list) # 选择按钮 btn_layout = QHBoxLayout() self.select_all_btn = QPushButton("全选") self.select_all_btn.clicked.connect(self.select_all_groups) btn_layout.addWidget(self.select_all_btn) self.deselect_all_btn = QPushButton("全不选") self.deselect_all_btn.clicked.connect(self.deselect_all_groups) btn_layout.addWidget(self.deselect_all_btn) group_list_layout.addLayout(btn_layout) group_select_layout.addLayout(group_list_layout, 1) # 占1/4宽度 # 图表操作按钮 chart_btn_layout = QVBoxLayout() chart_btn_layout.addWidget(QLabel("图表操作:")) self.export_btn = QPushButton("导出图表") self.export_btn.clicked.connect(self.export_chart) chart_btn_layout.addWidget(self.export_btn) self.clear_btn = QPushButton("清除图表") self.clear_btn.clicked.connect(self.clear_chart) chart_btn_layout.addWidget(self.clear_btn) self.zoom_reset_btn = QPushButton("重置缩放") self.zoom_reset_btn.clicked.connect(self.reset_zoom) chart_btn_layout.addWidget(self.zoom_reset_btn) group_select_layout.addLayout(chart_btn_layout, 1) # 占1/4宽度 layout.addLayout(group_select_layout, 1) # 控制面板占1/4高度 # 初始化后更新组列表 self.update_group_list() def setup_info_tab(self): layout = QVBoxLayout() self.info_tab.setLayout(layout) # 执行日志 exec_group = QGroupBox("执行日志") exec_layout = QVBoxLayout() self.exec_log = QTextEdit() self.exec_log.setReadOnly(True) exec_layout.addWidget(self.exec_log) btn_layout = QHBoxLayout() self.clear_log_btn = QPushButton("清除日志") self.clear_log_btn.clicked.connect(lambda: self.exec_log.clear()) btn_layout.addWidget(self.clear_log_btn) self.export_log_btn = QPushButton("导出日志") self.export_log_btn.clicked.connect(self.export_log) btn_layout.addWidget(self.export_log_btn) exec_layout.addLayout(btn_layout) exec_group.setLayout(exec_layout) layout.addWidget(exec_group) # 网卡状态 nic_group = QGroupBox("网卡状态监控") nic_layout = QVBoxLayout() self.nic_status = QTextEdit() self.nic_status.setReadOnly(True) nic_layout.addWidget(self.nic_status) # 网卡事件摘要 self.nic_summary_text = QTextEdit() self.nic_summary_text.setReadOnly(True) nic_layout.addWidget(QLabel("网卡事件摘要:")) nic_layout.addWidget(self.nic_summary_text) # 网卡映射 self.nic_mapping_text = QTextEdit() self.nic_mapping_text.setReadOnly(True) nic_layout.addWidget(QLabel("网卡测试组映射:")) nic_layout.addWidget(self.nic_mapping_text) nic_btn_layout = QHBoxLayout() self.refresh_nic_btn = QPushButton("刷新状态") self.refresh_nic_btn.clicked.connect(self.update_nic_status) nic_btn_layout.addWidget(self.refresh_nic_btn) self.export_nic_btn = QPushButton("导出状态") self.export_nic_btn.clicked.connect(self.export_nic_status) nic_btn_layout.addWidget(self.export_nic_btn) self.clear_nic_btn = QPushButton("清除事件") self.clear_nic_btn.clicked.connect(self.clear_nic_events) nic_btn_layout.addWidget(self.clear_nic_btn) nic_layout.addLayout(nic_btn_layout) nic_group.setLayout(nic_layout) layout.addWidget(nic_group) # 初始更新网卡状态 self.update_nic_status(initial=True) def browse_iperf_path(self): """浏览并选择iperf3可执行文件""" file_path, _ = QFileDialog.getOpenFileName( self, "选择iperf3可执行文件", "", "可执行文件 (*.exe);;所有文件 (*)" ) if file_path: self.iperf_path.setText(file_path) self.config['iperf_path'] = file_path self.save_config() def test_iperf_path(self): """测试iperf3路径是否有效""" path = self.iperf_path.text() if not path: QMessageBox.warning(self, "警告", "请先选择iperf3路径") return try: result = subprocess.run( [path, '-v'], capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW ) if 'iperf 3.' in result.stdout: QMessageBox.information(self, "测试成功", f"iperf3版本信息:\n{result.stdout.splitlines()[0]}") self.config['iperf_path'] = path self.save_config() else: QMessageBox.critical(self, "错误", "选择的文件不是有效的iperf3可执行文件") except Exception as e: QMessageBox.critical(self, "错误", f"测试失败: {str(e)}") def start_nic_monitor(self): """启动网卡事件监听线程""" self.nic_monitor = NicMonitorThread() self.nic_monitor.nic_event.connect(self.handle_nic_event) self.nic_monitor.error_signal.connect(self.handle_nic_error) # 连接错误信号 self.nic_monitor.start() def handle_nic_error(self, error_msg): """处理网卡监听错误""" self.exec_log.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {error_msg}") # 如果无法使用事件监听,回退到轮询方式 if "无法初始化WMI连接" in error_msg or "创建网卡监听器出错" in error_msg: self.exec_log.append("将使用轮询方式监控网卡状态") self.start_polling_nic_status() def start_polling_nic_status(self): """启动轮询方式监控网卡状态""" self.last_nic_status = {} # 初始化状态记录 self.nic_poll_timer = QTimer() self.nic_poll_timer.timeout.connect(self.poll_nic_status) self.nic_poll_timer.start(1000) # 每秒轮询一次 def poll_nic_status(self): """轮询网卡状态""" try: c = wmi.WMI() # 获取所有网卡状态 for adapter in c.Win32_NetworkAdapter(): status = 'unknown' if adapter.NetConnectionStatus == 2: status = 'up' elif adapter.NetConnectionStatus == 7: status = 'down' # 只处理状态变化的网卡 if adapter.Name in self.last_nic_status and self.last_nic_status[adapter.Name] != status: event_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] self.handle_nic_event({'name': adapter.Name, 'event': status, 'time': event_time}) # 记录当前状态 self.last_nic_status[adapter.Name] = status except Exception as e: self.exec_log.append(f"轮询网卡状态出错: {str(e)}") def handle_nic_event(self, event): """处理网卡事件""" name = event['name'] event_type = event['event'] event_time = event['time'] # 检查网卡映射 affected_groups = [] if name in self.nic_map: for group_id, port, role in self.nic_map[name]: affected_groups.append(f"测试组{group_id}的{role}端口{port}") # 记录事件 event_msg = f"[{event_time}] 网卡 '{name}' 状态变化: {event_type}" if affected_groups: event_msg += f" (影响: {', '.join(affected_groups)})" self.nic_events.append(event_msg) # 更新汇总 if name not in self.nic_summary: self.nic_summary[name] = {'down': 0, 'up': 0, 'last_status': 'unknown'} if event_type == 'down': self.nic_summary[name]['down'] += 1 elif event_type == 'up': self.nic_summary[name]['up'] += 1 self.nic_summary[name]['last_status'] = event_type # 更新网卡状态显示 self.update_nic_status() def add_test_group(self): # 计算当前可用最小ID existing_ids = set(self.config['test_groups'].keys()) group_id = 1 while group_id in existing_ids: group_id += 1 if group_id > 30: QMessageBox.warning(self, "警告", "最多只能添加30个测试组") return group = QGroupBox(f"测试组 {group_id}") group.setProperty('group_id', group_id) # 设置属性用于标识 group_layout = QVBoxLayout() # 组名 name_layout = QHBoxLayout() name_layout.addWidget(QLabel("组名:")) group_name = QLineEdit(f"测试组{group_id}") name_layout.addWidget(group_name) group_layout.addLayout(name_layout) # Server配置 server_group = QGroupBox("Server配置") server_layout = QVBoxLayout() ip_layout = QHBoxLayout() ip_layout.addWidget(QLabel("IP地址*:")) server_ip = QLineEdit() server_ip.setPlaceholderText("必须填写服务器IP") ip_layout.addWidget(server_ip) server_layout.addLayout(ip_layout) port_layout = QHBoxLayout() port_layout.addWidget(QLabel("端口号:")) server_port = QSpinBox() server_port.setRange(1024, 65535) server_port.setValue(5201 + group_id - 1) port_layout.addWidget(server_port) server_layout.addLayout(port_layout) # 添加网卡选择 nic_layout = QHBoxLayout() nic_layout.addWidget(QLabel("绑定网卡:")) server_nic = QComboBox() server_nic.addItem("") # 空选项 # 获取系统网卡列表 try: c = wmi.WMI() for adapter in c.Win32_NetworkAdapter(NetConnectionStatus=2): # 2表示已连接 server_nic.addItem(adapter.Name) except Exception as e: print(f"获取网卡列表失败: {e}") nic_layout.addWidget(server_nic) server_layout.addLayout(nic_layout) server_group.setLayout(server_layout) group_layout.addWidget(server_group) # Client配置 client_group = QGroupBox("Client配置") client_layout = QVBoxLayout() c_ip_layout = QHBoxLayout() c_ip_layout.addWidget(QLabel("IP地址*:")) client_ip = QLineEdit() client_ip.setPlaceholderText("必须填写客户端IP") c_ip_layout.addWidget(client_ip) client_layout.addLayout(c_ip_layout) c_name_layout = QHBoxLayout() c_name_layout.addWidget(QLabel("客户端名称:")) client_name = QLineEdit(f"客户端{group_id}") c_name_layout.addWidget(client_name) client_layout.addLayout(c_name_layout) # 添加网卡选择 c_nic_layout = QHBoxLayout() c_nic_layout.addWidget(QLabel("绑定网卡:")) client_nic = QComboBox() client_nic.addItem("") # 空选项 # 获取系统网卡列表 try: c = wmi.WMI() for adapter in c.Win32_NetworkAdapter(NetConnectionStatus=2): # 2表示已连接 client_nic.addItem(adapter.Name) except Exception as e: print(f"获取网卡列表失败: {e}") c_nic_layout.addWidget(client_nic) client_layout.addLayout(c_nic_layout) client_group.setLayout(client_layout) group_layout.addWidget(client_group) # 测试参数 param_group = QGroupBox("测试参数") param_layout = QVBoxLayout() direction_layout = QHBoxLayout() direction_layout.addWidget(QLabel("测试方向:")) test_direction = QComboBox() test_direction.addItems(["上传", "下载", "双向"]) test_direction.currentIndexChanged.connect(lambda idx, gid=group_id: self.update_port_visibility(gid, idx)) direction_layout.addWidget(test_direction) param_layout.addLayout(direction_layout) # 下载端口配置(默认隐藏) download_port_layout = QHBoxLayout() download_port_layout.addWidget(QLabel("下载端口号:")) download_port = QSpinBox() download_port.setRange(1024, 65535) download_port.setValue(server_port.value() + 1) download_port_layout.addWidget(download_port) # 初始时隐藏下载端口配置(除非是双向测试) if test_direction.currentIndex() != 2: download_port_layout.itemAt(0).widget().hide() download_port_layout.itemAt(1).widget().hide() param_layout.addLayout(download_port_layout) time_layout = QHBoxLayout() time_layout.addWidget(QLabel("测试时长:")) test_time = QDoubleSpinBox() test_time.setRange(1, 10000) test_time.setValue(10) time_layout.addWidget(test_time) time_unit = QComboBox() time_unit.addItems(["秒", "分钟", "小时"]) time_layout.addWidget(time_unit) param_layout.addLayout(time_layout) omit_layout = QHBoxLayout() omit_layout.addWidget(QLabel("跳过前N秒:")) omit_seconds = QSpinBox() omit_seconds.setRange(0, 60) omit_seconds.setValue(5) omit_layout.addWidget(omit_seconds) param_layout.addLayout(omit_layout) streams_layout = QHBoxLayout() streams_layout.addWidget(QLabel("并行流数量:")) streams = QSpinBox() streams.setRange(1, 100) streams.setValue(1) streams_layout.addWidget(streams) param_layout.addLayout(streams_layout) auto_restart = QCheckBox("每6小时自动重跑") auto_restart.setChecked(True) param_layout.addWidget(auto_restart) error_restart = QCheckBox("异常自动重跑") error_restart.setChecked(True) param_layout.addWidget(error_restart) restart_count_layout = QHBoxLayout() restart_count_layout.addWidget(QLabel("重跑次数:")) restart_count = QSpinBox() restart_count.setRange(1, 10) restart_count.setValue(3) restart_count_layout.addWidget(restart_count) param_layout.addLayout(restart_count_layout) param_group.setLayout(param_layout) group_layout.addWidget(param_group) # 删除按钮 delete_btn = QPushButton("删除此组") delete_btn.clicked.connect(lambda: self.remove_test_group(group)) group_layout.addWidget(delete_btn) group.setLayout(group_layout) self.test_groups_layout.addWidget(group) # 保存配置(只保存控件值) self.config['test_groups'][group_id] = { 'name': group_name.text(), 'server_ip': server_ip.text(), 'server_port': server_port.value(), 'server_nic': server_nic.currentText(), # 保存网卡名称 'download_port': download_port.value(), 'client_ip': client_ip.text(), 'client_name': client_name.text(), 'client_nic': client_nic.currentText(), # 保存网卡名称 'direction': test_direction.currentIndex(), 'test_time': test_time.value(), 'time_unit': time_unit.currentIndex(), 'omit': omit_seconds.value(), 'streams': streams.value(), 'auto_restart': auto_restart.isChecked(), 'error_restart': error_restart.isChecked(), 'restart_count': restart_count.value() } # 更新组选择列表 if hasattr(self, 'group_list'): self.update_group_list() def update_port_visibility(self, group_id, direction_idx): """根据测试方向显示/隐藏下载端口配置""" # 找到对应的group_widget for child in self.test_groups_widget.children(): if isinstance(child, QGroupBox) and child.property('group_id') == group_id: # 找到下载端口控件 download_port_label = None download_port_widget = None for layout in child.findChildren(QHBoxLayout): if layout.itemAt(0) and layout.itemAt(0).widget() and layout.itemAt(0).widget().text() == "下载端口号:": download_port_label = layout.itemAt(0).widget() download_port_widget = layout.itemAt(1).widget() break if download_port_label and download_port_widget: # 双向测试时显示下载端口配置 if direction_idx == 2: # 双向 download_port_label.show() download_port_widget.show() else: download_port_label.hide() download_port_widget.hide() break def remove_test_group(self, group_widget): group_id = group_widget.property('group_id') if group_id: # 从布局中移除 self.test_groups_layout.removeWidget(group_widget) group_widget.deleteLater() # 从配置中移除 if group_id in self.config['test_groups']: del self.config['test_groups'][group_id] self.save_config() # 更新组选择列表 if hasattr(self, 'group_list'): self.update_group_list() def update_group_list(self): """更新测试组选择列表""" if not hasattr(self, 'group_list'): return self.group_list.clear() for group_id in self.config['test_groups'].keys(): item = QListWidgetItem(f"测试组 {group_id}") item.setData(Qt.UserRole, group_id) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) self.group_list.addItem(item) def select_all_groups(self): """选择所有测试组""" if not hasattr(self, 'group_list'): return for i in range(self.group_list.count()): item = self.group_list.item(i) item.setCheckState(Qt.Checked) self.update_chart() def deselect_all_groups(self): """取消选择所有测试组""" if not hasattr(self, 'group_list'): return for i in range(self.group_list.count()): item = self.group_list.item(i) item.setCheckState(Qt.Unchecked) self.update_chart() def browse_log_path(self): path = QFileDialog.getExistingDirectory(self, "选择日志保存路径") if path: self.log_path.setText(path) self.config['log_path'] = path self.save_config() def start_test(self): # 验证配置 if not self.config['iperf_path']: QMessageBox.critical(self, "错误", "请先配置iperf3路径") return # 创建管理器(使用配置的iperf路径) self.manager = IperfManager(self.config['iperf_path']) # 禁用UI控件 self.start_btn.setEnabled(False) self.add_group_btn.setEnabled(False) self.stop_btn.setEnabled(True) # 清除图表 self.clear_chart() # 记录开始时间 self.start_time = datetime.now() self.canvas.start_time = self.start_time self.canvas.start_timestamp = time.time() self.exec_log.append(f"[{self.start_time.strftime('%Y-%m-%d %H:%M:%S')}] 测试开始") # 收集测试组配置 test_groups = {} self.nic_map = {} # 重置网卡映射 for group_id, group in self.config['test_groups'].items(): # 计算测试时长(秒)- 修复:转换为整数秒 time_val = group['test_time'] unit_idx = group['time_unit'] if unit_idx == 1: # 分钟 time_val *= 60 elif unit_idx == 2: # 小时 time_val *= 3600 # 转换为整数秒(至少1秒) duration = max(1, int(round(time_val))) test_direction = ["上传", "下载", "双向"][group['direction']] test_groups[group_id] = { 'server_ip': group['server_ip'], 'server_port': group['server_port'], 'client_ip': group['client_ip'], 'direction': test_direction, 'duration': duration, # 使用整数秒 'streams': group['streams'], 'omit': group['omit'], 'upload_port': group['server_port'], # 上传端口 'download_port': group['download_port'] # 下载端口 } try: # 记录网卡映射 server_nic = group['server_nic'] client_nic = group['client_nic'] if server_nic: if server_nic not in self.nic_map: self.nic_map[server_nic] = [] self.nic_map[server_nic].append((group_id, group['server_port'], '服务器')) # 启动服务器(上传) cmd = self.manager.start_server(group_id, test_groups[group_id]['upload_port'], test_groups[group_id]['server_ip'], server_nic) self.exec_log.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 测试组 {group_id} 启动上传服务器: {cmd}") # 如果是双向测试,启动下载服务器 if test_groups[group_id]['direction'] in ["下载", "双向"]: cmd = self.manager.start_server(group_id, test_groups[group_id]['download_port'], test_groups[group_id]['server_ip'], server_nic) self.exec_log.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 测试组 {group_id} 启动下载服务器: {cmd}") # 启动客户端 if test_groups[group_id]['direction'] in ["上传", "双向"]: if client_nic: if client_nic not in self.nic_map: self.nic_map[client_nic] = [] self.nic_map[client_nic].append((group_id, test_groups[group_id]['upload_port'], '客户端(上传)')) cmd = self.manager.start_client( group_id, test_groups[group_id]['server_ip'], # 连接服务器IP test_groups[group_id]['upload_port'], # 连接上传端口 test_groups[group_id]['duration'], # 整数秒 test_groups[group_id]['streams'], test_groups[group_id]['client_ip'], # 绑定客户端IP False, test_groups[group_id]['omit'], client_nic ) self.exec_log.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 测试组 {group_id} 启动上传客户端: {cmd}") if test_groups[group_id]['direction'] in ["下载", "双向"]: if client_nic: if client_nic not in self.nic_map: self.nic_map[client_nic] = [] self.nic_map[client_nic].append((group_id, test_groups[group_id]['download_port'], '客户端(下载)')) cmd = self.manager.start_client( group_id, test_groups[group_id]['server_ip'], # 连接服务器IP test_groups[group_id]['download_port'], # 连接下载端口 test_groups[group_id]['duration'], # 整数秒 test_groups[group_id]['streams'], test_groups[group_id]['client_ip'], # 绑定客户端IP True, # 下载方向 test_groups[group_id]['omit'], client_nic ) self.exec_log.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 测试组 {group_id} 启动下载客户端: {cmd}") except ValueError as e: self.exec_log.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 错误: {str(e)}") QMessageBox.critical(self, "错误", str(e)) self.stop_test() return # 启动数据收集线程 self.data_collector = DataCollectorThread(self.manager, test_groups) self.data_collector.data_updated.connect(self.update_chart) self.data_collector.test_completed.connect(self.handle_test_completed) self.data_collector.test_timeout.connect(self.handle_test_timeout) self.data_collector.start() # 更新网卡映射显示 self.update_nic_status() def stop_test(self): if self.manager: self.manager.stop_all() if self.data_collector: self.data_collector.stop() self.data_collector.quit() self.data_collector.wait() # 启用UI控件 self.start_btn.setEnabled(True) self.add_group_btn.setEnabled(True) self.stop_btn.setEnabled(False) # 记录结束时间 end_time = datetime.now() self.exec_log.append(f"[{end_time.strftime('%Y-%m-%d %H:%M:%S')}] 测试停止") # 清空网卡映射 self.nic_map = {} self.update_nic_status() def handle_test_completed(self, group_id, data): self.exec_log.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 测试组 {group_id} 完成") def handle_test_timeout(self): self.exec_log.append(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 测试超时") self.stop_test() def update_chart(self): if not self.data_collector or not hasattr(self.data_collector, 'data'): return # 获取选中的测试组 selected_groups = set() for i in range(self.group_list.count()): item = self.group_list.item(i) if item.checkState() == Qt.Checked: group_id = item.data(Qt.UserRole) selected_groups.add(group_id) # 更新图表 self.canvas.update_plot(self.data_collector.data, selected_groups) def clear_chart(self): self.canvas.ax.clear() self.canvas.draw() def reset_zoom(self): self.canvas.ax.relim() self.canvas.ax.autoscale_view() self.canvas.draw() def export_chart(self): file_path, _ = QFileDialog.getSaveFileName( self, "导出图表", "", "PNG图像 (*.png);;JPEG图像 (*.jpg);;PDF文件 (*.pdf)" ) if file_path: self.canvas.fig.savefig(file_path) def export_log(self): file_path, _ = QFileDialog.getSaveFileName( self, "导出日志", "", "文本文件 (*.txt);;所有文件 (*)" ) if file_path: with open(file_path, 'w') as f: f.write(self.exec_log.toPlainText()) def export_nic_status(self): file_path, _ = QFileDialog.getSaveFileName( self, "导出网卡状态", "", "文本文件 (*.txt);;所有文件 (*)" ) if file_path: with open(file_path, 'w') as f: f.write(self.nic_status.toPlainText()) f.write("\n\n网卡事件摘要:\n") f.write(self.nic_summary_text.toPlainText()) f.write("\n\n网卡测试组映射:\n") f.write(self.nic_mapping_text.toPlainText()) def clear_nic_events(self): self.nic_events = [] self.nic_summary = {} self.update_nic_status() def update_nic_status(self, initial=False): """更新网卡状态显示""" # 获取当前网卡状态 status_text = "" try: c = wmi.WMI() for adapter in c.Win32_NetworkAdapter(): status = '未知' if adapter.NetConnectionStatus == 2: status = '已连接' elif adapter.NetConnectionStatus == 7: status = '已断开' status_text += f"网卡: {adapter.Name}\n" status_text += f"状态: {status}\n" status_text += f"MAC地址: {adapter.MACAddress}\n" status_text += f"描述: {adapter.Description}\n" status_text += "-"*30 + "\n" except Exception as e: status_text = f"获取网卡状态失败: {str(e)}" self.nic_status.setText(status_text) # 更新网卡事件摘要 summary_text = "" for name, data in self.nic_summary.items(): summary_text += f"网卡: {name}\n" summary_text += f"最后状态: {data['last_status']}\n" summary_text += f"断开次数: {data['down']}\n" summary_text += f"连接次数: {data['up']}\n" summary_text += "-"*30 + "\n" self.nic_summary_text.setText(summary_text) # 更新网卡映射 mapping_text = "" for nic, mappings in self.nic_map.items(): mapping_text += f"网卡: {nic}\n" for group_id, port, role in mappings: mapping_text += f" - 测试组{group_id} {role} 端口{port}\n" mapping_text += "\n" self.nic_mapping_text.setText(mapping_text) # 如果是初始更新,则添加事件日志 if initial and self.nic_events: self.exec_log.append("\n".join(self.nic_events)) def load_config(self): # 这里应该从文件加载配置 pass def save_config(self): # 这里应该保存配置到文件 pass if __name__ == "__main__": app = QApplication(sys.argv) window = IperfVisualizer() window.show() sys.exit(app.exec_())
08-14
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值