You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

193 lines
7.0 KiB
Python

import pika
import uuid
import json
import threading
4 months ago
import socket
import requests
class RpcClient:
def __init__(self, clientName, rabbitHost='localhost', protocolManager=None):
"""
初始化RPC客户端
:param clientName: 客户端名称
:param rabbitHost: RabbitMQ服务器地址
:param protocolManager: 协议管理器实例用于处理读写操作
"""
self.clientName = clientName
self.protocolManager = protocolManager
4 months ago
self.rabbitHost = rabbitHost
self.variables = {}
# self.variables = {
# "temp": {"type": "AI", "min": 0, "max": 100, "value": 25.5},
# "status": {"type": DO", "min": None, "max": None, "value": "OK"},
# }
self.credentials = pika.PlainCredentials('dcs', '123456')
self.connection = pika.BlockingConnection(
pika.ConnectionParameters(host=rabbitHost, credentials=self.credentials)
)
self.channel = self.connection.channel()
self.queueName = f"rpc_queue_{self.clientName}"
self.channel.queue_declare(queue=self.queueName)
4 months ago
# print(f"[{self.clientName}] 等待服务端指令...")
def onRequest(self, ch, method, props, body):
request = json.loads(body)
cmd = request.get("cmd")
response = {}
if cmd == "ping":
4 months ago
# 响应ping命令带本机IP
ip = self.getLocalIp()
response = {"client": self.clientName, "result": "pong", "ip": ip}
elif cmd == "read":
response = {"client": self.clientName, "variables": self.variables}
elif cmd == "write":
# 使用协议管理器处理写入操作
response = self.handleWriteRequest(request)
else:
# 未知命令的响应
response = {"client": self.clientName, "result": "fail", "reason": "unknown command"}
ch.basic_publish(
exchange='',
routing_key=props.reply_to,
properties=pika.BasicProperties(correlation_id=props.correlation_id),
body=json.dumps(response)
)
ch.basic_ack(delivery_tag=method.delivery_tag)
def handleWriteRequest(self, request):
"""
处理写入请求
:param request: 请求数据
:return: 响应数据
"""
varName = '.'.join(request.get("varName").split('.')[:-1])
value = request.get("value")
# 如果有协议管理器,使用它来处理写入
if self.protocolManager:
try:
success = self.protocolManager.writeVariableValue(varName, value)
if success:
return {
"client": self.clientName,
"result": "success",
"varName": varName,
"value": value
}
else:
return {
"client": self.clientName,
"result": "fail",
"reason": "write failed"
}
except Exception as e:
return {
"client": self.clientName,
"result": "fail",
"reason": f"write error: {str(e)}"
}
def setVarContent(self, variableName, value, min, max, type):
if type in ['DI', 'DO']:
min = 0
max = 1
4 months ago
variableName = variableName + '.' + 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]["max"] = max
self.variables[variableName]["type"] = type
def start(self):
"""启动客户端监听(阻塞方法)"""
self.channel.basic_qos(prefetch_count=1)
self.channel.basic_consume(queue=self.queueName, on_message_callback=self.onRequest)
self.channel.start_consuming()
def startNonBlocking(self):
"""非阻塞启动客户端(在后台线程中运行)"""
import threading
self.client_thread = threading.Thread(target=self.start, daemon=True)
self.client_thread.start()
print(f"[{self.clientName}] RPC客户端已在后台线程启动")
def isRunning(self):
"""检查客户端是否正在运行"""
return hasattr(self, 'client_thread') and self.client_thread.is_alive()
def close(self):
"""关闭RPC连接"""
try:
if self.channel and not self.channel.is_closed:
self.channel.queue_delete(queue=self.queueName)
self.channel.close()
if self.connection and not self.connection.is_closed:
self.connection.close()
print(f"[{self.clientName}] 连接已关闭")
except Exception as e:
print(f"[{self.clientName}] 关闭连接时出错: {e}")
4 months ago
def getLocalIp(self):
# 获取本机第一个非回环IPv4地址
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
ip = s.getsockname()[0]
s.close()
return ip
except:
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}"
4 months ago
if __name__ == "__main__":
import sys
clientName = sys.argv[1] if len(sys.argv) > 1 else "Client1"
client = RpcClient(clientName)
client.start()