first commit

This commit is contained in:
2026-06-16 15:40:19 +08:00
commit 8ba6324244
97 changed files with 6216 additions and 0 deletions

View File

Binary file not shown.

View File

@@ -0,0 +1,191 @@
# # -*- coding: utf-8 -*-
"""
Author Tan Mingyan
@Time : 2026/2/27
"""
"""
UDP数据协议分发处理器
功能解析udp_server.recv_multi_udp_hex()返回的数据按protocol字段分发到对应处理函数
支持协议UART、SYNC、CAN、1553B、AD、OC、网络
"""
import sys
import io
import os
# 设置标准输出和标准错误的编码为UTF-8
# sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
# sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
from loguru import logger
from typing import List, Dict, Any
from pathlib import Path
# 获取当前文件的目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 获取上级目录(项目根目录)
parent_dir = os.path.dirname(current_dir)
# 将项目根目录添加到系统路径
sys.path.append(parent_dir)
sys.path.append(str(Path(__file__).parent.parent.parent))
import midware.config.base_config as base_config
#from midware.config.base_config import DEVICE_CONFIG_DICT, UDP_CONFIG
# ==================== 协议类型常量(统一管理,避免硬编码)====================
# 与你要求的协议类型严格对应
PROTOCOL_UART = "UART"
PROTOCOL_SYNC = "SYNC"
PROTOCOL_CAN = "CAN"
PROTOCOL_1553B = "1553B"
PROTOCOL_AD = "AD"
PROTOCOL_OC = "OC"
PROTOCOL_NETWORK = "网络"
# 支持的协议列表(用于合法性校验)
SUPPORTED_PROTOCOLS = [
PROTOCOL_UART,
PROTOCOL_SYNC,
PROTOCOL_CAN,
PROTOCOL_1553B,
PROTOCOL_AD,
PROTOCOL_OC,
PROTOCOL_NETWORK
]
# ==================== 各协议处理函数(预留接口,按需填充逻辑)====================
def handle_uart(data: Dict[str, Any]) -> None:
"""处理UART协议数据"""
logger.info(f"[UART处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充UART数据解析、转发、故障注入等业务逻辑
pass
def handle_sync(data: Dict[str, Any]) -> None:
"""处理SYNC同步协议数据"""
logger.info(f"[SYNC处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充SYNC同步数据解析、时序控制等业务逻辑
pass
def handle_can(data: Dict[str, Any]) -> None:
"""处理CAN协议数据"""
logger.info(f"[CAN处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充CAN帧解析、总线转发等业务逻辑
pass
def handle_1553b(data: Dict[str, Any]) -> None:
"""处理1553B协议数据"""
logger.info(f"[1553B处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充1553B总线数据解析、RT/BC交互等业务逻辑
pass
def handle_ad(data: Dict[str, Any]) -> None:
"""处理AD模拟量输入协议数据"""
logger.info(f"[AD处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充AD采样值解析、校准、越限告警等业务逻辑
pass
def handle_oc(data: Dict[str, Any]) -> None:
"""处理OC数字量输出协议数据"""
logger.info(f"[OC处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充OC通道控制、状态反馈、故障模拟等业务逻辑
pass
def handle_network(data: Dict[str, Any]) -> None:
"""处理网络协议数据"""
logger.info(f"[网络处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充网络包转发、IP/TCP解析、网络故障注入等业务逻辑
pass
def handle_unknown_protocol(data: Dict[str, Any]) -> None:
"""处理未知协议数据(容错兜底)"""
unknown_protocol = data.get('protocol', '未知')
logger.warning(
f"[未知协议] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | "
f"协议类型:{unknown_protocol} | 支持的协议列表:{SUPPORTED_PROTOCOLS}"
)
# ==================== 核心分发函数(对外暴露的唯一入口)====================
def dispatch_udp_data(recv_data: List[Dict[str, Any]]) -> None:
"""
核心分发函数遍历recv_data按protocol分发到对应处理函数
:param recv_data: udp_server.recv_multi_udp_hex()返回的原始数据列表,每条数据格式:
{"port": 端口号, "dev_name": 外设名, "protocol": 协议类型, "raw_data": 原始二进制数据}
"""
# 第一步:校验输入数据类型
if not isinstance(recv_data, list):
logger.error("分发失败输入的recv_data不是列表类型")
return
# 第二步:空数据直接返回(避免无意义日志)
if not recv_data:
return
# 第三步:构建协议-处理函数映射表(核心:新增/修改协议只需改此表)
protocol_handler_map = {
PROTOCOL_UART: handle_uart,
PROTOCOL_SYNC: handle_sync,
PROTOCOL_CAN: handle_can,
PROTOCOL_1553B: handle_1553b,
PROTOCOL_AD: handle_ad,
PROTOCOL_OC: handle_oc,
PROTOCOL_NETWORK: handle_network
}
# 第四步:遍历每条数据,按协议分发处理
for single_data in recv_data:
try:
# 容错确保protocol字段存在且为字符串
protocol = single_data.get("protocol", "").strip().upper()
port = single_data["port"]
dev_name = single_data["dev_name"]
# protocol = data["protocol"]
raw_data = single_data["raw_data"]
#根据dev_name获取内存基地址偏移地址发送缓存区大小接收缓存区大小
#dev_info = DEVICE_CONFIG_DICT.get(dev_name, {})
if dev_name not in base_config.DEVICE_CONFIG_DICT:
logger.error(f"UART外设不存在{dev_name}")
return False
# 获取外设基地址、偏移地址
base_addr = base_config.DEVICE_CONFIG_DICT[device_name]["base_addr"]
offset_addr = base_config.DEVICE_CONFIG_DICT[device_name]["offset_addr"]
print(f"sysbus WriteDoubleWord {base_addr + offset_addr} {raw_data}")
# 兼容协议名大小写(如"can"→"CAN"、"sync"→"SYNC"
if protocol in protocol_handler_map:
# 调用对应处理函数
protocol_handler_map[protocol](single_data)
else:
# 未知协议调用兜底函数
handle_unknown_protocol(single_data)
except Exception as e:
# 单条数据处理异常不影响整体,记录错误日志
logger.error(
f"[数据处理异常] 外设:{single_data.get('dev_name', '未知')} | 错误信息:{str(e)}",
exc_info=True
)
# ==================== 测试用例(验证分发逻辑是否正常)====================
def test_dispatcher():
"""模拟udp_server.recv_multi_udp_hex()返回的数据,测试分发逻辑"""
# 模拟接收数据
mock_recv_data = [
{"port": 8880, "dev_name": "Uart0", "protocol": "uart", "raw_data": b"UART_TEST_DATA"},
{"port": 8881, "dev_name": "Sync0", "protocol": "SYNC", "raw_data": b"SYNC_TEST_DATA"},
{"port": 8882, "dev_name": "Can1", "protocol": "Can", "raw_data": b"CAN_TEST_DATA"},
{"port": 8883, "dev_name": "1553B0", "protocol": "1553B", "raw_data": b"1553B_TEST_DATA"},
{"port": 8884, "dev_name": "AD0", "protocol": "ad", "raw_data": b"AD_TEST_DATA"},
{"port": 8885, "dev_name": "OC0", "protocol": "OC", "raw_data": b"OC_TEST_DATA"},
{"port": 8886, "dev_name": "Net0", "protocol": "网络", "raw_data": b"NETWORK_TEST_DATA"},
{"port": 8887, "dev_name": "Unknown0", "protocol": "SPI", "raw_data": b"UNKNOWN_TEST_DATA"},
]
# 执行分发
logger.info("开始测试协议分发逻辑...")
dispatch_udp_data(mock_recv_data)
logger.info("协议分发测试完成")
if __name__ == "__main__":
# 运行测试用例,验证分发逻辑
test_dispatcher()

View File

@@ -0,0 +1,394 @@
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.000001s1us满足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