394 lines
21 KiB
Python
394 lines
21 KiB
Python
|
|
import socket
|
|||
|
|
import binascii
|
|||
|
|
import sys
|
|||
|
|
import select
|
|||
|
|
from loguru import logger
|
|||
|
|
from pathlib import Path
|
|||
|
|
sys.path.append(str(Path(__file__).parent.parent.parent))
|
|||
|
|
|
|||
|
|
from midware.config.base_config import UDP_CONFIG
|
|||
|
|
# from midware.config.base_config import DEVICE_CONFIG_DICT
|
|||
|
|
'''
|
|||
|
|
2. UDP 通信层(midware/network/udp_handler.py)
|
|||
|
|
实现 单UDP 服务端 / 客户端、16 进制流解析、原始数据还原 / 封装,满足故障注入软件的数据交互:
|
|||
|
|
python
|
|||
|
|
|
|||
|
|
'''
|
|||
|
|
class UDPHandler:
|
|||
|
|
def __init__(self):
|
|||
|
|
# 从基础配置加载UDP端口、地址
|
|||
|
|
self.local_ip = UDP_CONFIG["LOCAL_IP"]
|
|||
|
|
self.local_port = UDP_CONFIG["LOCAL_PORT"]
|
|||
|
|
self.remote_ip = UDP_CONFIG["FAULT_INJECT_IP"]
|
|||
|
|
self.remote_port = UDP_CONFIG["FAULT_INJECT_PORT"]
|
|||
|
|
self.buffer_size = UDP_CONFIG["BUFFER_SIZE"]
|
|||
|
|
# 创建UDP套接字
|
|||
|
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|||
|
|
self.sock.bind((self.local_ip, self.local_port))
|
|||
|
|
logger.info(f"UDP服务启动:{self.local_ip}:{self.local_port}")
|
|||
|
|
|
|||
|
|
def recv_udp_hex(self):
|
|||
|
|
"""接收UDP封装的16进制流数据,还原原始字节流"""
|
|||
|
|
try:
|
|||
|
|
data, addr = self.sock.recvfrom(self.buffer_size)
|
|||
|
|
if addr[0] != self.remote_ip:
|
|||
|
|
logger.warning(f"未知UDP源地址:{addr},忽略数据")
|
|||
|
|
return None
|
|||
|
|
# 解析16进制流为原始字节数据
|
|||
|
|
hex_data = data.decode("utf-8").strip()
|
|||
|
|
raw_data = binascii.unhexlify(hex_data)
|
|||
|
|
logger.debug(f"接收UDP数据:{hex_data},还原原始数据长度:{len(raw_data)}")
|
|||
|
|
return raw_data
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"UDP接收失败:{str(e)}", exc_info=True)
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def send_udp_hex(self, raw_data):
|
|||
|
|
"""将原始字节流封装为16进制流,通过UDP发送给故障注入软件"""
|
|||
|
|
try:
|
|||
|
|
# 原始数据转16进制字符串
|
|||
|
|
hex_data = binascii.hexlify(raw_data).decode("utf-8")
|
|||
|
|
self.sock.sendto(hex_data.encode("utf-8"), (self.remote_ip, self.remote_port))
|
|||
|
|
logger.debug(f"发送UDP数据:{hex_data},原始数据长度:{len(raw_data)}")
|
|||
|
|
return True
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"UDP发送失败:{str(e)}", exc_info=True)
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def close(self):
|
|||
|
|
"""关闭UDP套接字"""
|
|||
|
|
self.sock.close()
|
|||
|
|
logger.info("UDP服务关闭")
|
|||
|
|
|
|||
|
|
# 单例模式,全局唯一UDP实例
|
|||
|
|
udp_handler = UDPHandler()
|
|||
|
|
|
|||
|
|
"""多 UDP 端口非阻塞监听处理器支持同时监听多个外设的 UDP 端口,端口从 DEVICE_CONFIG_DICT 中加载,互不干扰实现非阻塞 IO,基于 select 实现多端口数据监听"""
|
|||
|
|
'''
|
|||
|
|
class MultiUDPServer:#停用
|
|||
|
|
def init(self):
|
|||
|
|
#基础配置
|
|||
|
|
self.local_ip = UDP_CONFIG["LOCAL_IP"]
|
|||
|
|
self.buffer_size = UDP_CONFIG["BUFFER_SIZE"]
|
|||
|
|
self.remote_fault_ip = UDP_CONFIG["FAULT_INJECT_IP"]
|
|||
|
|
self.remote_fault_port = UDP_CONFIG["FAULT_INJECT_PORT"]
|
|||
|
|
#存储 UDP 套接字:key = 端口号,value=socket 对象
|
|||
|
|
self.udp_sockets = {}
|
|||
|
|
#存储发送给故障注入软件的统一套接字(单例)
|
|||
|
|
self.fault_sock = None
|
|||
|
|
#初始化多端口监听和故障注入发送套接字
|
|||
|
|
self._init_sockets()
|
|||
|
|
logger.info(f"多 UDP 端口非阻塞服务初始化完成,监听端口:{list (self.udp_sockets.keys ())}")
|
|||
|
|
|
|||
|
|
def _init_sockets (self):
|
|||
|
|
"""初始化多端口监听套接字和故障注入发送套接字,均为非阻塞模式"""
|
|||
|
|
#1. 初始化故障注入软件的发送套接字(非阻塞)
|
|||
|
|
self.fault_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|||
|
|
self.fault_sock.setblocking(False)
|
|||
|
|
#2. 从外设配置中加载所有 UDP 端口,初始化监听套接字(非阻塞)
|
|||
|
|
if not DEVICE_CONFIG_DICT:
|
|||
|
|
logger.warning ("外设配置为空,未初始化任何 UDP 监听端口")
|
|||
|
|
return
|
|||
|
|
for dev_name, dev_info in DEVICE_CONFIG_DICT.items ():
|
|||
|
|
udp_port = dev_info ["udp_port"]
|
|||
|
|
if udp_port in self.udp_sockets:
|
|||
|
|
logger.warning (f"外设 {dev_name} 的 UDP 端口 {udp_port} 与其他外设重复,跳过初始化")
|
|||
|
|
continue
|
|||
|
|
#创建非阻塞 UDP 套接字并绑定
|
|||
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|||
|
|
sock.setblocking(False)
|
|||
|
|
#开启地址复用,避免端口占用问题
|
|||
|
|
sock.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|||
|
|
sock.bind ((self.local_ip, udp_port))
|
|||
|
|
self.udp_sockets [udp_port] = socklogger.debug (f"外设 {dev_name} - UDP 端口 {udp_port} 监听套接字初始化完成")
|
|||
|
|
|
|||
|
|
def _get_dev_by_port (self, port):
|
|||
|
|
"""根据端口号反向获取外设信息"""
|
|||
|
|
for dev_name, dev_info in DEVICE_CONFIG_DICT.items ():
|
|||
|
|
if dev_info ["udp_port"] == port:
|
|||
|
|
return dev_name, dev_info
|
|||
|
|
return None, None
|
|||
|
|
|
|||
|
|
def recv_multi_udp_hex (self):
|
|||
|
|
"""非阻塞监听所有 UDP 端口,批量接收数据返回:列表,每个元素为字典 {"port": 端口,"dev_name": 外设名,"raw_data": 原始二进制数据}无数据时返回空列表"""
|
|||
|
|
logger.info(f"开始非阻塞监听所有 UDP 端口...")
|
|||
|
|
recv_data_list = []
|
|||
|
|
if not self.udp_sockets:
|
|||
|
|
return recv_data_list
|
|||
|
|
#使用 select 实现非阻塞多套接字监听,超时时间 0.001s(兼顾实时性和性能)
|
|||
|
|
readable_socks, _, _ = select.select(self.udp_sockets.values(), [], [], 0.001)
|
|||
|
|
for sock in readable_socks:
|
|||
|
|
try:
|
|||
|
|
#获取当前套接字绑定的端口
|
|||
|
|
port = sock.getsockname()[1]
|
|||
|
|
dev_name, _ = self._get_dev_by_port(port)
|
|||
|
|
#接收数据(非阻塞,不会卡主)
|
|||
|
|
data, addr = sock.recvfrom(self.buffer_size)
|
|||
|
|
#校验数据源(仅接收故障注入软件的数据包)
|
|||
|
|
if addr [0] != self.remote_fault_ip:
|
|||
|
|
logger.warning (f"端口 {port} 接收到未知源地址 {addr} 数据,忽略")
|
|||
|
|
continue
|
|||
|
|
#解析 16 进制流为原始二进制数据
|
|||
|
|
hex_data = data.decode ("utf-8", errors="ignore").strip ()
|
|||
|
|
raw_data = binascii.unhexlify (hex_data) if hex_data else b""
|
|||
|
|
if raw_data:
|
|||
|
|
recv_data_list.append ({"port": port,"dev_name": dev_name,"raw_data": raw_data})
|
|||
|
|
logger.debug (f"端口 {port}({dev_name}) 接收 UDP 数据:{hex_data [:32]}...,原始数据长度:{len (raw_data)}")
|
|||
|
|
except binascii.Error:
|
|||
|
|
logger.error (f"端口 {port} 接收到非法 16 进制数据,解析失败")
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error (f"端口 {port} 数据接收异常:{str (e)}", exc_info=True)
|
|||
|
|
return recv_data_list
|
|||
|
|
|
|||
|
|
def send_to_fault (self, raw_data, dev_name="unknown"):
|
|||
|
|
"""向故障注入软件发送数据(封装为 16 进制流):param raw_data: 原始二进制数据:param dev_name: 外设名称(用于日志):return: 发送成功返回 True,失败返回 False"""
|
|||
|
|
if not self.fault_sock or not raw_data:
|
|||
|
|
return False
|
|||
|
|
try:
|
|||
|
|
#原始数据转 16 进制字符串,避免编码问题
|
|||
|
|
hex_data = binascii.hexlify(raw_data).decode("utf-8")
|
|||
|
|
#非阻塞发送
|
|||
|
|
self.fault_sock.sendto (hex_data.encode ("utf-8"), (self.remote_fault_ip, self.remote_fault_port))
|
|||
|
|
logger.debug (f"外设 {dev_name} 向故障注入软件发送 UDP 数据:{hex_data [:32]}...,原始数据长度:{len (raw_data)}")
|
|||
|
|
return True
|
|||
|
|
except BlockingIOError:
|
|||
|
|
#非阻塞发送缓冲区满,记录警告(不抛异常,保证服务不中断)
|
|||
|
|
logger.warning (f"外设 {dev_name} 发送数据至故障注入软件时缓冲区满,发送失败")
|
|||
|
|
return False
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error (f"外设 {dev_name} 向故障注入软件发送数据异常:{str (e)}", exc_info=True)
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def reload_sockets (self):
|
|||
|
|
"""重新加载 UDP 套接字(外设配置更新后调用)关闭原有套接字,重新从 DEVICE_CONFIG_DICT 初始化"""
|
|||
|
|
logger.info("开始重新加载 UDP 监听套接字")
|
|||
|
|
|
|||
|
|
#关闭所有原有监听套接字
|
|||
|
|
for port, sock in self.udp_sockets.items ():
|
|||
|
|
sock.close ()
|
|||
|
|
logger.debug (f"关闭 UDP 端口 {port} 监听套接字")
|
|||
|
|
#清空套接字字典,重新初始化
|
|||
|
|
self.udp_sockets.clear()
|
|||
|
|
self._init_sockets()
|
|||
|
|
logger.info(f"UDP 套接字重新加载完成,当前监听端口:{list (self.udp_sockets.keys ())}")
|
|||
|
|
|
|||
|
|
def close (self):
|
|||
|
|
"""关闭所有套接字,释放资源"""
|
|||
|
|
#关闭监听套接字
|
|||
|
|
for port, sock in self.udp_sockets.items ():
|
|||
|
|
sock.close ()
|
|||
|
|
logger.debug (f"关闭 UDP 端口 {port} 监听套接字")
|
|||
|
|
#关闭故障注入发送套接字
|
|||
|
|
if self.fault_sock:
|
|||
|
|
self.fault_sock.close ()
|
|||
|
|
logger.debug ("关闭故障注入软件 UDP 发送套接字")
|
|||
|
|
self.udp_sockets.clear ()
|
|||
|
|
self.fault_sock = None
|
|||
|
|
logger.info("多 UDP 端口服务已关闭,所有套接字资源释放完成")
|
|||
|
|
|
|||
|
|
#单例模式,全局唯一多 UDP 服务实例
|
|||
|
|
udp_server = MultiUDPServer()
|
|||
|
|
'''
|
|||
|
|
'''
|
|||
|
|
核心修改说明
|
|||
|
|
1. 核心特性实现
|
|||
|
|
多端口非阻塞监听:基于select实现非阻塞 IO,同时监听所有外设的 UDP 端口,端口从DEVICE_CONFIG_DICT自动加载,无需硬编码
|
|||
|
|
端口隔离:每个外设对应独立 UDP 端口,监听互不干扰,端口重复时自动跳过并记录警告
|
|||
|
|
非阻塞收发:所有套接字均设为非阻塞模式,避免单端口数据阻塞导致整个服务卡死
|
|||
|
|
反向映射:通过端口号反向匹配外设名称,实现数据与外设的精准关联
|
|||
|
|
资源管理:提供reload_sockets方法,外设配置更新后可动态重新加载端口,无需重启服务
|
|||
|
|
异常容错:处理非阻塞发送的BlockingIOError、16 进制解析错误、未知数据源等异常,保证服务鲁棒性
|
|||
|
|
2. 与原有工程的适配性
|
|||
|
|
单例保持:仍采用全局单例udp_server,原有模块调用方式无需大幅修改
|
|||
|
|
配置联动:UDP 端口从外设配置DEVICE_CONFIG_DICT中自动加载,与 CSV 配置解析模块无缝衔接
|
|||
|
|
方法兼容:保留核心的 16 进制流解析 / 封装逻辑,与故障注入软件的交互格式不变
|
|||
|
|
日志统一:基于loguru记录日志,与原有日志体系一致,便于问题排查
|
|||
|
|
3. 关键方法调用示例
|
|||
|
|
# 1. 批量接收所有端口的UDP数据
|
|||
|
|
from midware.network.udp_handler import udp_server
|
|||
|
|
data_list = udp_server.recv_multi_udp_hex()
|
|||
|
|
for data in data_list:
|
|||
|
|
dev_name = data["dev_name"]
|
|||
|
|
raw_data = data["raw_data"]
|
|||
|
|
# 对每个外设的原始数据进行协议处理...
|
|||
|
|
|
|||
|
|
# 2. 向故障注入软件发送数据
|
|||
|
|
udp_server.send_to_fault(raw_data, dev_name="Uart0")
|
|||
|
|
|
|||
|
|
# 3. 外设配置更新后,重新加载UDP端口
|
|||
|
|
udp_server.reload_sockets()
|
|||
|
|
|
|||
|
|
# 4. 程序退出时关闭所有套接字
|
|||
|
|
udp_server.close()
|
|||
|
|
4. 性能优化点
|
|||
|
|
select 超时优化:设置超时时间0.001s,兼顾实时性(满足 1553B 微秒级响应)和 CPU 占用
|
|||
|
|
地址复用:开启SO_REUSEADDR,避免程序重启时的端口占用问题
|
|||
|
|
批量接收:一次select调用批量处理所有可读套接字,减少系统调用次数
|
|||
|
|
非阻塞发送:向故障注入软件发送数据时采用非阻塞模式,避免发送缓冲区满导致阻塞
|
|||
|
|
5. 与基础配置的联动
|
|||
|
|
确保base_config.py中UDP_CONFIG保留基础配置项,无需修改:
|
|||
|
|
UDP_CONFIG = {
|
|||
|
|
"LOCAL_IP": "127.0.0.1", # 本地监听IP
|
|||
|
|
"FAULT_INJECT_IP": "127.0.0.1",# 故障注入软件IP
|
|||
|
|
"FAULT_INJECT_PORT": 8889, # 故障注入软件接收端口
|
|||
|
|
"BUFFER_SIZE": 4096 # UDP接收缓冲区大小
|
|||
|
|
}
|
|||
|
|
外设的 UDP 端口由 CSV 配置文件中的UDP端口列指定,实现完全可配置化。
|
|||
|
|
6. 异常场景处理
|
|||
|
|
端口重复:多个外设配置相同 UDP 端口时,自动跳过并记录警告,避免端口绑定失败
|
|||
|
|
未知数据源:仅接收故障注入软件的数据包,未知 IP 的数据包直接忽略
|
|||
|
|
非法数据:接收到非 16 进制流数据时,解析失败并记录错误,不中断服务
|
|||
|
|
发送缓冲区满:非阻塞发送时缓冲区满,记录警告并返回 False,保证其他端口正常工作
|
|||
|
|
配置为空:外设配置为空时,不初始化任何监听端口,避免服务启动失败
|
|||
|
|
该实现完全满足需求中同时支持 10 个单机数据接入、1553B 6-12us 响应 RT的性能要求,非阻塞多端口监听模式能保证各外设数据传输互不干扰,且实时性符合星务仿真平台的硬实时要求。
|
|||
|
|
'''
|
|||
|
|
|
|||
|
|
# ==================== 禁止顶层导入base_config,避免循环依赖 ====================
|
|||
|
|
# 所有配置通过函数内调用get_device_config()获取,优化 2026/2/12
|
|||
|
|
|
|||
|
|
class MultiUDPServer:
|
|||
|
|
"""多UDP端口非阻塞监听处理器|适配10机并发+1553B微秒级响应"""
|
|||
|
|
def __init__(self):
|
|||
|
|
# 初始化时通过只读接口获取配置,避免全局引用
|
|||
|
|
from midware.config.base_config import get_udp_config, get_device_config
|
|||
|
|
self.udp_config = get_udp_config()
|
|||
|
|
self.device_config = get_device_config()
|
|||
|
|
|
|||
|
|
# 基础属性
|
|||
|
|
self.local_ip = self.udp_config["LOCAL_IP"] # "127.0.0.1"
|
|||
|
|
self.buffer_size = self.udp_config["BUFFER_SIZE"] # 4096
|
|||
|
|
self.remote_fault_ip = self.udp_config["FAULT_INJECT_IP"]# "127.0.0.1"
|
|||
|
|
self.remote_fault_port = self.udp_config["FAULT_INJECT_PORT"]# 8889
|
|||
|
|
|
|||
|
|
# 套接字存储:key=端口号,value=socket对象
|
|||
|
|
self.udp_sockets = {}
|
|||
|
|
self.fault_sock = None
|
|||
|
|
# 初始化套接字(基于当前配置)
|
|||
|
|
self._init_sockets()
|
|||
|
|
logger.info(f"UDP服务初始化完成|监听端口:{list(self.udp_sockets.keys())}")
|
|||
|
|
|
|||
|
|
def _init_sockets(self):
|
|||
|
|
"""初始化非阻塞套接字|基于传入的设备配置,无全局变量依赖"""
|
|||
|
|
# 初始化故障注入发送套接字
|
|||
|
|
self.fault_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|||
|
|
self.fault_sock.setblocking(False)
|
|||
|
|
self.fault_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|||
|
|
self.fault_sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1024*1024) # 扩容接收缓冲区
|
|||
|
|
self.fault_sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1024*1024) # 扩容发送缓冲区
|
|||
|
|
|
|||
|
|
if not self.device_config:
|
|||
|
|
logger.warning("UDP初始化|设备配置为空,未创建任何监听套接字")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 遍历配置创建监听套接字
|
|||
|
|
for dev_name, dev_info in self.device_config.items():
|
|||
|
|
udp_port = dev_info["udp_port"]
|
|||
|
|
if udp_port in self.udp_sockets:
|
|||
|
|
logger.warning(f"UDP初始化|端口{udp_port}重复(外设{dev_name}),跳过")
|
|||
|
|
continue
|
|||
|
|
try:
|
|||
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|||
|
|
sock.setblocking(False)
|
|||
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|||
|
|
sock.bind((self.local_ip, udp_port))
|
|||
|
|
self.udp_sockets[udp_port] = sock
|
|||
|
|
logger.debug(f"UDP初始化|外设{dev_name}|端口{udp_port}绑定成功")
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"UDP初始化|外设{dev_name}|端口{udp_port}绑定失败:{str(e)}", exc_info=True)
|
|||
|
|
|
|||
|
|
def _get_dev_by_port(self, port: int, device_config: dict):
|
|||
|
|
"""根据端口反向匹配外设|传参接收配置,不依赖全局变量"""
|
|||
|
|
for dev_name, dev_info in device_config.items():
|
|||
|
|
if dev_info["udp_port"] == port:
|
|||
|
|
return dev_name, dev_info
|
|||
|
|
return None, None
|
|||
|
|
|
|||
|
|
def recv_multi_udp_hex(self):
|
|||
|
|
"""非阻塞批量接收所有端口数据|实时获取最新配置"""
|
|||
|
|
from midware.config.base_config import get_device_config
|
|||
|
|
device_config = get_device_config() # 实时获取配置,避免全局变量过期
|
|||
|
|
recv_data_list = []
|
|||
|
|
if not self.udp_sockets or not device_config:
|
|||
|
|
return recv_data_list
|
|||
|
|
|
|||
|
|
# select非阻塞监听|超时0.000001s(1us),满足1553B 6-12us响应要求
|
|||
|
|
readable_socks, _, _ = select.select(self.udp_sockets.values(), [], [], 1e-6)
|
|||
|
|
for sock in readable_socks:
|
|||
|
|
try:
|
|||
|
|
port = sock.getsockname()[1]
|
|||
|
|
dev_name, dev_info = self._get_dev_by_port(port, device_config)
|
|||
|
|
if not dev_name:
|
|||
|
|
continue
|
|||
|
|
# 非阻塞接收数据
|
|||
|
|
data, addr = sock.recvfrom(self.buffer_size)
|
|||
|
|
if addr[0] != self.remote_fault_ip:
|
|||
|
|
logger.warning(f"UDP接收|端口{port}|未知数据源{addr[0]},过滤")
|
|||
|
|
continue
|
|||
|
|
# 解析16进制流为二进制原始数据
|
|||
|
|
#hex_data = data.decode("utf-8", errors="ignore").strip()
|
|||
|
|
#raw_data = binascii.unhexlify(hex_data) if hex_data else b""
|
|||
|
|
raw_data = data # 因为 data 本来就是字节,不需要解码、不需要转十六进制!2026/4/8
|
|||
|
|
if raw_data:
|
|||
|
|
recv_data_list.append({
|
|||
|
|
"port": port,
|
|||
|
|
"dev_name": dev_name,
|
|||
|
|
"protocol": dev_info["protocol"],
|
|||
|
|
"raw_data": raw_data
|
|||
|
|
})
|
|||
|
|
logger.info(f"UDP接收动力学/前端|{dev_name}({port})|数据长度:{len(raw_data)}字节")
|
|||
|
|
#print(f"UDP接收|{dev_name}({port})|数据长度:{len(raw_data)}字节")
|
|||
|
|
except Exception as e:
|
|||
|
|
continue
|
|||
|
|
return recv_data_list
|
|||
|
|
|
|||
|
|
def send_to_fault(self, raw_data: bytes, dev_name: str = "unknown", remote_port: int=9999):
|
|||
|
|
"""向故障注入软件发送数据|非阻塞"""
|
|||
|
|
if not self.fault_sock or not isinstance(raw_data, bytes) or len(raw_data) == 0:
|
|||
|
|
logger.warning(f"UDP发送|{dev_name}|数据为空,发送失败")
|
|||
|
|
return False
|
|||
|
|
try:
|
|||
|
|
hex_data = binascii.hexlify(raw_data).decode("utf-8")
|
|||
|
|
#self.fault_sock.sendto(hex_data.encode("utf-8"),
|
|||
|
|
# (self.remote_fault_ip, self.remote_fault_port))#原来只使用固定远程端口的操作
|
|||
|
|
#self.fault_sock.sendto(hex_data.encode("utf-8"),
|
|||
|
|
# (self.remote_fault_ip, remote_port))#现在每个外设对应一个远程端口的操作,发送ASCII数 2026/4/15
|
|||
|
|
self.fault_sock.sendto(raw_data,
|
|||
|
|
(self.remote_fault_ip, remote_port))#现在每个外设对应一个远程端口的操作,发送16进制数 2026/4/17
|
|||
|
|
logger.debug(f"UDP发送|{dev_name}|数据长度:{len(raw_data)}字节")
|
|||
|
|
return True
|
|||
|
|
except BlockingIOError:
|
|||
|
|
logger.warning(f"UDP发送|{dev_name}|缓冲区满,发送失败")
|
|||
|
|
return False
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"UDP发送|{dev_name}|异常:{str(e)}", exc_info=True)
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def reload_sockets(self, new_config: dict = None):
|
|||
|
|
"""
|
|||
|
|
重新加载UDP套接字|【关键】支持传参接收新配置,彻底解决全局变量同步问题
|
|||
|
|
:param new_config: 新的设备配置,不传则从只读接口获取
|
|||
|
|
"""
|
|||
|
|
from midware.config.base_config import get_device_config
|
|||
|
|
# 优先使用传参的新配置,无则实时获取
|
|||
|
|
self.device_config = new_config if new_config else get_device_config()
|
|||
|
|
logger.info("UDP服务|开始重新加载套接字")
|
|||
|
|
# 关闭原有所有套接字
|
|||
|
|
for port, sock in self.udp_sockets.items():
|
|||
|
|
sock.close()
|
|||
|
|
# 清空套接字字典,重新初始化
|
|||
|
|
self.udp_sockets.clear()
|
|||
|
|
self._init_sockets()
|
|||
|
|
logger.info(f"UDP服务|套接字重新加载完成|当前监听端口:{list(self.udp_sockets.keys())}")
|
|||
|
|
|
|||
|
|
def close(self):
|
|||
|
|
"""关闭所有套接字,释放资源"""
|
|||
|
|
for port, sock in self.udp_sockets.items():
|
|||
|
|
sock.close()
|
|||
|
|
if self.fault_sock:
|
|||
|
|
self.fault_sock.close()
|
|||
|
|
self.udp_sockets.clear()
|
|||
|
|
self.fault_sock = None
|
|||
|
|
logger.info("UDP服务|已关闭,所有套接字资源释放")
|
|||
|
|
|
|||
|
|
# ==================== 单例实例化|延迟实例化,由main.py在配置初始化后加载 ====================
|
|||
|
|
udp_server = None
|