# DeviceProtocol **Repository Path**: spacefus/device-protocol ## Basic Information - **Project Name**: DeviceProtocol - **Description**: 回顾练习 demo - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-23 - **Last Updated**: 2026-10-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## TCP/IP ### IP(Internet Protocol,网际协议) 是**网络层协议**,负责跨网络寻址、路由转发。 1. 核心作用:给主机分配 IP 地址,把数据包从源主机送到目标主机,只负责**投递,不保证可靠**。 2. 特点:无连接、不可靠;只管把包发出去,不确认对方收到,不重传、不排序。 3. 报文:IP 头部 + 上层数据(TCP/UDP);有 TTL(跳数,防止循环转发)。 > > 一句话:IP 负责**找到对方**。 ### TCP(Transmission Control Protocol,传输控制协议) 是**传输层协议**,跑在 IP 之上,面向连接、可靠传输协议。 1. 核心特点 - 面向连接:通信前三次握手建立连接,结束四次挥手断开; - 可靠:序列号、ACK 确认、超时重传; - 流量控制:滑动窗口,防止发送太快把接收方撑爆; - 拥塞控制:慢启动、拥塞避免等,防止网络拥堵; - 字节流:**没有消息边界!这就是粘包的根源**。 > > 一句话:TCP 负责**稳稳地把数据发给对方**。 > > 对比:UDP 同样在 IP 之上,无连接、不可靠、面向报文,不会粘包(但会丢包)。 ## 什么是 TCP 粘包 TCP 是字节流,内核的 TCP 缓冲区会做合并: 发送方连续多次 `send()`,数据在发送缓冲区攒到一定大小一起发出;或者接收方缓冲区里,多次发来的数据挨在一起,应用层一次 `recv()` 读到**多条消息混在一起**,分不清哪条是哪条,就是**粘包**。 两种典型场景: 1. 发送快、小包多 → 内核 Nagle 算法合并发送(最常见) 2. 接收方一次 recv 读取的缓冲区太大,一次性读出多条消息 > > 重点:**TCP 本身没有包的概念,只有一串连续字节,TCP 层不会帮你区分消息边界**,粘包是应用层问题,不是 bug。 > > 注意:同一个连接内部会发生沾包/拆包,不同链接之间不会。 ### 什么是丢包(TCP 丢包) 分两种:**链路丢包**、**应用层读慢导致的丢包(溢出)** 1. 网络链路丢包:网线、WiFi、路由器拥堵、信号差,IP 包在半路丢了。TCP 检测到超时 / 收到重复 ACK,会自动**超时重传**,TCP 底层会尽力修复,上层不一定感知到,代价是延迟升高。 2. 接收缓冲区溢出丢包:接收程序读数据太慢,TCP 接收缓冲区满了,发送方窗口变成 0,发送停滞;如果是 UDP 会直接丢,TCP 不会主动丢,但会阻塞; 3. 还有一种:**应用层处理逻辑错误,读到数据直接丢弃**(业务层面丢包)。 > > TCP 底层不会 “丢包不重传”,如果业务上感觉数据少了,大多是:粘包解析失败丢弃、连接断开、读取逻辑写错。 [打开C++ TCP/IP 示例](c++/tcpip) C++/tcpip ## Modbus Test Modbus 是工业领域常用的主从式(Master-Slave)串行通信协议,由施耐德退出,属于应用层协议,本身不定义接口,可以跑在RS485、RS232、ModbusTCP上。 核心特点:一问一答、主站发起请求、从站被动响应;同一总线只能有1个主站,最多247个从站。(实际受限一般不会这么多) #### RTU 报文结构 完整帧 = 从站地址 + 功能码 + 数据域 + CRC16校验(低字节在前,高字节在后) RTU 帧间隔规则:与帧之间空闲时间 ≥ 3.5 个字符时间,用来分割报文。 波特率越低,这个间隔越长。 ##### 常用功能码: 功能码 名称 含义 0x01 Read Coils 读线圈(离散输出,位,ON/OFF) 0x02 Read Discrete Inputs 读离散输入(只读位) 0x03 Read Holding Registers 读保持寄存器(最常用,16bit,可读写) 0x04 Read Input Registers 读输入寄存器(只读 16bit) 0x05 Write Single Coil 写单个线圈 0x06 Write Single Register 写单个保持寄存器 0x0F Write Multiple Coils 写多个线圈 0x10 Write Multiple Registers 写多个保持寄存器 ##### 异常响应报文 正常响应:功能码原样返回 异常响应:**功能码最高位置 1(+0x80)** + 1 字节异常码 例:请求 0x03,异常返回 `0x83 + 异常码` 常见异常码: - 01:非法功能码 - 02:非法地址 - 03:非法数据值 - 04:从站设备故障 ![Rtu](Images/ScreenShot_NModbus_2026-10-23_184941.png) #### Modbus TCP 结构 TCP 没有 3.5 字符间隔、没有 CRC 校验 报文 = MBAP 头(7 字节) + PDU(和 RTU 的【功能码 + 数据】完全一致) ![Tcp](Images/ScreenShot_Modbus_2026-10-23_190545.png) #### RTU/TCP,**PDU 结构统一**:功能码 + 数据 RTU:PDU 外面包【从站地址 + CRC】 TCP:PDU 外面包【MBAP 头】 ## CAN Controller Area Network,控制器局域网,博世 1986 年推出,属于**OSI 物理层 + 数据链路层**,用于实时控制的串行通信总线协议; ### 核心原理:非破坏性逐位仲裁 1. 总线空闲,多个节点同时开始发帧,从 ID 最高位开始逐位发送、逐位回读对比总线电平 2. 规则:发送隐性 1,但读到总线是显性 0 → 仲裁失败,立刻停止发送,转为接收方 3. 结论:ID 数值越小,优先级越高;ID 里越早出现 0,越容易赢仲裁 4. 获胜节点不受影响,继续发完整帧;输的节点等总线空闲后自动重发 #### CAN 的 5 种帧类型(CAN2.0) 1. **数据帧**:最常用,携带有效数据 2. **远程帧**:RTR=1,请求同 ID 节点发数据,**无数据段**(现在工程很少用) 3. **错误帧**:任意节点检出总线错误,立刻发送错误标志,通知全网 4. **过载帧**:接收节点来不及处理,通知发送方延迟下一帧 5. **帧间隔**:帧与帧之间的分隔位 > > CAN2.0 分 A/B: > CAN2.0A:**标准帧,11bit ID,0~0x7FF** > CAN2.0B:兼容标准帧 + **扩展帧,29bit ID** #### 数据帧报文格式(重点) ![CAN](Images/can_2026102515.png) 简单举例 报文:标准帧 ID=0x123,DLC=2,Data=0x11 0x22 含义:11bit ID=0x123,带 2 字节数据,数据内容 0x11、0x22;抓包软件一般写成 `123#1122`
![CAN](Images/CANFD.png) #### CAN2.0B 与 CANFD 对比 ![CAN2.0b_CANFD](Images/CAN2.0b_CANFD.png) #### CAN 硬件滤波的掩码逻辑(核心规则先记死) **掩码二进制位 = 1 → 对应 ID 位必须和基准 ID 完全一致才能通过** **掩码二进制位 = 0 → 对应 ID 位随便是什么,都不参与匹配,直接放行** 标准帧是 **11 位 ID**(范围 0x000 ~ 0x7FF),我们按 11 位二进制来算 举例: 我们的目标:放行 `0x100 ~ 0x10F` 这 16 个连续 ID。 第 1 步:把起止 ID 转成 11 位二进制 对齐高位,补全 11 位: - 起始 `0x100` → 二进制:`001 0000 0000` - 结束 `0x10F` → 二进制:`001 0000 1111` 第 2 步:找「固定不变的位」和「变化的位」 对比上下两行: - 高 7 位(左边 7 位):`001 0000` → 两个 ID 完全一样,是固定位 - 低 4 位(右边 4 位):`0000` → `1111` → 全部在变,是忽略位 第 3 步:按规则拼掩码 - 固定位 → 掩码对应位写 `1` - 变化位 → 掩码对应位写 `0` 得到掩码的二进制: ``` 111 1111 0000 ``` 第 4 步:二进制转十六进制 4 位二进制对应 1 位十六进制: - `111 1111` = `0x7F` - `0000` = `0x0` 合起来就是:**0x7F0** 通用计算方法(快速口算) 1. 先看你要放行的 ID 范围里,**有多少个连续 ID**,数量必须是 2 的 n 次方(2、4、8、16、32…) 2. 变化的位数 = n,低 n 位全部写 0,高位全部写 1 3. 最后和 0x7FF(11 位全 1)做与运算,得到标准帧掩码 举两个例子 1. **只放行单个 ID(精确匹配)** , 变化位数 n=0,11 位全是固定位 → 掩码全 1 → **0x7FF** 2. **放行 0x200 ~ 0x2FF(256 个 ID)** 256 = 2^8,变化位数 n=8,低 8 位写 0,高 3 位写 1 , 二进制:`111 0000 0000` → 十六进制:**0x700**