Vendor MsQuic with build and install support, add DeviceManager status to configurable heartbeats, and report only enabled devices. Add the local QUIC gateway, protocol coverage, real MsQuic E2E tests, process smoke tests, and updated integration documentation.
8.7 KiB
Service 模块开发指南
service/ 实现边缘端对外协议和设备抽象之间的适配。Service 负责解析请求、查找设备、转换 DTO 和返回结果,不负责创建具体设备后端。
返回项目总览。
当前结构
| 目录 | 职责 |
|---|---|
grpc/ |
入站设备控制、状态查询和兼容流式接口 |
quic_edge/ |
边缘端主动连接平台的 QUIC client、控制状态机和媒体 packetizer |
quic_edge/tests/ |
已登记到 CTest 的 QUIC 协议测试 |
两个遗留 gRPC client test 位于 grpc/src/*_client_test.cpp,当前没有通过 add_test() 登记。
gRPC 和 QUIC 的职责边界:
- 机械臂、AGV 等可靠控制继续使用 gRPC;
- 节点注册、心跳和 IP 上报使用 QUIC reliable stream;
- 实时音视频使用 QUIC DATAGRAM;
quic_edge/不是平台 Gateway,也不是浏览器服务器。
新增 gRPC Service
当前没有动态 service registry,必须完成以下全部步骤。
1. 定义 Proto
在 ../../protos/cmvr/api/ 增加或扩展:
<domain>_command.proto<domain>_service.proto
import 路径必须相对于 protos/。兼容规则见 ../../protos/README.md。
2. 实现 Service
目录约定:
service/grpc/
├── include/grpc_example_service.h
└── src/grpc_example_service.cpp
实现类继承生成的:
cmvr::api::ExampleService::Service
通过已初始化的 DeviceManager 获取抽象设备。不要在 service 中创建厂商 SDK 对象,不要绕过设备 factory。
3. 加入 service target
将实现 .cpp 加入 CMakeLists.txt 的 service library,并声明最小依赖。
4. 注册到 GrpcServerTask
还必须修改:
../task/grpc_server_task/include/grpc_server_task.h../task/grpc_server_task/src/grpc_server_task.cpp
完成:
- 增加 service owner;
- 在 start 中构造;
- 调用
builder.RegisterService(...); - 在
clearServices()中 reset。
漏掉该步骤时项目可能编译成功,但服务不会出现在 reflection 或运行时。
5. 测试
- 直接测试 service handler 或启动临时 gRPC server;
- 覆盖设备不存在、类型不匹配、设备错误和取消;
- 使用 grpcurl/reflection 验证服务全名;
- 流式 RPC 覆盖客户端断开和慢消费者;
- 在 CMake 中使用
if(BUILD_TESTING)包裹测试目标,并通过add_test()登记。
grpc_arm_client_test 和 grpc_hlc_client_test 是未登记到 CTest 的历史可执行文件,不能代表默认自动覆盖。
gRPC 实现约束
错误语义
当前历史服务存在两种风格:
- gRPC status 返回 OK,业务失败写入 Feedback header;
- 使用非 OK gRPC status 表达 transport/API 失败。
扩展已有服务时保持其兼容语义。新增服务必须在设计时明确:
- 哪些错误使用 gRPC status;
- 哪些错误使用业务 Feedback;
- 是否允许部分成功;
- deadline/cancellation 如何映射;
- 不得同时返回互相矛盾的 transport 和业务状态。
流式 RPC
- 检查
context->IsCancelled(); - 检查
Read()/Write()返回; - 使用 RAII 或 MediaSourceHub Subscription 释放 producer lease;
- 不持有设备状态锁进行网络写;
- 为 wait/read 使用有限 timeout;
- 慢客户端不能阻塞设备生产线程;
- H.264/H.265 丢帧后等待关键帧恢复;
- gRPC RGB 流在积压超过
camera_stream_max_pending_frames或帧龄超过camera_stream_max_frame_age_ms时主动丢弃旧帧,请求 IDR,并从下一个关键帧恢复。
当前仅 gRPC RGB 和麦克风流使用 MediaSourceHub;Depth/RGBD 仍直接读取设备帧。
gRPC 相机实时流默认最多保留 2 帧积压、最大允许 250 ms 帧龄。两个配置项填 0 时使用上述默认值。该策略以低延迟为目标,不保证每个视频帧都到达客户端;控制命令 仍由普通 gRPC RPC 承担。
FrameData 附带 capture_utc_ns、source_sequence、pts、dts、
source_fps、source_timestamp 和 source_frame_number。平台端可用采集时间
与接收时间的差值区分设备、网络、服务端写阻塞和客户端解码/渲染队列延迟。新增字段
保持 protobuf wire compatibility,旧客户端可以继续连接,但需要重新生成代码后才能
读取这些诊断字段。
当前安全状态
GrpcServerTask 使用同步 grpc::ServerBuilder 和 grpc::InsecureServerCredentials()。reflection 由配置控制。当前没有 gRPC TLS、认证、授权或标准 health service。
QUIC 配置中的 grpc_endpoint_tls 只是上报字段,不会启用 gRPC TLS。
扩展 QUIC Edge
关键层次:
| 层 | 主要文件 |
|---|---|
| 控制流 framing | quic_edge/src/control_framing.cpp |
| DATAGRAM 固定头和分片 | quic_edge/src/datagram_packetizer.cpp |
| 会话和状态机 | quic_edge/src/quic_edge_service.cpp |
| 传输抽象 | quic_edge/include/quic_transport.h |
| MsQuic 后端 | quic_edge/src/msquic_transport.cpp |
| 设备媒体适配 | quic_edge/src/quic_edge_device_adapter.cpp |
| DeviceManager 心跳适配 | quic_edge/src/quic_edge_device_adapter.cpp |
线协议见 ../../protos/cmvr/quic_edge/v1/README.md。
新增 Transport 后端
- 实现
QuicTransport完整接口; - 明确 callback 所在线程;
- stop/close 后不得再访问已销毁 service;
QUEUED表示 transport 接管待发送数据;WOULD_BLOCK或ERROR不得接管任何字节;- DATAGRAM batch 本地准入必须原子;
- native send 部分失败时关闭连接并清理 session;
- 添加 fake transport 故障注入测试;
- 在默认 transport factory 中显式选择后端。
新增控制消息
不能只修改 Proto,还要同步:
- envelope 构造与发送;
- 入站 dispatch;
- 合法状态和消息时序;
- message sequence 校验;
- session ID 和 heartbeat sequence 校验;
- reconnect 后状态清理;
- Java Gateway 对端;
- framing、状态机和 fake transport 测试。
当前 Edge 入站只接受:
NodeRegisterResponseNodeHeartbeatAckProtocolError
虽然 Proto 定义了 MediaSessionClose,本版本 Edge 收到它仍会判为 unexpected,不应将其描述为已实现的双向控制能力。
DeviceManager 心跳快照
QuicEdgeService 通过可注入的 DeviceSnapshotProvider 获取协议无关的纯值
快照。生产构造绑定已经初始化的 DeviceManager,fake transport 测试则注入
合成快照,因此协议状态机不需要创建硬件对象或依赖 DeviceManager 单例。
DeviceManager 的本地快照继续保留禁用设备;QUIC wire 映射层仅序列化
enabled=true 的设备。已启用但创建、初始化或启动失败的设备不会被过滤。
心跳线程只读取 Manager 维护的内存状态,不能在这里同步访问厂商 SDK、网络或
设备总线。新增设备健康探针必须实现 AbstractDevice::healthSnapshot() 的
线程安全、无阻塞 I/O 契约;未实现时上报 UNSPECIFIED,不得伪造为健康。
设备异常字符串会限长,整条消息仍受 maximum_control_frame_bytes 约束。
跨 QUIC 通道顺序
Edge 会先调用可靠流发送 session/descriptor,再调用 DATAGRAM 发送媒体,但 QUIC stream 与 DATAGRAM 没有跨通道到达顺序保证。
Gateway 必须容忍 DATAGRAM 先到,对未知 session epoch 或 codec generation 的数据有界暂存或丢弃。
QUIC 测试要求
参考 quic_edge/CMakeLists.txt 和 quic_edge_protocol_test,至少覆盖:
- 控制消息拆包、粘包和超限;
- 重复或倒退 sequence;
- 注册、ACK 超时和重连;
- DeviceManager 已启用设备过滤、类型/状态映射和 provider 失败隔离;
- Gateway 返回零心跳周期时采用本地
heartbeat_interval_ms; - session epoch 清理;
- DATAGRAM header 字节序;
- 分片边界和超大帧;
- 原子队列准入和背压;
- 丢帧、generation 和关键帧恢复;
- stop 与 callback 并发。
cmake --build build --target quic_edge_protocol_test
ctest \
--test-dir build \
-R '^quic_edge_protocol_test$' \
--output-on-failure
提交检查
- service 不创建具体硬件后端
- Proto、实现、CMake 和 GrpcServerTask 注册均已更新
- 错误语义与已有服务兼容
- deadline、cancel、Read/Write 失败均处理
- 流式资源通过 RAII 释放
- gRPC 安全能力没有被配置字段误描述
- QUIC 状态机与 Gateway 同步更新
- 协议测试已登记到 CTest