first commit
This commit is contained in:
0
midware/network/__init__.py
Normal file
0
midware/network/__init__.py
Normal file
BIN
midware/network/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
midware/network/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/network/__pycache__/protocol_dispatcher.cpython-311.pyc
Normal file
BIN
midware/network/__pycache__/protocol_dispatcher.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/network/__pycache__/udp_handler.cpython-311.pyc
Normal file
BIN
midware/network/__pycache__/udp_handler.cpython-311.pyc
Normal file
Binary file not shown.
191
midware/network/protocol_dispatcher.py
Normal file
191
midware/network/protocol_dispatcher.py
Normal 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()
|
||||
394
midware/network/udp_handler.py
Normal file
394
midware/network/udp_handler.py
Normal 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.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
|
||||
Reference in New Issue
Block a user