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

网络通信工程:UDP → TCP → CAN/SocketCAN → ZMQ → Protobuf


1、UDP


1、窗口重排是什么?

假设收到:

复制代码
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
+
不同Payload

3、队列水位又是什么?

例如:

复制代码
队列容量 = 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 搞混。

TCP:

复制代码
IP + Port

CAN:

复制代码
CAN ID

CAN 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 为什么叫 SocketCAN?

Linux 不需要你自己重新设计一套:

复制代码
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

ID:201

DLC:8

Data:
11 22 33 44 55 66 77 88

6. cansend 是干什么的?

和 candump 对应:

复制代码
cansend can0 201#1122334455667788

意思就是:

复制代码
通过 can0
发送 ID = 0x201
Data = 11 22 33 44 55 66 77 88

所以最基本的 CAN 调试习惯:

复制代码
candump
    ↓
看设备有没有发数据

cansend
    ↓
主动给设备发送数据

7. CAN 和 EtherCAT 不要混淆

这非常容易混。

你可以先粗略理解:

CAN

复制代码
CAN
 ↓
共享总线
 ↓
一帧一帧发送

EtherCAT

复制代码
EtherCAT
 ↓
工业实时以太网
 ↓
Master
 ↓
多个Slave

EtherCAT更强调:

复制代码
实时
同步
周期通信
大量设备
高效率

所以机器人多关节控制里,经常看到:

复制代码
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 Logger

MotionInput 发布:

复制代码
HumanPose

多个模块都可以订阅。

复制代码
Publisher
    ↓
 ┌──┼──┐
 ↓  ↓  ↓
S1 S2 S3

一个发布者:

复制代码
PUB

多个订阅者:

复制代码
SUB

典型用途:

复制代码
机器人状态广播
传感器数据广播
日志
监控

PUB 做什么?

PUB:

发布消息。

例如:

复制代码
HumanPose
Frame 10001
Frame 10002
Frame 10003

SUB 做什么?

SUB:

订阅自己感兴趣的消息。

例如:

复制代码
SUB
 ↓
HumanPose

或者按照 topic/filter:

复制代码
mocap/
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/PULL

4. 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
     ↓
MuJoCo

5. ZMQ 的几个重要对象

Context

可以理解成:

复制代码
ZMQ运行环境

一个程序通常先创建 Context。


Socket

不是简单等同于 TCP socket。

ZMQ Socket 是:

一种特定通信模式的消息端点。

例如:

复制代码
PUB socket
SUB socket
PUSH socket
PULL socket
REQ socket
REP socket

Message

ZMQ 面向的是:

复制代码
Message

而不是让你直接处理 TCP 字节流。


6. HWM 是什么?

HWM:

High 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
                ↓
             MuJoCo

5、 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 是什么?

你写:

复制代码
id="g3oz3k"
human_pose.proto

机器并不能直接拿这个 .proto 文件通信。

需要:

复制代码
.proto
   ↓
protoc
   ↓
生成代码

例如 C++:

复制代码
human_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
 ↓
HumanPose

6. 现在把四个技术彻底串起来

这是你这一部分最重要的一张图:

复制代码
                 应用数据
                    │
                    ↓
               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 不能用于机器人,而是根据场景选择。

相关推荐
Zelman1 小时前
协议栈数据流全景分析
网络协议·tcp/ip·https
北京中科新远科技1 小时前
AI网卡五层检查法:协议、PCIe、NUMA、端口与验收
服务器·网络·人工智能
天赐范式1 小时前
天赐范式第181天:让耦合开始失稳——竞争耦合与线性失稳边界
python·数字生命·天赐范式·lyapunov指数·动态运行时·耦合失稳·laplacian矩阵
happylifetree2 小时前
Python15:核心语法-数据存储与运算-字符串拼接
python
All for pursuit.2 小时前
【二分查找-2】34.在排序数组中查找元素的第一个和最后一个位置
数据结构·c++·算法·leetcode
数字供应链安全产品选型2 小时前
智能体与代码安全一体化选型指南:跳出功能清单,以运行时硬指标衡量真实防护能力
网络·人工智能
data analyse 4562 小时前
埋点工具的私有化部署成本高吗?
前端·数据分析·github
大侠归来2 小时前
C语言与C++在基础语法上的区别
java·c语言·c++
huisheng_qaq2 小时前
【Python基础篇-09】深入理解python的闭包与装饰器
python·闭包