0728更新 添加公网服务器远程模式

main
zcwBit 3 months ago
parent 25c76a2961
commit 84e9eb0650

@ -70,8 +70,8 @@ class RemoteConnectSettingWidget(QWidget):
# 从站切换到主站,关闭客户端 # 从站切换到主站,关闭客户端
from utils import Globals from utils import Globals
protocolManage = Globals.getValue('protocolManage') protocolManage = Globals.getValue('protocolManage')
if protocolManage and hasattr(protocolManage, 'closeClent'): if protocolManage and hasattr(protocolManage, 'closeClient'):
protocolManage.closeClent() protocolManage.closeClient()
# 移除当前Widget # 移除当前Widget
self.contentLayout.removeWidget(self.currentWidget) self.contentLayout.removeWidget(self.currentWidget)
self.currentWidget.hide() self.currentWidget.hide()

@ -1,8 +1,13 @@
import sys import sys
import socket import socket
import threading import threading
import requests
import json
import time
import uuid
from PyQt5.QtWidgets import QMainWindow, QVBoxLayout, QPushButton, \ from PyQt5.QtWidgets import QMainWindow, QVBoxLayout, QPushButton, \
QListWidget, QLabel, QWidget, QMessageBox, QTextEdit QListWidget, QLabel, QWidget, QMessageBox, QTextEdit, \
QLineEdit, QHBoxLayout, QRadioButton, QButtonGroup, QGroupBox
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer
from PyQt5.QtNetwork import QNetworkInterface,QAbstractSocket from PyQt5.QtNetwork import QNetworkInterface,QAbstractSocket
from datetime import datetime from datetime import datetime
@ -15,6 +20,7 @@ class TcpClient(object):
self.udpPort = 54321 self.udpPort = 54321
self.broadcastAddr = '<broadcast>' self.broadcastAddr = '<broadcast>'
def discoverServers(self): def discoverServers(self):
# print("[*] 正在扫描局域网设备...") # print("[*] 正在扫描局域网设备...")
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@ -106,14 +112,23 @@ class DeviceMasterWidget(QMainWindow):
self.statusLabel = None self.statusLabel = None
self.infoLabel = None self.infoLabel = None
self.disconnectList = None self.disconnectList = None
self.publicServerInput = None
self.publicClientList = None
self.publicClients = []
self.autoRefreshEnabled = False
self.initUI() self.initUI()
# 定时器启动放到控件初始化后 # 定时器启动放到控件初始化后
self.statusTimer = QTimer(self) self.statusTimer = QTimer(self)
self.statusTimer.timeout.connect(self.updateStatusInfo) self.statusTimer.timeout.connect(self.updateStatusInfo)
self.statusTimer.start(1000) self.statusTimer.start(1000)
# 公网客户端自动刷新定时器
self.publicRefreshTimer = QTimer(self)
self.publicRefreshTimer.timeout.connect(self.autoRefreshPublicClients)
def initUI(self): def initUI(self):
self.setWindowTitle('局域网设备发现') self.setWindowTitle('设备连接管理')
# self.setGeometry(1000, 500, 400, 300) # self.setGeometry(1000, 500, 400, 300)
# 主窗口部件 # 主窗口部件
@ -125,6 +140,7 @@ class DeviceMasterWidget(QMainWindow):
layout.addWidget(self.statusLabel) layout.addWidget(self.statusLabel)
self.infoLabel = QLabel('连接信息: 无') self.infoLabel = QLabel('连接信息: 无')
layout.addWidget(self.infoLabel) layout.addWidget(self.infoLabel)
# 新增:断开客户端区 # 新增:断开客户端区
self.disconnectList = QListWidget() self.disconnectList = QListWidget()
self.disconnectList.setFixedHeight(60) self.disconnectList.setFixedHeight(60)
@ -132,29 +148,87 @@ class DeviceMasterWidget(QMainWindow):
layout.addWidget(self.disconnectList) layout.addWidget(self.disconnectList)
self.disconnectList.itemClicked.connect(self.disconnectClient) self.disconnectList.itemClicked.connect(self.disconnectClient)
# 公网服务端配置区域
publicGroup = QGroupBox('公网服务端配置')
publicLayout = QVBoxLayout()
# 服务器地址输入
serverLayout = QHBoxLayout()
serverLayout.addWidget(QLabel('服务器地址:'))
self.publicServerInput = QLineEdit('43.138.48.181')
self.publicServerInput.setPlaceholderText('输入公网服务器IP地址')
serverLayout.addWidget(self.publicServerInput)
publicLayout.addLayout(serverLayout)
# 启动公网服务端按钮
self.publicConnectButton = QPushButton('启动公网服务端模式')
self.publicConnectButton.clicked.connect(self.startPublicServer)
self.publicConnectButton.setObjectName('setButton')
publicLayout.addWidget(self.publicConnectButton)
# 公网客户端扫描区域
publicLayout.addWidget(QLabel('公网服务器客户端列表:'))
self.publicClientList = QListWidget()
self.publicClientList.setFixedHeight(100)
self.publicClientList.itemDoubleClicked.connect(self.connectToPublicClient)
publicLayout.addWidget(self.publicClientList)
# 扫描和连接按钮
publicButtonLayout = QHBoxLayout()
self.scanPublicButton = QPushButton('扫描公网客户端')
self.scanPublicButton.clicked.connect(self.scanPublicClients)
self.scanPublicButton.setObjectName('setButton')
publicButtonLayout.addWidget(self.scanPublicButton)
self.connectPublicButton = QPushButton('连接选中客户端')
self.connectPublicButton.clicked.connect(self.connectToPublicClient)
self.connectPublicButton.setEnabled(False)
self.connectPublicButton.setObjectName('setButton')
publicButtonLayout.addWidget(self.connectPublicButton)
publicLayout.addLayout(publicButtonLayout)
# 自动刷新选项
from PyQt5.QtWidgets import QCheckBox
self.autoRefreshCheckBox = QCheckBox('自动刷新客户端列表 (5秒)')
self.autoRefreshCheckBox.stateChanged.connect(self.toggleAutoRefresh)
publicLayout.addWidget(self.autoRefreshCheckBox)
publicGroup.setLayout(publicLayout)
layout.addWidget(publicGroup)
self.ipAddresslabel = QLabel(self) self.ipAddresslabel = QLabel(self)
self.ipAddresslabel.setObjectName("setlabel") self.ipAddresslabel.setObjectName("setlabel")
self.ipAddresslabel.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) self.ipAddresslabel.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
layout.addWidget(self.ipAddresslabel) layout.addWidget(self.ipAddresslabel)
# 局域网设备发现区域
lanGroup = QGroupBox('局域网设备发现')
lanLayout = QVBoxLayout()
# 标题标签 # 标题标签
self.titleLabel = QLabel('点击"扫描设备"按钮发现局域网中的设备') self.titleLabel = QLabel('点击"扫描设备"按钮发现局域网中的设备')
layout.addWidget(self.titleLabel) lanLayout.addWidget(self.titleLabel)
# 设备列表 # 设备列表
self.deviceList = QListWidget() self.deviceList = QListWidget()
layout.addWidget(self.deviceList) lanLayout.addWidget(self.deviceList)
lanLayout2 = QHBoxLayout()
self.scanButton = QPushButton('扫描设备') self.scanButton = QPushButton('扫描设备')
self.scanButton.clicked.connect(self.startDiscovery) self.scanButton.clicked.connect(self.startDiscovery)
layout.addWidget(self.scanButton)
self.scanButton.setObjectName('setButton') self.scanButton.setObjectName('setButton')
lanLayout2.addWidget(self.scanButton)
self.connectButton = QPushButton('连接设备') self.connectButton = QPushButton('连接设备')
self.connectButton.clicked.connect(self.connectToDevice) self.connectButton.clicked.connect(self.connectToDevice)
self.connectButton.setEnabled(False) self.connectButton.setEnabled(False)
self.connectButton.setObjectName('setButton') self.connectButton.setObjectName('setButton')
layout.addWidget(self.connectButton) lanLayout2.addWidget(self.connectButton)
lanLayout.addLayout(lanLayout2)
lanGroup.setLayout(lanLayout)
layout.addWidget(lanGroup)
self.ipAddresslabel.setText('当前设备IP地址: ' + self.getLocalIp()) self.ipAddresslabel.setText('当前设备IP地址: ' + self.getLocalIp())
@ -191,6 +265,176 @@ class DeviceMasterWidget(QMainWindow):
self.connectButton.setEnabled(True) self.connectButton.setEnabled(True)
def startPublicServer(self):
"""启动公网服务端模式"""
serverIp = self.publicServerInput.text().strip()
if not serverIp:
QMessageBox.warning(self, '警告', '请输入服务器地址')
return
try:
# 设置为服务端模式
Globals.getValue('protocolManage').closeClient()
Globals.getValue('protocolManage').setServerMode(rabbitmqHost=serverIp)
QMessageBox.information(self, '成功', f'已启动公网服务端模式\n服务器地址: {serverIp}')
except Exception as e:
QMessageBox.critical(self, '失败', f'启动公网服务端模式失败: {str(e)}')
def scanPublicClients(self):
"""扫描公网服务器上的客户端"""
serverIp = self.publicServerInput.text().strip()
if not serverIp:
QMessageBox.warning(self, '警告', '请输入服务器地址')
return
try:
# 通过RabbitMQ管理API获取所有RPC队列
url = f"http://{serverIp}:15672/api/queues"
response = requests.get(url, auth=('dcs', '123456'), timeout=10)
queues = response.json()
# 筛选RPC客户端队列
rpc_queues = []
for queue in queues:
queue_name = queue['name']
if queue_name.startswith('rpc_queue_client'):
client_name = queue_name.replace('rpc_queue_', '')
rpc_queues.append({
'name': client_name,
'queue': queue_name,
'messages': queue.get('messages', 0),
'consumers': queue.get('consumers', 0),
'status': 'online' if queue.get('consumers', 0) > 0 else 'offline'
})
# 更新客户端列表
self.publicClients = rpc_queues
self.publicClientList.clear()
if not rpc_queues:
self.publicClientList.addItem('未发现任何客户端')
self.connectPublicButton.setEnabled(False)
else:
for client in rpc_queues:
status_text = "在线" if client['status'] == 'online' else "离线"
item_text = f"{client['name']} ({status_text}) - 消息:{client['messages']}"
self.publicClientList.addItem(item_text)
self.connectPublicButton.setEnabled(True)
QMessageBox.information(self, '扫描完成', f'发现 {len(rpc_queues)} 个客户端')
except Exception as e:
QMessageBox.critical(self, '扫描失败', f'扫描公网客户端失败: {str(e)}')
self.publicClientList.clear()
self.publicClientList.addItem('扫描失败')
self.connectPublicButton.setEnabled(False)
def connectToPublicClient(self):
"""连接到选中的公网客户端"""
selectedIndex = self.publicClientList.currentRow()
if selectedIndex == -1:
QMessageBox.warning(self, '警告', '请先选择一个客户端')
return
if selectedIndex >= len(self.publicClients):
QMessageBox.warning(self, '警告', '选择的客户端无效')
return
selectedClient = self.publicClients[selectedIndex]
clientName = selectedClient['name']
# 检查客户端状态
# if selectedClient['status'] == 'offline':
# reply = QMessageBox.question(self, '客户端离线',
# f'客户端 {clientName} 当前离线,是否仍要尝试连接?',
# QMessageBox.Yes | QMessageBox.No)
# if reply == QMessageBox.No:
# return
try:
# 检查是否已经连接
protocolManage = Globals.getValue('protocolManage')
if protocolManage and hasattr(protocolManage, 'RpcServer') and protocolManage.RpcServer:
existing_clients = protocolManage.RpcServer.getClientNames()
if clientName in existing_clients:
QMessageBox.information(self, '提示', f'客户端 {clientName} 已连接,无需重复连接')
return
# 尝试添加客户端
if protocolManage.RpcServer.addClient(clientName):
QMessageBox.information(self, '连接成功', f'已成功连接到客户端 {clientName}')
# 刷新状态信息
self.updateStatusInfo()
else:
QMessageBox.warning(self, '连接失败', f'无法连接到客户端 {clientName},客户端可能不在线')
else:
QMessageBox.warning(self, '错误', '请先启动公网服务端模式')
except Exception as e:
QMessageBox.critical(self, '连接失败', f'连接客户端失败: {str(e)}')
def autoRefreshPublicClients(self):
"""自动刷新公网客户端列表(静默模式)"""
if not self.autoRefreshEnabled:
return
serverIp = self.publicServerInput.text().strip()
if not serverIp:
return
try:
# 静默扫描,不显示消息框
url = f"http://{serverIp}:15672/api/queues"
response = requests.get(url, auth=('dcs', '123456'), timeout=5)
queues = response.json()
# 筛选RPC客户端队列
rpc_queues = []
for queue in queues:
queue_name = queue['name']
if queue_name.startswith('rpc_queue_client'):
client_name = queue_name.replace('rpc_queue_', '')
rpc_queues.append({
'name': client_name,
'queue': queue_name,
'messages': queue.get('messages', 0),
'consumers': queue.get('consumers', 0),
'status': 'online' if queue.get('consumers', 0) > 0 else 'offline'
})
# 更新客户端列表(保持选中状态)
current_selection = self.publicClientList.currentRow()
self.publicClients = rpc_queues
self.publicClientList.clear()
if not rpc_queues:
self.publicClientList.addItem('未发现任何客户端')
self.connectPublicButton.setEnabled(False)
else:
for client in rpc_queues:
status_text = "在线" if client['status'] == 'online' else "离线"
item_text = f"{client['name']} ({status_text}) - 消息:{client['messages']}"
self.publicClientList.addItem(item_text)
self.connectPublicButton.setEnabled(True)
# 恢复选中状态
if 0 <= current_selection < len(rpc_queues):
self.publicClientList.setCurrentRow(current_selection)
except Exception as e:
# 静默处理错误,不显示消息框
print(f"自动刷新公网客户端失败: {e}")
def toggleAutoRefresh(self, state):
"""切换自动刷新状态"""
self.autoRefreshEnabled = state == 2 # Qt.Checked = 2
if self.autoRefreshEnabled:
self.publicRefreshTimer.start(5000) # 每5秒刷新一次
print("已启用公网客户端自动刷新")
else:
self.publicRefreshTimer.stop()
print("已禁用公网客户端自动刷新")
def connectToDevice(self): def connectToDevice(self):
"""连接选中的设备""" """连接选中的设备"""
selectedIndex = self.deviceList.currentRow() selectedIndex = self.deviceList.currentRow()
@ -418,13 +662,14 @@ class TcpServer(QObject):
class DeviceSlaveWidget(QMainWindow): class DeviceSlaveWidget(QMainWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.setWindowTitle("TCP/UDP 服务器控制") self.setWindowTitle("设备从站控制")
# self.setGeometry(100, 100, 600, 400) # self.setGeometry(100, 100, 600, 400)
self.server = TcpServer() self.server = TcpServer()
self.server.updateSignal.connect(self.updateLog) self.server.updateSignal.connect(self.updateLog)
self.statusLabel = None self.statusLabel = None
self.infoLabel = None self.infoLabel = None
self.publicServerInput = None
self.initUI() self.initUI()
# 定时器启动放到控件初始化后 # 定时器启动放到控件初始化后
self.statusTimer = QTimer(self) self.statusTimer = QTimer(self)
@ -440,20 +685,42 @@ class DeviceSlaveWidget(QMainWindow):
self.infoLabel = QLabel('连接信息: 无') self.infoLabel = QLabel('连接信息: 无')
mainLayout.addWidget(self.infoLabel) mainLayout.addWidget(self.infoLabel)
# 控制按钮 # 公网客户端连接配置
# self.tcpButton = QPushButton("启动 TCP 服务器") publicGroup = QGroupBox('公网客户端连接')
# self.tcpButton.clicked.connect(self.toggleTcpServer) publicLayout = QVBoxLayout()
# self.tcpButton.setObjectName('setButton')
# 服务器地址输入
serverLayout = QHBoxLayout()
serverLayout.addWidget(QLabel('服务器地址:'))
self.publicServerInput = QLineEdit('43.138.48.181')
self.publicServerInput.setPlaceholderText('输入公网服务器IP地址')
serverLayout.addWidget(self.publicServerInput)
publicLayout.addLayout(serverLayout)
# 公网客户端连接按钮
self.publicConnectButton = QPushButton('连接公网服务器')
self.publicConnectButton.clicked.connect(self.connectToPublicServer)
self.publicConnectButton.setObjectName('setButton')
publicLayout.addWidget(self.publicConnectButton)
publicGroup.setLayout(publicLayout)
mainLayout.addWidget(publicGroup)
# 局域网模式控制
lanGroup = QGroupBox('局域网通讯模式')
lanLayout = QVBoxLayout()
self.udpButton = QPushButton("开启远程通讯模式") self.udpButton = QPushButton("开启远程通讯模式")
self.udpButton.clicked.connect(self.toggleUdpServer) self.udpButton.clicked.connect(self.toggleUdpServer)
self.udpButton.setObjectName('setButton') self.udpButton.setObjectName('setButton')
lanLayout.addWidget(self.udpButton)
lanGroup.setLayout(lanLayout)
mainLayout.addWidget(lanGroup)
# 日志显示 # 日志显示
self.logDisplay = QTextEdit() self.logDisplay = QTextEdit()
self.logDisplay.setReadOnly(True) self.logDisplay.setReadOnly(True)
# 添加到布局
# mainLayout.addWidget(self.tcpButton)
mainLayout.addWidget(self.udpButton)
mainLayout.addWidget(QLabel("服务器日志:")) mainLayout.addWidget(QLabel("服务器日志:"))
mainLayout.addWidget(self.logDisplay) mainLayout.addWidget(self.logDisplay)
@ -470,6 +737,27 @@ class DeviceSlaveWidget(QMainWindow):
self.server.startTcpServer() self.server.startTcpServer()
# print("启动 TCP 服务器") # print("启动 TCP 服务器")
def connectToPublicServer(self):
"""连接到公网服务器作为客户端"""
serverIp = self.publicServerInput.text().strip()
if not serverIp:
QMessageBox.warning(self, '警告', '请输入服务器地址')
return
# try:
# 获取客户端名称
from protocol.RPC.RpcClient import RpcClient
clientName = RpcClient.getNextClientNameFromRabbitMQ(serverIp)
print(f"获取到客户端名称: {clientName}")
# 设置为客户端模式
Globals.getValue('protocolManage').closeServer()
Globals.getValue('protocolManage').setClentMode(clientName, rabbitmqHost=serverIp)
QMessageBox.information(self, '连接成功', f'已成功连接到公网服务器 {serverIp}\n客户端名称: {clientName}')
# except Exception as e:
# QMessageBox.critical(self, '连接失败', f'连接公网服务器失败: {str(e)}')
def toggleUdpServer(self): def toggleUdpServer(self):
if self.server.udpRunning: if self.server.udpRunning:
self.server.stopUdpServer() self.server.stopUdpServer()

@ -99,7 +99,7 @@ class ProtocolManage(object):
# 使用非阻塞方式启动RPC客户端 # 使用非阻塞方式启动RPC客户端
self.RpcClient.startNonBlocking() self.RpcClient.startNonBlocking()
def closeClent(self): def closeClient(self):
if self.RpcClient: if self.RpcClient:
self.RpcClient.close() self.RpcClient.close()
self.RpcClient = None self.RpcClient = None
@ -297,6 +297,8 @@ class ProtocolManage(object):
self.tcpVarManager.shutdown() self.tcpVarManager.shutdown()
# 关闭所有Modbus通讯 # 关闭所有Modbus通讯
self.modbusManager.stopAllModbus() self.modbusManager.stopAllModbus()
self.closeClient()
self.closeServer()
# 关闭后台读取线程 # 关闭后台读取线程
if hasattr(self, 'readThreadStop') and hasattr(self, 'readThread'): if hasattr(self, 'readThreadStop') and hasattr(self, 'readThread'):
self.readThreadStop.set() self.readThreadStop.set()

@ -3,6 +3,7 @@ import uuid
import json import json
import threading import threading
import socket import socket
import requests
class RpcClient: class RpcClient:
def __init__(self, clientName, rabbitHost='localhost', protocolManager=None): def __init__(self, clientName, rabbitHost='localhost', protocolManager=None):
@ -63,7 +64,7 @@ class RpcClient:
:param request: 请求数据 :param request: 请求数据
:return: 响应数据 :return: 响应数据
""" """
varName = request.get("varName") varName = '.'.join(request.get("varName").split('.')[:-1])
value = request.get("value") value = request.get("value")
# 如果有协议管理器,使用它来处理写入 # 如果有协议管理器,使用它来处理写入
@ -95,7 +96,12 @@ class RpcClient:
min = 0 min = 0
max = 1 max = 1
variableName = variableName + '.' + self.clientName variableName = variableName + '.' + self.clientName
self.variables[variableName]["value"] = value + '.' + self.clientName
# 确保变量条目存在,如果不存在则创建
if variableName not in self.variables:
self.variables[variableName] = {}
self.variables[variableName]["value"] = str(value)
self.variables[variableName]["min"] = min self.variables[variableName]["min"] = min
self.variables[variableName]["max"] = max self.variables[variableName]["max"] = max
self.variables[variableName]["type"] = type self.variables[variableName]["type"] = type
@ -122,6 +128,7 @@ class RpcClient:
"""关闭RPC连接""" """关闭RPC连接"""
try: try:
if self.channel and not self.channel.is_closed: if self.channel and not self.channel.is_closed:
self.channel.queue_delete(queue=self.queueName)
self.channel.close() self.channel.close()
if self.connection and not self.connection.is_closed: if self.connection and not self.connection.is_closed:
self.connection.close() self.connection.close()
@ -138,7 +145,46 @@ class RpcClient:
s.close() s.close()
return ip return ip
except: except:
return '127.0.0.1' return '127.0.0.1'\
@classmethod
def getNextClientNameFromRabbitMQ(cls, rabbitHost, username='dcs', password='123456'):
# try:
# 获取所有队列
url = f"http://{rabbitHost}:15672/api/queues"
response = requests.get(url, auth=(username, password), timeout=5)
queues = response.json()
print(queues)
# 筛选RPC队列
if not queues:
return "client1"
rpcQueues = [q['name'] for q in queues if q['name'].startswith('rpc_queue_client')]
# 提取客户端编号
clientNumbers = []
for queueName in rpcQueues:
# rpc_queue_client1 -> client1 -> 1
if queueName.startswith('rpc_queue_client'):
try:
numberStr = queueName.replace('rpc_queue_client', '')
if numberStr.isdigit():
clientNumbers.append(int(numberStr))
except:
continue
# 返回下一个可用编号
if not clientNumbers:
return "client1"
else:
nextNumber = max(clientNumbers) + 1
return f"client{nextNumber}"
# except Exception as e:
# print(f"从RabbitMQ获取客户端名称失败: {e}")
# # 降级方案:使用时间戳
# import time
# timestamp = int(time.time() % 10000)
# return f"client{timestamp}"
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys

Loading…
Cancel
Save