import sys import socket import threading from PyQt5.QtWidgets import QMainWindow, QVBoxLayout, QPushButton, \ QListWidget, QLabel, QWidget, QMessageBox, QTextEdit from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer from PyQt5.QtNetwork import QNetworkInterface,QAbstractSocket from datetime import datetime from utils import Globals class TcpClient(object): def __init__(self): # 配置信息 self.udpPort = 54321 self.broadcastAddr = '' def discoverServers(self): # print("[*] 正在扫描局域网设备...") udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udpSock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) udpSock.settimeout(2) udpSock.sendto(b"DISCOVERY_REQUEST", (self.broadcastAddr, self.udpPort)) servers = [] seen = set() local_ips = self.getLocalIps() try: while True: data, addr = udpSock.recvfrom(1024) if data.startswith(b"DISCOVERY_RESPONSE"): _, hostname, tcpPort = data.decode('utf-8').split(':') serverIp = addr[0] # 跳过本机IP if serverIp in local_ips: continue key = (serverIp, int(tcpPort)) if key in seen: continue seen.add(key) servers.append({ 'ip': (serverIp,), 'port': int(tcpPort), 'hostname': hostname }) print(data.decode('utf-8')) except socket.timeout: pass finally: udpSock.close() return servers def connectToServer(self, ip, tcpPort): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: # 显式转换类型确保安全 client.connect((str(ip), int(tcpPort))) # print(f"[+] 已连接到 {ip}:{tcpPort}") Globals.getValue('protocolManage').setServerMode() clientName = Globals.getValue('protocolManage').RpcServer.getNextClientName() client.send(clientName.encode('utf-8')) response = client.recv(1024) # print(f"服务端响应: {response.decode('utf-8')}") resClientName = response.decode('utf-8') Globals.getValue('protocolManage').addClient(clientName = resClientName) QMessageBox.information(None, '连接成功', f'已成功连接到从站 {ip}:{tcpPort}') return True except Exception as e: print(f"连接失败: {e}") return False finally: client.close() def getLocalIps(self): """获取本机所有IPv4地址""" ips = set() for interface in QNetworkInterface.allInterfaces(): if interface.flags() & QNetworkInterface.IsUp: for entry in interface.addressEntries(): ip = entry.ip() if ip.protocol() == QAbstractSocket.IPv4Protocol and not ip.isLoopback(): ips.add(ip.toString()) return ips class DiscoveryThread(QThread): """用于在后台执行设备发现的线程""" discoveryFinished = pyqtSignal(list) # 信号,携带发现的设备列表 def __init__(self, tcpClient): super().__init__() self.tcpClient = tcpClient def run(self): servers = self.tcpClient.discoverServers() self.discoveryFinished.emit(servers) class DeviceMasterWidget(QMainWindow): def __init__(self): super().__init__() self.tcpClient = TcpClient() self.servers = [] self.discoveryThread = None self.statusLabel = None self.infoLabel = None self.disconnectList = None self.initUI() # 定时器启动放到控件初始化后 self.statusTimer = QTimer(self) self.statusTimer.timeout.connect(self.updateStatusInfo) self.statusTimer.start(1000) def initUI(self): self.setWindowTitle('局域网设备发现') # self.setGeometry(1000, 500, 400, 300) # 主窗口部件 centralWidget = QWidget() self.setCentralWidget(centralWidget) layout = QVBoxLayout() self.statusLabel = QLabel('服务器模式: 无') layout.addWidget(self.statusLabel) self.infoLabel = QLabel('连接信息: 无') layout.addWidget(self.infoLabel) # 新增:断开客户端区 self.disconnectList = QListWidget() self.disconnectList.setFixedHeight(60) layout.addWidget(QLabel('已连接客户端(点击可断开):')) layout.addWidget(self.disconnectList) self.disconnectList.itemClicked.connect(self.disconnectClient) self.ipAddresslabel = QLabel(self) self.ipAddresslabel.setObjectName("setlabel") self.ipAddresslabel.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) layout.addWidget(self.ipAddresslabel) # 标题标签 self.titleLabel = QLabel('点击"扫描设备"按钮发现局域网中的设备') layout.addWidget(self.titleLabel) # 设备列表 self.deviceList = QListWidget() layout.addWidget(self.deviceList) self.scanButton = QPushButton('扫描设备') self.scanButton.clicked.connect(self.startDiscovery) layout.addWidget(self.scanButton) self.scanButton.setObjectName('setButton') self.connectButton = QPushButton('连接设备') self.connectButton.clicked.connect(self.connectToDevice) self.connectButton.setEnabled(False) self.connectButton.setObjectName('setButton') layout.addWidget(self.connectButton) self.ipAddresslabel.setText('当前设备IP地址: ' + self.getLocalIp()) centralWidget.setLayout(layout) # self.show() def startDiscovery(self): """开始扫描设备""" self.scanButton.setEnabled(False) self.titleLabel.setText('正在扫描局域网设备...') self.deviceList.clear() # 创建并启动发现线程 self.discoveryThread = DiscoveryThread(self.tcpClient) self.discoveryThread.discoveryFinished.connect(self.onDiscoveryFinished) self.discoveryThread.start() def onDiscoveryFinished(self, servers): """设备扫描完成后的回调""" self.scanButton.setEnabled(True) self.servers = servers if not servers: self.titleLabel.setText('未发现任何设备') QMessageBox.information(self, '提示', '未发现任何设备') return self.titleLabel.setText(f'发现 {len(servers)} 个设备:') # 在列表中显示设备 for server in servers: itemText = f"{server['hostname']} ({server['ip'][0]})" self.deviceList.addItem(itemText) self.connectButton.setEnabled(True) def connectToDevice(self): """连接选中的设备""" selectedIndex = self.deviceList.currentRow() if selectedIndex == -1: QMessageBox.warning(self, '警告', '请先选择一个设备') return selectedServer = self.servers[selectedIndex] from utils import Globals protocolManage = Globals.getValue('protocolManage') already_connected = False ip = selectedServer['ip'][0] if isinstance(selectedServer['ip'], (list, tuple)) else selectedServer['ip'] if protocolManage and hasattr(protocolManage, 'RpcServer') and protocolManage.RpcServer: clients = protocolManage.RpcServer.getClientNames() ipMap = protocolManage.RpcServer.getClientIpMap() if hasattr(protocolManage.RpcServer, 'getClientIpMap') else {} for c in clients: if ipMap.get(c, '') == ip: already_connected = True break if already_connected: QMessageBox.information(self, '提示', f'客户端 {ip} 已连接,无需重复连接') return self.tcpClient.connectToServer(ip, selectedServer['port']) def getLocalIp(self): for interface in QNetworkInterface.allInterfaces(): if interface.flags() & QNetworkInterface.IsUp: for entry in interface.addressEntries(): ip = entry.ip() # 修改为新的枚举访问方式 if ip.protocol() == QAbstractSocket.IPv4Protocol and not ip.isLoopback(): return ip.toString() return "192.168.1.1" # 默认回退值 def cleanup(self): # 释放后台线程 if self.discoveryThread and self.discoveryThread.isRunning(): self.discoveryThread.terminate() self.discoveryThread.wait() def closeEvent(self, event): self.cleanup() event.accept() def __del__(self): self.cleanup() def updateStatusInfo(self): protocolManage = Globals.getValue('protocolManage') mode = '无' info = '无' if protocolManage: if hasattr(protocolManage, 'RpcServer') and protocolManage.RpcServer: mode = '服务端模式' try: clients = protocolManage.RpcServer.getClientNames() clientIpMap = protocolManage.RpcServer.getClientIpMap() if hasattr(protocolManage.RpcServer, 'getClientIpMap') else {} info = '已连接客户端: ' + (', '.join([f"{c} ({clientIpMap.get(c, '')})" for c in clients]) if clients else '无') self.disconnectList.clear() for c in clients: ip = clientIpMap.get(c, '') display = f"{c} ({ip})" if ip else c self.disconnectList.addItem(display) except: info = '已连接客户端: 无' self.disconnectList.clear() elif hasattr(protocolManage, 'RpcClient') and protocolManage.RpcClient: mode = '客户端模式' try: clientName = protocolManage.RpcClient.clientName serverInfo = protocolManage.RpcClient.rabbitHost info = f'客户端名: {clientName} 服务器: {serverInfo}' self.disconnectList.clear() except: info = '客户端信息获取失败' self.disconnectList.clear() self.statusLabel.setText(f'服务器模式: {mode}') self.infoLabel.setText(f'连接信息: {info}') def disconnectClient(self, item): # 支持“客户端名 (IP)”格式 text = item.text() clientName = text.split(' ')[0] from utils import Globals protocolManage = Globals.getValue('protocolManage') if protocolManage and hasattr(protocolManage, 'disconnectClient'): protocolManage.disconnectClient(clientName) QMessageBox.information(self, '断开连接', f'已断开客户端 {clientName}') # 立即刷新列表 self.updateStatusInfo() class TcpServer(QObject): updateSignal = pyqtSignal(str) def __init__(self): super().__init__() # 配置信息 self.tcpPort = 12345 # 服务端TCP监听端口 self.udpPort = 54321 # 设备发现UDP端口 self.broadcastAddr = '' # 广播地址 self.tcpRunning = False self.udpRunning = False self.tcpThread = None self.udpThread = None self.serverSocket = None self.udpSocket = None def log(self, message): currentTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S") logMessage = f"[{currentTime}] {message}" self.updateSignal.emit(logMessage) def tcpServer(self): try: self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.serverSocket.settimeout(1) # 设置1秒超时 self.serverSocket.bind(('0.0.0.0', self.tcpPort)) self.serverSocket.listen(5) self.log(f"TCP服务端已启动,监听端口 {self.tcpPort}") while self.tcpRunning: try: clientSock, addr = self.serverSocket.accept() self.log(f"接收到来自 {addr} 的连接") clientThread = threading.Thread(target=self.handleClient, args=(clientSock,addr)) clientThread.start() except socket.timeout: continue # 超时后继续检查运行标志 except: break except Exception as e: self.log(f"TCP服务器错误: {e}") finally: if self.serverSocket: self.serverSocket.close() self.log("TCP服务端已停止") def handleClient(self, clientSock, addr): try: while True: data = clientSock.recv(1024) if not data: break Globals.getValue('protocolManage').closeServer() Globals.getValue('protocolManage').setClentMode(data.decode('utf-8'), rabbitmqHost = addr[0]) self.log(f"收到消息: {data.decode('utf-8')}") clientSock.send(data) # 返回确认 # 新增:弹窗提示本机已被连接 # QMessageBox.information(None, '连接提示', f'本机已被 {addr[0]} 连接') except Exception as e: self.log(f"客户端异常断开: {e}") finally: clientSock.close() def udpDiscoveryServer(self): try: self.udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.udpSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.udpSocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) self.udpSocket.bind(('', self.udpPort)) self.log(f"UDP发现服务已启动,监听端口 {self.udpPort}") while self.udpRunning: try: data, addr = self.udpSocket.recvfrom(1024) if data.decode('utf-8') == "DISCOVERY_REQUEST": self.log(f"收到来自 {addr} 的发现请求") response = f"DISCOVERY_RESPONSE:{socket.gethostname()}:{self.tcpPort}" # print(response) self.udpSocket.sendto(response.encode('utf-8'), addr) except: break except Exception as e: self.log(f"UDP服务器错误: {e}") finally: if self.udpSocket: self.udpSocket.close() self.log("UDP发现服务已停止") def startTcpServer(self): if not self.tcpRunning: self.tcpRunning = True self.tcpThread = threading.Thread(target=self.tcpServer) self.tcpThread.start() def stopTcpServer(self): if self.tcpRunning: self.tcpRunning = False # 唤醒accept阻塞,安全关闭线程 if self.serverSocket: try: self.serverSocket.shutdown(socket.SHUT_RDWR) except: pass try: self.serverSocket.close() except: pass if self.tcpThread and self.tcpThread.is_alive(): self.tcpThread.join(timeout=1) def startUdpServer(self): if not self.udpRunning: self.udpRunning = True self.udpThread = threading.Thread(target=self.udpDiscoveryServer) self.udpThread.start() def stopUdpServer(self): if self.udpRunning: self.udpRunning = False # 唤醒recvfrom阻塞,安全关闭线程 if self.udpSocket: try: self.udpSocket.shutdown(socket.SHUT_RDWR) except: pass try: self.udpSocket.close() except: pass if self.udpThread and self.udpThread.is_alive(): self.udpThread.join(timeout=1) class DeviceSlaveWidget(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("TCP/UDP 服务器控制") # self.setGeometry(100, 100, 600, 400) self.server = TcpServer() self.server.updateSignal.connect(self.updateLog) self.statusLabel = None self.infoLabel = None self.initUI() # 定时器启动放到控件初始化后 self.statusTimer = QTimer(self) self.statusTimer.timeout.connect(self.updateStatusInfo) self.statusTimer.start(1000) def initUI(self): # 主布局 mainLayout = QVBoxLayout() self.statusLabel = QLabel('服务器模式: 无') mainLayout.addWidget(self.statusLabel) self.infoLabel = QLabel('连接信息: 无') mainLayout.addWidget(self.infoLabel) # 控制按钮 # self.tcpButton = QPushButton("启动 TCP 服务器") # self.tcpButton.clicked.connect(self.toggleTcpServer) # self.tcpButton.setObjectName('setButton') self.udpButton = QPushButton("开启远程通讯模式") self.udpButton.clicked.connect(self.toggleUdpServer) self.udpButton.setObjectName('setButton') # 日志显示 self.logDisplay = QTextEdit() self.logDisplay.setReadOnly(True) # 添加到布局 # mainLayout.addWidget(self.tcpButton) mainLayout.addWidget(self.udpButton) mainLayout.addWidget(QLabel("服务器日志:")) mainLayout.addWidget(self.logDisplay) # 设置中心部件 container = QWidget() container.setLayout(mainLayout) self.setCentralWidget(container) def toggleTcpServer(self): if self.server.tcpRunning: self.server.stopTcpServer() # print("关闭 TCP 服务器") else: self.server.startTcpServer() # print("启动 TCP 服务器") def toggleUdpServer(self): if self.server.udpRunning: self.server.stopUdpServer() self.udpButton.setText("开启远程通讯模式") else: self.server.startUdpServer() self.udpButton.setText("关闭远程通讯模式") self.toggleTcpServer() def updateLog(self, message): self.logDisplay.append(message) def cleanup(self): if self.server.tcpRunning: self.server.stopTcpServer() if self.server.udpRunning: self.server.stopUdpServer() def closeEvent(self, event): self.cleanup() event.accept() def __del__(self): # print(111111111111111111) self.cleanup() def updateStatusInfo(self): protocolManage = Globals.getValue('protocolManage') mode = '无' info = '无' if protocolManage: if hasattr(protocolManage, 'RpcClient') and protocolManage.RpcClient: mode = '客户端模式' try: clientName = protocolManage.RpcClient.clientName serverInfo = protocolManage.RpcClient.rabbitHost info = f'客户端名: {clientName} 服务器: {serverInfo}' except: info = '客户端信息获取失败' else: # 没有RpcClient,显示无 mode = '无' info = '无' else: mode = '无' info = '无' self.statusLabel.setText(f'服务器模式: {mode}') self.infoLabel.setText(f'连接信息: {info}')