编辑 | blame | 历史 | 原始文档

CAN 模拟器独立 TCP 服务进程 — 设计规格

日期:2026-11-09
状态:已与用户逐节确认
关联代码基线:V3.0 混合架构(Local + DeviceBroker),进程内 CanSimulator

1. 问题与目标

现状问题:CanDriverAdapter 用进程内 static 字典 _sharedBuses 管理总线实例,
主程序(CAN-001/002)与 DeviceBroker 宿主(CAN-003/004)各自持有一套同名但隔离的
CAN_BUS_1 实例,跨进程不可见,只能靠"同宿主对端设备"变通验证收发。

目标:将 CAN 模拟器改造为独立 TCP 服务进程(总线权威),模仿物理 CAN 分析仪行为,
实现跨进程多通道并发测试;同时把 CAN 驱动收敛为**唯一类** CanDeviceDriver,
Local/Broker 差异下沉为驱动内部传输后端,自动化程序只面向 IDeviceDriver 编程。

2. 已确认的关键决策

决策点 结论
协议取向 命令串直传(SEND:/RECV_ALL/AUTO_RESPONSE:ON 等现有语义原样传输)
分帧方式 4 字节大端长度 + UTF8 文本(与 BrokerIpc 同惯用法)
服务端形态 独立 WPF 进程 CanSimulatorServer.exe(带 UI)
地址来源 全局 App.config(CanSimulator.Host / CanSimulator.Port)
模式取舍 全面切 TCP,驱动删除进程内总线分支,无回退开关
服务端实现 复用现有 CanBusSimulator/CanNode 路由与自动响应逻辑,零重写
会话模型 方案 A:请求-响应(保序)+ 服务端主动推送 EV:RECV(双工)
驱动收敛 唯一 CAN 驱动类 CanDeviceDriver,内部 ICanTransport 双实现
面板改造 抽 CanChannelView + CanDebugPanel 壳化为 TabControl 多通道
启动方式 模拟器服务纯手动启动,主程序与 Broker 均不拉起

3. 总体架构

┌─────────────────────────  CanSimulatorServer.exe(新建,WPF) ─────────────────────────┐
│ TcpListener(默认 127.0.0.1:9000)                                                       │
│  ├─ TcpAcceptor:每连接一个 ConnectionSession                                          │
│  ├─ CanBusManager:懒创建 CanBusSimulator;(bus,nodeIndex) 唯一性仲裁;                 │
│  │                 节点 FrameReceived → 会话推送 的胶水                                 │
│  └─ UI:节点树 | 实时帧表 | 总线启停/统计 | 日志                                        │
└────────────────────────────────────────────────────────────────────────────────────────┘
       ▲ TCP(NodeChannelTransport)              ▲ TCP
       │                                          │
┌──────┴──────────────┐                ┌──────────┴──────────────┐
│ 主程序               │   命名管道RPC  │ DeviceBroker.exe         │
│ CAN-001/002          │ ◄────────────► │ CAN-003/004              │
│ CanDeviceDriver      │  BrokerRpc     │ CanDeviceDriver          │
│ (NodeChannelTransport)│ Transport     │ (NodeChannelTransport)   │
└──────────────────────┘                └─────────────────────────┘
  • 全系统只有一份"节点通道驱动"代码:Broker 模式下宿主进程内的物理会话同样是
    CanDeviceDriver(NodeChannelTransport)。
  • 仲裁链路完全不动:BrokerServer 三重关卡(白名单→configHash→租约栅栏)、
    SessionGate、维护租约、只读豁免均保留在 BrokerRpcTransport 委托路径上。
  • 自动化程序经 DeviceManager.GetDriver(deviceId) 拿到的永远是 CanDeviceDriver,
    ExecutionMode 在驱动构造时读一次,上层零感知。

4. TCP 线协议

分帧:4 字节大端长度 + UTF8 文本行;文本按冒号分段。

4.1 客户端 → 服务端

消息 说明
HELLO:{bus}:{nodeIndex}:{clientTag} 握手并挂载节点;确认后节点激活、开始接收总线帧
SEND:{frameId}:{data} 发送帧(现行命令串透发)
QUERY:{命令串} RECV / RECV_ALL / STATUS / AUTO_RESPONSE:ON / FILTER:id:mask / LOG 等透发
DISCONNECT 注销本连接节点

4.2 服务端 → 客户端

消息 说明
OK:{payload} / ERR:{code}:{message} 对上一条请求的响应(恰好一行,保序)
EV:RECV:{frameId}:{data}:{HH:mm:ss.fff}:{srcNodeIndex} 总线路由推送的接收帧(主动推送,无请求关联)
EV:STATE:{connected\|lost} 对端上下线通知,MVP 仅定义格式不发送

