first commit
This commit is contained in:
162
tests/udp_test_connect.py
Normal file
162
tests/udp_test_connect.py
Normal file
@@ -0,0 +1,162 @@
|
||||
'''
|
||||
功能对等客户端:模拟故障注入软件,发送/接收UDP数据
|
||||
author:Tan Mingyan
|
||||
更新日期:2025/2/26
|
||||
'''
|
||||
|
||||
|
||||
'''
|
||||
2. 功能自测步骤
|
||||
步骤 1:启动工程主程序
|
||||
运行main.py,查看日志确认:
|
||||
外设配置成功加载(10 个外设);
|
||||
UDP 服务初始化完成,所有端口正常监听;
|
||||
无套接字绑定、配置解析错误。
|
||||
步骤 2:运行 UDP 测试客户端
|
||||
运行test/udp_test_client.py,执行单端口发送和多端口并行发送,验证以下核心功能:
|
||||
发送成功:客户端打印📤 发送数据成功,无端口连接失败;
|
||||
中间层接收成功:查看工程logs/run_*.log,打印对应端口的数据接收日志,无 16 进制解析错误;
|
||||
端口隔离性:多端口并行发送时,各端口数据独立接收,无串包、丢包(日志中各外设数据互不干扰);
|
||||
校验和检测:若发送数据包含正确校验位,日志打印校验和检测通过;若修改校验位为错误值,日志打印校验和检测失败并记录到logs/error_*.log;
|
||||
回传数据接收:若在工程中调用udp_server.send_to_fault()(如协议解析后回传),客户端能打印📥 接收中间层回传数据,数据格式正确。
|
||||
3. 常见问题排查
|
||||
表格
|
||||
问题现象 排查方向
|
||||
客户端提示发送数据失败 1. 中间层主程序是否未启动;2. 目标端口是否在 CSV 配置中;3. 端口是否被占用
|
||||
中间层未接收数据 1. 客户端发送的 IP 是否为127.0.0.1;2. 16 进制流格式是否正确(无乱码);3. 查看logs/error_*.log是否有数据源过滤(非故障注入 IP)
|
||||
校验和检测失败 1. 发送数据的校验位是否与error_check.py的算法匹配;2. 数据是否包含校验位(长度是否足够)
|
||||
多端口串包 1. CSV 配置中是否有重复端口;2. 工程udp_handler.py的_get_dev_by_port是否正确映射外设
|
||||
'''
|
||||
import socket
|
||||
import binascii
|
||||
import threading
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import io
|
||||
|
||||
# 设置标准输出和标准错误的编码为UTF-8
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||
|
||||
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))
|
||||
from midware.config.base_config import UDP_CONFIG
|
||||
|
||||
class UDPTestClient:
|
||||
"""UDP测试客户端:模拟故障注入软件,发送/接收UDP数据"""
|
||||
def __init__(self):
|
||||
self.local_ip = UDP_CONFIG["LOCAL_IP"]
|
||||
self.fault_port = UDP_CONFIG["FAULT_INJECT_PORT"] # 故障注入软件接收端口(客户端监听)
|
||||
self.buffer_size = UDP_CONFIG["BUFFER_SIZE"]
|
||||
# 发送套接字(向中间层外设端口发包)
|
||||
self.send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.send_sock.setblocking(False)
|
||||
# 接收套接字(监听故障注入端口,接收中间层回传)
|
||||
self.recv_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.recv_sock.bind((self.local_ip, self.fault_port))
|
||||
self.recv_sock.setblocking(False)
|
||||
# 接收线程标识
|
||||
self.recv_running = True
|
||||
# 启动接收线程(后台接收,不阻塞发包)
|
||||
self.recv_thread = threading.Thread(target=self._recv_loop, daemon=True)
|
||||
self.recv_thread.start()
|
||||
print(f"UDP测试客户端初始化完成 | 监听回传数据:{self.local_ip}:{self.fault_port}")
|
||||
|
||||
def _recv_loop(self):
|
||||
"""后台接收线程:接收中间层回传的16进制流数据"""
|
||||
while self.recv_running:
|
||||
try:
|
||||
data, addr = self.recv_sock.recvfrom(self.buffer_size)
|
||||
hex_data = data.decode("utf-8").strip()
|
||||
raw_data = binascii.unhexlify(hex_data) if hex_data else b""
|
||||
print(f"\n📥 接收中间层回传数据 | 来源:{addr} | 16进制流:{hex_data[:64]}... | 原始数据长度:{len(raw_data)}字节")
|
||||
except BlockingIOError:
|
||||
time.sleep(0.001)
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"\n❌ 接收数据异常:{str(e)}")
|
||||
continue
|
||||
|
||||
def send_hex_data(self, dev_port, raw_data):
|
||||
"""
|
||||
向中间层指定外设端口发送16进制流数据(模拟故障注入软件发包)
|
||||
:param dev_port: 外设UDP端口(中间层监听端口)
|
||||
:param raw_data: 原始二进制数据,自动转为16进制流
|
||||
"""
|
||||
try:
|
||||
# 原始数据转16进制流(符合工程数据格式要求)
|
||||
hex_data = binascii.hexlify(raw_data).decode("utf-8")
|
||||
self.send_sock.sendto(hex_data.encode("utf-8"), (self.local_ip, dev_port))
|
||||
print(f"\n📤 发送数据成功 | 目标端口:{dev_port} | 16进制流:{hex_data[:64]}... | 原始数据长度:{len(raw_data)}字节")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"\n❌ 发送数据失败 | 目标端口:{dev_port} | 异常:{str(e)}")
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
"""关闭套接字,释放资源"""
|
||||
self.recv_running = False
|
||||
self.send_sock.close()
|
||||
self.recv_sock.close()
|
||||
print("\n✅ UDP测试客户端已关闭")
|
||||
|
||||
# 多端口并行发送测试(线程版)
|
||||
def multi_port_send(client, port_data_list):
|
||||
"""
|
||||
多端口并行发送数据,验证端口隔离性
|
||||
:param port_data_list: 列表,元素为(端口, 原始数据)
|
||||
"""
|
||||
def send_task(port, data):
|
||||
client.send_hex_data(port, data)
|
||||
# 模拟不同单机的发包间隔
|
||||
time.sleep(0.001)
|
||||
|
||||
threads = []
|
||||
for port, data in port_data_list:
|
||||
t = threading.Thread(target=send_task, args=(port, data), daemon=True)
|
||||
threads.append(t)
|
||||
t.start()
|
||||
# 等待所有线程完成
|
||||
for t in threads:
|
||||
t.join()
|
||||
print("\n===== 多端口并行发送任务完成 =====")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 初始化测试客户端
|
||||
client = UDPTestClient()
|
||||
# 测试用原始数据(可自定义,建议包含校验位,适配error_check.py的校验逻辑)
|
||||
test_data1 = b"UART_TEST_DATA_001" + b'\x9c\x47' # UART数据+CRC16校验位
|
||||
test_data2 = b"CAN_TEST_DATA_001" + b'\x1a\x2b' # CAN数据+CRC16校验位
|
||||
test_data3 = b"1553B_TEST_DATA_001" + b'\x0f\x3d' # 1553B数据+CRC16校验位
|
||||
test_data4 = b"AD_TEST_DATA_001" + b'\x5f' # AD数据+累加和校验位
|
||||
test_data5 = b"OC_TEST_DATA_001" + b'\x6a' # OC数据+累加和校验位
|
||||
|
||||
try:
|
||||
# 1. 单端口发送测试(例:向Uart0(8880)发送数据)
|
||||
print("===== 单端口发送测试 =====")
|
||||
# client.send_hex_data(8880, test_data1)
|
||||
client.send_hex_data(4000, test_data1)
|
||||
time.sleep(1)
|
||||
|
||||
# 2. 多端口并行发送测试(验证端口互不干扰)
|
||||
print("\n===== 多端口并行发送测试 =====")
|
||||
port_data = [
|
||||
(4000, test_data1), (4001, test_data2), (4002, test_data3),
|
||||
(4003, test_data4), (4004, test_data1)
|
||||
]
|
||||
multi_port_send(client, port_data)
|
||||
time.sleep(3)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n用户终止测试")
|
||||
finally:
|
||||
# 关闭客户端
|
||||
client.close()
|
||||
time.sleep(1)
|
||||
Reference in New Issue
Block a user