ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

简历技术栈全面复习——UDP / TCP / CAN / ZMQ / Protobuf 通信工程

简历技术栈全面复习——UDP / TCP / CAN / ZMQ / Protobuf 通信工程 网络通信工程UDP → TCP → CAN/SocketCAN → ZMQ → Protobuf1、UDP1、窗口重排是什么假设收到100 101 103 102 104不能收到103就马上输出。因为102可能只是晚了一点。所以程序可以建立重排窗口例如期待102 当前收到 103 104 等待102等102来了102 103 104再依次输出。但是不能无限等这是实时系统里的关键。假设期待102但是102永远丢了如果一直等系统卡死所以必须有超时于是收到103 ↓ 等待102 ↓ 超过窗口/超时 ↓ 判断102缺失 ↓ 继续处理103这就是实时性和完整性的权衡。UDP ↓ 收到数据 ↓ 解析帧号 ↓ 进入有界队列 ↓ 重排窗口 ↓ 判断缺帧 ↓ 去重 ↓ 合并 ↓ 输出2、CRC32在这里干什么Payload ↓ CRC32 ↓ 一个校验值例如Payload A ↓ CRC 123456如果另一份数据Payload B ↓ CRC 789012说明内容不同。因此可以快速发现同一个Frame ID 不同Payload3、队列水位又是什么例如队列容量 256当前队列里有180个那么watermark 180如果长期200 220 240 250 255说明生产速度明显快于消费速度。这时候即使网络没问题系统也快撑不住了。所以网络监控 队列监控需要一起看。2、 TCP从“能通信”到“工程上可靠”1. 应用层定义“消息边界”这是 TCP 工程最核心的知识之一。常见方法方法 A固定长度例如每条消息固定 64 bytes那么每收到 64 bytes ↓ 一条完整消息适合固定结构的数据。方法 B特殊结束符例如HELLO\n WORLD\n看到\n就知道一条消息结束。适合文本协议。方法 C长度 Payload机器人项目里非常常见┌────────────┬──────────────────┐ │ length │ payload │ │ 4 bytes │ N bytes │ └────────────┴──────────────────┘例如[100][Protobuf数据100 bytes]接收端先收 4 bytes ↓ 知道 payload 100 bytes ↓ 继续接收 100 bytes ↓ 得到完整消息 ↓ Protobuf deserialize这就和你之前学的TCP ↓ Protobuf ↓ 反序列化真正连起来了。2. TCP 心跳进一步可以设计Client ───── heartbeat ─────→ Server Client ←──── heartbeat ack ── Server例如PING ↓ PONG如果连续很多次PING ↓ 没有 PONG ↓ timeout ↓ 认为连接异常所以TCP连接状态 应用层心跳可以共同判断通信状态。3. 自动重连真正的机器人程序通常不会只写connect();而是connect ↓ 成功 ─────→ communication │ 失败 ↓ retry ↓ retry ↓ retry断线communication ↓ disconnect ↓ cleanup ↓ reconnect ↓ connected这里又出现了一个你之前 C 学过的东西生命周期管理TCP connection 本身就可以看成一个生命周期Disconnected ↓ Connecting ↓ Connected ↓ Error ↓ Disconnected这和你后面 EtherCAT、ROS2 lifecycle、设备驱动状态管理会产生非常强的联系。3、CAN从 CAN 总线到 Linux SocketCAN你先建立一个整体认识CAN 总线 ↓ CAN Frame ↓ CAN ID Data ↓ 设备之间通过 ID 区分消息 ↓ Linux SocketCAN ↓ candump / cansend ↓ C/C 程序 ↓ 电机 / 驱动器 / 传感器1. CAN 到底是什么CAN 可以简单理解成多个设备共用一条通信总线大家都可以发送消息。例如┌── 电机1 │ CAN Bus ├── 电机2 │ ├── 编码器 │ └── 控制器不像 TCP电脑 ─────────→ 设备CAN 更像┌── Device A │ CAN ────┼── Device B │ └── Device C大家挂在同一条总线上。CAN有一个非常重要的特点多个设备可以同时尝试发送。比如设备A → ID 0x100 设备B → ID 0x200 设备C → ID 0x300如果同时发送怎么办CAN通过ID仲裁解决。核心规律先记ID越小优先级越高。所以0x100 0x200 0x300竞争时0x100 ↓ 优先发送2. CAN ID 是干什么的这里非常容易和 TCP/IP 搞混。TCPIP PortCANCAN IDCAN ID 可以帮助设备判断这是什么消息例如0x201 → 电机1状态 0x202 → 电机2状态 0x301 → 电机1控制具体 ID 含义由具体协议决定。所以CAN本身 ↓ 只提供CAN Frame 上层协议 ↓ 规定0x201代表什么 规定Data每个Byte代表什么这就是CAN ≠ 电机协议。3. DLC 是什么你简历里如果看到DLC它表示Data Length Code简单理解就是这条 CAN Frame 里面有多少数据。例如ID: 0x201 DLC: 8 Data: 11 22 33 44 55 66 77 88表示8 bytes注意DLC 是 CAN 帧层面的数据长度描述不等于某个具体应用协议的数据含义。4. Linux 为什么叫 SocketCANLinux 不需要你自己重新设计一套CAN驱动 CAN接收 CAN发送Linux 内核提供了SocketCAN它把 CAN 接口统一成 Linux 网络编程风格的接口。所以你可以把它理解成CAN硬件 ↓ Linux CAN Driver ↓ SocketCAN ↓ Linux Socket API ↓ 你的 C/C 程序你在 Linux 里看到can0这时候不要认为“can0就是CAN协议。”不是。可以理解成CAN硬件 ↓ Linux CAN驱动 ↓ SocketCAN ↓ Linux Socket API ↓ 你的C/C程序所以程序可以通过类似 Socket 的方式操作 CAN。例如你以前看到candump can0就是从 Linux 的can0接口监听 CAN 数据。而cansend can0 123#11223344就是往 CAN 总线上发送一帧数据。5. candump 是干什么的在 Linux 上调试 CAN 时非常常用。例如candump can0意思可以简单理解成监听 can0 上收到的 CAN Frame。如果总线上有数据你可能看到类似can0 201 [8] 11 22 33 44 55 66 77 88你可以把它理解成接口can0 ID201 DLC8 Data 11 22 33 44 55 66 77 886. cansend 是干什么的和candump对应cansend can0 201#1122334455667788意思就是通过 can0 发送 ID 0x201 Data 11 22 33 44 55 66 77 88所以最基本的 CAN 调试习惯candump ↓ 看设备有没有发数据 cansend ↓ 主动给设备发送数据7. CAN 和 EtherCAT 不要混淆这非常容易混。你可以先粗略理解CANCAN ↓ 共享总线 ↓ 一帧一帧发送EtherCATEtherCAT ↓ 工业实时以太网 ↓ Master ↓ 多个SlaveEtherCAT更强调实时 同步 周期通信 大量设备 高效率所以机器人多关节控制里经常看到EtherCAT你现在可以这样建立关系CAN │ ├── CAN Frame ├── ID ├── DLC └── Data而EtherCAT │ ├── Master ├── Slave ├── PDO ├── SDO ├── Distributed Clocks └── Process Data它们都是工业通信技术但体系结构不同。再往上CAN ↓ CANopen ↓ CiA402 ↓ 电机控制而你的另一条技术链EtherCAT ↓ IgH Master ↓ PDO / SDO ↓ CiA402 ↓ CSP ↓ 电机控制所以CiA402 可以出现在不同底层通信技术之上不要把“CAN”和“CiA402”当成同一个层级的东西。4、 ZMQ从 Socket 到消息通信先建立一张图TCP / UDP ↓ 原始通信能力 ↓ ZMQ ↓ 消息模式 队列 连接管理 ↓ 应用程序所以ZMQ 不是替代 TCP/UDP 的另一种物理网络协议。它更像是把底层网络通信封装成更好用的消息通信模型。你可以先记住一句ZMQ 的核心不是“怎么发 TCP 数据”而是“程序之间采用什么消息通信关系”。例如发布者 ↓ 多个订阅者或者任务生产者 ↓ 任务队列 ↓ 多个消费者或者Client ↓ Server ↓ Response这些就是 ZMQ 的通信模式。1. PUB / SUB这是机器人项目里非常容易遇到的一种。┌── Subscriber A │ Publisher ───┼── Subscriber B │ └── Subscriber C例如你的动捕系统MotionInput ↓ Publisher ↓ ┌───┼────┐ ↓ ↓ ↓ GMR MuJoCo LoggerMotionInput 发布HumanPose多个模块都可以订阅。Publisher ↓ ┌──┼──┐ ↓ ↓ ↓ S1 S2 S3一个发布者PUB多个订阅者SUB典型用途机器人状态广播 传感器数据广播 日志 监控PUB 做什么PUB发布消息。例如HumanPose Frame 10001 Frame 10002 Frame 10003SUB 做什么SUB订阅自己感兴趣的消息。例如SUB ↓ HumanPose或者按照 topic/filtermocap/ robot/ diagnostics/发布者通常不知道具体有多少订阅者。所以它很适合一个数据源 ↓ 多个数据消费者例如Motion Capture ↓ ZMQ PUB ↙ ↓ ↘ GMR MuJoCo Logger这就是非常典型的解耦。MotionInput 不需要知道到底谁在使用我的数据它只负责发布数据2. PUSH / PULL这个模式和 PUB/SUB 不一样。它更像任务流水线。Producer ↓ PUSH ↓ Queue ↓ PULL ↓ Worker例如Frame Processing ↓ PUSH ↓ ┌────┼────┐ ↓ ↓ ↓ Worker1 Worker2 Worker3适合任务处理 数据处理 并行计算PUSH ↓ ┌────────┐ ↓ ↓ PULL PULL更像任务分发。例如任务生产者 ↓ PUSH ↓ 任务队列 ↓ ┌──┴──┐ ↓ ↓ Worker1 Worker2适合任务处理 计算任务 工作线程3. PUB/SUB 和 PUSH/PULL 不要混简单记PUB/SUB 广播 PUSH/PULL 分发任务例如一个人体姿态 ↓ GMR MuJoCo Logger更像PUB/SUB而1000个待处理Frame ↓ 多个Worker分担更像PUSH/PULL4. REQ / REP这是比较接近传统Client → Server Server → Client例如Client │ │ request ↓ Server │ │ response ↓ Client可以用于查询 控制命令 配置 RPC风格接口例如Client: “读取当前机器人状态” ↓ Server: “当前状态 ENABLED”Client REQ ↓ REP Server典型请求 ↓ 处理 ↓ 响应例如获取机器人状态 ↓ 返回状态生产者 Xsens / Noitom ↓ UDP ↓ MotionInput ↓ Queue ↓ 消费者 GMR ↓ MocapFrame ↓ ZMQ ↓ MuJoCo5. ZMQ 的几个重要对象Context可以理解成ZMQ运行环境一个程序通常先创建 Context。Socket不是简单等同于 TCP socket。ZMQ Socket 是一种特定通信模式的消息端点。例如PUB socket SUB socket PUSH socket PULL socket REQ socket REP socketMessageZMQ 面向的是Message而不是让你直接处理 TCP 字节流。6. HWM 是什么HWMHigh Water Mark简单理解给队列设置一个上限。例如Queue │ ├── msg ├── msg ├── msg ├── ... └── HWM达到限制以后ZMQ 会根据具体 socket 类型和配置采取相应行为。你真正应该记住的是无限队列 ↓ 可能无限积压 ↓ 延迟越来越大而实时机器人系统通常更关心当前最新数据而不是几秒钟以前还没处理完的数据这和你之前学习的queue_capacity 256 queue watermark是同一个工程思想。7. ZMQ 和 Protobuf这两个东西解决的是不同问题。ZMQ ↓ 负责“怎么把消息送过去” Protobuf ↓ 负责“消息里面的数据长什么样”例如MotionInput ↓ Protobuf Serialize ↓ HumanPose binary message ↓ ZMQ ↓ 网络/进程间传输 ↓ ZMQ Receive ↓ Protobuf Deserialize ↓ HumanPose所以ZMQ ≠ 数据格式。Protobuf ≠ 网络传输框架。两者组合起来非常自然。Protobuf ┌────────────────┐ │ 数据怎么表示 │ └───────┬────────┘ ↓ GMR ───────→ MocapFrame ↓ Serialize ↓ ZMQ ↓ Network ↓ ZMQ ↓ Parse ↓ MocapFrame ↓ MuJoCo5、 Protobuf定义“消息里面装什么”先记住一句话Protobuf 主要解决“数据怎么定义、怎么序列化和反序列化”而不是负责网络传输。所以UDP / TCP / ZMQ ↓ 负责“把数据送过去” ↓ Protobuf ↓ 负责“数据长什么样”1..proto是什么你可以把.proto理解成通信数据结构的“接口定义文件”。例如message HumanPose { uint64 timestamp 1; uint64 frame_id 2; repeated float joint_positions 3; }这句话是在定义HumanPose │ ├── timestamp ├── frame_id └── joint_positions注意 1 2 3不是数组下标。它们是 Protobuf 的field number字段编号。2. message 是什么message就相当于定义一个数据结构。比如message MotorState { int32 motor_id 1; float position 2; float velocity 3; }逻辑上就是MotorState │ ├── motor_id ├── position └── velocity在 C 中protoc会根据这个定义生成对应代码。于是你的业务代码不需要自己手工处理第1个字节是什么 第2个字节是什么 ……3. protoc 是什么你写idg3oz3k human_pose.proto机器并不能直接拿这个.proto文件通信。需要.proto ↓ protoc ↓ 生成代码例如 Chuman_pose.proto ↓ protoc ↓ human_pose.pb.h human_pose.pb.cc你的 C 程序再使用这些生成代码。所以.proto ↓ 协议定义 protoc ↓ 代码生成 .pb.h / .pb.cc ↓ C使用4. Serialize 是什么假设你的程序里面已经有HumanPose对象timestamp 1000 frame_id 123 joint_positions [...]现在准备通过网络发送。需要对象 ↓ Serialize ↓ 二进制 bytes这就是序列化。5. Deserialize 是什么接收端网络 bytes ↓ Deserialize ↓ HumanPose这就是反序列化。所以完整流程发送端 HumanPose ↓ Serialize ↓ bytes ↓ ZMQ / TCP / UDP ↓ 网络接收端网络 ↓ bytes ↓ Deserialize ↓ HumanPose6. 现在把四个技术彻底串起来这是你这一部分最重要的一张图应用数据 │ ↓ Protobuf │ Serialize │ ↓ bytes │ ┌─────────┼─────────┐ ↓ ↓ ↓ UDP TCP ZMQ │ │ │ ↓ ↓ ↓ 网络传输 字节流 消息通信 │ │ │ └─────────┼─────────┘ ↓ 接收 bytes ↓ Protobuf ↓ Deserialize ↓ 应用数据7. Protobuf 和 JSON 的区别你还需要知道一个实际工程区别。JSON{ frame_id: 123, position: 1.2 }优点人容易看懂 调试方便缺点数据体积通常更大 解析开销通常更高Protobuf结构化二进制数据更适合高频 大量数据 跨进程 跨语言 机器人实时数据链路所以Web API → JSON 很常见 机器人高频数据 → Protobuf 很常见不是说 JSON 不能用于机器人而是根据场景选择。
返回列表