4.3 节点身份模型

一个总线节点 = (busName, nodeIndex),服务端以 nodeIndex 为总线上唯一寻址
(与真实 CAN 节点地址语义一致)。同一 nodeIndex 被其他连接占用时握手返回
ERR:NODE_BUSY 拒绝挂载;连接断开自动释放。两个设备配置同 (bus,node) 属配置错误,
靠错误文案引导修改端点,不做自动避让。

4.4 错误与边界

  • 未知命令 / 格式非法 → ERR:UNKNOWN_CMD / ERR:BAD_FRAME,不断连接
  • HELLO 前发其它命令 → ERR:NOT_HANDSHAKED
  • 服务端不可达 → ConnectAsync 抛连接异常,文案含"请先启动 CAN 模拟器服务 {Host}:{Port}"

5. 驱动层:统一 CanDeviceDriver

5.1 类型划分(Aging.Infrastructure)

IDeviceDriver(现有契约,不变)
    ▲
CanDeviceDriver          全系统唯一 CAN 驱动类(由 CanDriverAdapter 更名重构)
 ├─ 命令串解析、_recvBuffer、RECV/RECV_ALL 组装、DriverFrameReceived 事件、
 │  STATUS/统计、连接状态机、Dispose
 └─ ICanTransport _transport(构造时按设备 ExecutionMode 选择)

ICanTransport(内部接口)
 ├─ ConnectAsync / TransceiveAsync(cmd)→响应行 / Dispose
 ├─ event FramePushed(EV:RECV 行到达;NodeChannel 为 TCP 直推,
 │                       BrokerRpc 为随宿主响应捎带到达,见 §5.4)
 ├─ event TransportBroken
 │
 ├─ NodeChannelTransport   TCP 直连服务端;读循环分流响应/推送;
 │                         断线指数退避重连(0.5s 起、上限 5s)、重连自动重发 HELLO
 └─ BrokerRpcTransport     现 BrokeredCanDriver 的 RPC 逻辑原样迁入
                           (Hello 握手 / SubmitTask / ResultBody 态→异常映射不变:
                            RejectedStaleLease→LEASE_STALE、ConfigMismatch、
                            Unknown→TimeoutException 禁止假定未执行)

5.2 注册与分派

  • DriverFactory.CreateDriver("CAN", …) 统一返回 CanDeviceDriver;工厂读 data.db 该设备
    ExecutionMode/BrokerId 选择传输后端。
  • InfrastructureBootstrapper.RegisterBrokeredDrivers 取消——Broker 设备与 Local 设备
    走同一条 LoadDevicesFromConfig 注册路径。
  • 删除:BrokeredCanDriver 类(逻辑入 BrokerRpcTransport)、CanDriverAdapter._sharedBuses
    进程内分支。

5.3 上层兼容

  • DeviceManager.GetDriver 契约、DeviceService/CanBusService/AutoDeviceFacade 自动化链、
    DeviceDebug 的 ExistingDriver 复用逻辑均不改。
  • 按 GetType().Name 区分驱动的日志/断言改为按传输后端类型区分。
  • 面板 DrainBrokerMailbox(RECV_ALL 1s 轮询) 降级为兜底(重连窗口期补显示),
    帧来源标注 BUS@tcp / BUS@broker。

5.4 Broker 捎带推送

宿主内驱动的 NodeChannelTransport 把 EV:RECV 追加到 pendingPushes 列表(有界 1000,
满丢最老);BrokerServer 响应 QUERY/SEND 时在 readback 末尾追加 |PUSHED:n + 推送行;
BrokerRpcTransport 解析后回调 FramePushed 并从宿主列表移除(一次性消费,不丢不重)。
空闲无请求时推送滞留宿主,下一次任意请求捎带——Broker 路径实时性仍为请求驱动,
与现状一致,面板 1s RECV_ALL 兜底保证最终显示。

6. 服务端工程 CanSimulatorServer

  • 新目录 CanSimulatorServer\,SDK 风格 WPF(net472;UseWPF=true),
    引用 CanSimulator 类库 + log4net,**不引用 Aging.Infrastructure**;
    加入 WPFProject\TapProject.sln。
  • App.config:ServerPort(默认 9000)、ListenOnAllInterfaces(默认 false)。
  • 结构:TcpAcceptor / CanBusManager / ConnectionSession(每连接 MPSC 有界发送队列
    1000 帧,满丢最老+计数)/ MainWindow(节点树、环形 2000 行帧表 TX/RX 分色、
    每总线启停与清空、统计、日志;Dispatcher 100ms 批量刷新)。
  • 服务端**必须引用 CanSimulator 类库**(CanBusSimulator/CanNode/CanFrame 即路由引擎,
    复用其路由、自动响应、滤波器、总线日志,零重写)。
  • 帧去重:TCP 模式下帧进入客户端驱动仅经推送事件一条路,驱动不重复入节点邮箱,
    避免历史上 buffer+邮箱双报问题重现。

7. 调试面板多通道

CanDebugPanel(类名/入口不变,壳化为 TabControl)
 ├─ 工具条:[+ 添加通道] [关闭当前通道];Tab 头 "CAN-001 @CAN_BUS_1:0"
 └─ TabControl → CanChannelView × N(新 UserControl,承载现单通道全部 UI)
  • CanChannelView:现有面板四区(连接控制/发送/接收记录/状态统计)整体抽出,
    x:Name 控件归 View 内部,每 Tab 独立控件树。
  • 通道两入口:
  1. DeviceDebug 选设备 → 打开/聚焦该设备通道页(重复选择只聚焦不重建),
    注入 CanDeviceDriver 后总线/节点输入只读(设备表权威);
  2. 手动添加通道 → 小对话框(总线/节点编号/后端 Local-TCP),自建驱动连接。
  • 每 View 独立 100ms PollTimer;维护模式按钮语义分两类:注入驱动→委托
    DebugSessionManager 租约状态机(Token 缓存复用等现有逻辑不动);
    自建通道→维持模拟器本地直连语义(与自动化无关,不引入仲裁旁路)。
  • 主程序 Tab3"CAN 模拟器"宿主容器改用同一壳类,MainWindow 无需大改。

8. 配置、启动与错误处理

键 位置 默认
CanSimulator.Host 主程序 + DeviceBroker App.config 127.0.0.1
CanSimulator.Port 同上 9000
ServerPort / ListenOnAllInterfaces 服务端 App.config 9000 / false

UseTcpMode 取消;Endpoint 格式 CAN_BUS_1:0 不变;data.db 零迁移。
启动顺序:用户手起服务 → 主程序(照常拉起 DeviceBroker)→ CAN 按需连接;
主程序退出只关自己的连接,服务常驻。

故障 行为
服务不可达(连接时) 抛连接异常含启动提示文案
中途断线 IsConnected=false → 指数退避重连 → HELLO 重发;期间发送快速失败 NOT_CONNECTED;ConnectionChanged 上抛
在途请求撞断线 TimeoutException,语义等同 Unknown(INV-06),禁止自动重试 SEND
节点被占 ERR:NODE_BUSY,提示修改设备端点
推送队列满 丢最老+UI 计数;客户端 1s RECV_ALL 兜底补账
Broker 管道断/宿主死 沿用现异常;宿主内 TCP 重连对客户端透明

9. 测试与验收

  • 存量改造:依赖进程内总线的用例(BrokerE2ETests CAN 收发断言、CanBusService 相关)
    改为测试 fixture 在测试进程内 new CanBusManager + TcpListener(随机端口)
    托管服务核心(无 UI 复用层),与被测驱动走真实 TCP 回环,无需人工起服务。
  • 协议单测:SimulatorFraming 粘包/半包/超长拒绝;HELLO 状态机;NODE_BUSY 仲裁。
  • 驱动单测:重连后 HELLO 重发;在途超时语义;EV:RECV→事件链路。
  • 手动验收清单:
  1. 启动 CanSimulatorServer.exe;
  2. 主程序 CAN-001/002 经 TCP 挂载;
  3. DeviceBroker 启动,CAN-003/004 经宿主 RPC 中转走 TCP;
  4. 调试面板同屏打开 CAN-001 与 CAN-003 两个通道页;
  5. CAN-001 发帧 → CAN-003 收到;
  6. CAN-003 发帧 → CAN-001 收到;
  7. 双通道并发收发互不阻塞。

10. 构建约束与已知风险

  • 构建须用 VS 完整版 MSBuild(解决方案 WPFProject\TapProject.sln),
    命令行不加 /p:Platform=x64(老式工程 XAML 编译链路限制)。
  • 风险 1:服务端 TCP 核心与 UI 的引用边界——已定:服务端必须引用 CanSimulator 类库,
    TcpAcceptor/CanBusManager 放服务端工程内,CanSimulator 保持无 TCP 依赖的纯净类库。
  • 风险 2:全面切 TCP 后服务未启动时 Local CAN 设备完全不可用——已定,
    以清晰错误文案与测试 fixture 自托管缓解。
  • 风险 3:Broker 路径实时性仍为请求驱动(捎带模型)——与现状一致,验收标准不因此提高。