GPSR
概述GPSR 是一种经典的无线自组织网络或中使用的无状态地理路由协议。它的核心思想非常简单:利用节点的物理位置信息来转发数据包,而不是维护传统的、基于地址的路由表。 无状态:路由器(节点)不需要维护到所有可能目的地的路由表。这大大减少了协议的开销和内存占用,特别适合资源受限的网络(如传感器网络)。 地理路由:每个节点都知道自己的地理位置(例如通过内置 GPS 接收器或其它定位算法),并且假设数据包的最终目的地位置是已知的。节点在转发数据包时,只依赖邻居节点的位置信息和目的地的位置来做决策。 核心工作原理GPSR 主要使用两种数据包转发模式,并根据网络情况在它们之间智能切换: 贪婪转发 - 默认模式,在节点密集区域非常高效。 周边转发 - 恢复模式,当贪婪转发失败时(遇到“空洞”时)使用。 贪婪转发这是 GPSR 的首选和主要模式。 基本思想每个中间节点都试图将数据包转发给地理上最接近最终目的地的邻居节点。 工作流程 当节点 S 有一个要发往目的地 D 的数据包时,它首先会检查自己的邻居表。 邻居表是通过周期性的信标广播建立的。每个节点都会广播自己的位置,听到广播的节点就知...
AODV
AODV 路由过程描述AODV(Ad-hoc 按需距离矢量路由协议)是专为移动自组织网络(MANET)设计的按需式路由协议,仅在源节点需要向目的节点发送数据时才触发路由发现,通过“路由请求(RREQ)-路由回复(RREP)”握手建立路径,无数据传输时几乎无控制开销。 阶段 1:路由发现(源节点触发 RREQ 广播,无预存路由时)路由发现触发条件当源节点 S 有数据要发送给 D,但自身路由表中无“D 的有效路由条目”(或路由已过期)时,触发路由发现流程。 RREQ 消息结构包含核心字段: RREQ ID(源节点 S 分配的唯一标识,避免重复处理); 源节点地址(S)、目的节点地址(D); 源节点当前跳数(初始为 0)、目的节点序列号(初始为 0,用于判断路由新鲜度)。 RREQ 广播与路由表临时更新 每个中间节点(如 A、B、C)接收 RREQ 后,首先检查“RREQ ID+ 源地址”是否已处理(避免循环广播):若未处理,更新自身路由表的“临时前驱条目”——记录“目的 D 的跳数=收到的跳数 +1,前驱节点=发送 RREQ 的节点”(如 A 收到 S 的 R...
OLSR
OLSR 路由过程描述阶段 1:邻居发现与对称链路建立(HELLO 消息交互)HELLO 消息发送所有节点周期性(默认 1 秒)广播 HELLO 消息,消息包含自身 IP 地址、当前一跳邻居列表、转发意愿(Willingness,如 WILL_ALWAYS/WILL_NEVER)。 HELLO 只在一跳范围内传输。 对称链路验证节点接收邻居的 HELLO 消息后,若发现自身地址在对方的“一跳邻居列表”中,判定为对称链路(可双向通信);若仅单向接收 HELLO(如 A 能收到 C 的消息,但 C 收不到 A 的),则视为无效链路,不纳入后续计算。 所有链路必须双向验证才被认为是可用的。可以双向交互的链路是对称链路,可以双向交互的两个节点是对称节点。 邻居侦听的过程如下图所示,在初始化阶段,当节点 A 收到一个来自于邻居节点 B 的 HELLO 分组,A 将 B 放入自己的邻居集中,并将到 B 的链路标记为非对称状态,然后,在 A 向 B 发送 HELLO 分组时,在 HELLO 分组中就包含有 B 是 A 的非对称状态的邻居节点的信息,当 B 收到该 HELLO 分组...
判断浮点数大小
判断相等在 C 语言中,不能直接用 == 来判断两个浮点数是否相等,浮点数在内存中是以二进制形式存储的,存在精度误差。 例如: #include <stdio.h> int main() { float a = 0.1; float b = 0.2; float c = 0.3; if (a + b == c) { printf("相等\n"); } else { printf("不相等\n"); } return 0; } 输出是不相等,因为 0.1 和 0.2 在二进制中是无限循环小数,相加后和 0.3 有微小差别。 正确的方法:比较差值是否小于一个很小的数我们通常定义一个极小的误差范围(epsilon),当两个数的差的绝对值小于这个范围时,就认为它们相等。 #include <stdio.h> #include <math.h> // 需要包含 math.h 才能使用 fabs #...
GPIO 配置指南
GPIO 配置的核心要素不管是哪种总线或功能,GPIO 配置都围绕以下几个关键点: 要素说明 引脚号确定使用哪个端口的哪个引脚(如 PA5、PB12) 模式(Mode)输入 / 输出 / 复用功能 / 模拟 输出类型(Type)推挽(Push-Pull)/ 开漏(Open-Drain) 上拉/下拉(Pull)上拉、下拉、浮空 速度(Speed)低速、中速、高速、超高速(影响驱动能力和功耗) 复用功能(Alternate Function)当用作外设引脚时,需要指定对应的复用功能编号 通用配置步骤步骤 1:使能 GPIO 端口时钟在配置任何引脚前,必须先开启对应 GPIO 端口的时钟(以 ARM Cortex-M 为例): __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟 步骤 2:选择引脚和模式 普通输入:读取外部电平(按键、传感器等) 普通输出:控制 LED、继电器等 复用功能:作为 UART、SPI、I²C 等外设引脚 模拟模式:用于 ADC 输入、DAC 输出 步骤 3:设置输出类型(仅输出模式有效) 推挽输...
TCP 状态转移解析
TCP(传输控制协议)是一种面向连接、可靠的传输层协议,其状态转移机制是实现“三次握手”建立连接、“四次挥手”关闭连接及数据传输过程中异常处理的核心。理解 TCP 状态转移,需先明确 11 种核心状态,再围绕“连接建立-数据传输-连接关闭”三大阶段梳理状态变迁逻辑。 一、TCP 核心状态定义首先明确每种状态的含义,这是理解状态转移的基础: 状态名称(英文)状态名称(中文)核心含义 CLOSED关闭状态TCP连接的初始/最终状态,无任何连接活动。 LISTEN监听状态服务器端处于“等待客户端连接请求”的状态(如调用`listen()`后)。 SYN_SENT同步已发送客户端发送连接请求(SYN报文)后,等待服务器确认的状态。 SYN_RCVD同步已接收服务器收到客户端SYN报文,回复SYN+ACK报文后,等待客户端最终确认的状态。 ESTABLISHED已建立连接客户端与服务器完成“三次握手”,连接正式建立,可进行数据传输的状态(核心工作状态)。 FIN_WAIT_1终止等待1主动关闭方(客户端/服务器均可)发送关闭请求(FIN报文)后,等待对方确认的状态。 FI...
Git
以下是 Git 常用操作的整理 一、基础配置(首次使用必做)# 配置用户名和邮箱(关联远程仓库提交记录) git config --global user.name "Your Name" git config --global user.email "your.email@example.com" # 查看配置信息 git config --list SSH绑定 SSH 密钥到 Git 仓库(如 GitHub、GitLab 等)可以实现免密码拉取和推送代码,且使用 SSH 更加稳定,操作步骤如下: 一、检查是否已有 SSH 密钥首先查看本地是否已存在 SSH 密钥(避免重复创建): # 进入 SSH 密钥目录 cd ~/.ssh # 列出目录下的文件 ls 若看到 id_rsa(私钥)和 id_rsa.pub(公钥),说明已有密钥,可直接跳到步骤三。 二、生成新的 SSH 密钥若没有密钥,执行以下命令生成(一路回车,无需设置密码,除非需要额外安全验证): # 替换为你的 Git 绑定邮箱 ssh-keygen -t ed25519 -C "your_email@exam...
指针赋值分析
现象描述链表中常用指针进行操作: ListNode *slow= head; ListNode *pre = NULL; pre = slow; slow = slow->next; 此时 pre 指向的仍然是 slow 第一次指向的地址,即 head 指针赋值给指针指针变量存储的是内存地址,当执行 pre = slow 时,是将 slow 当时指向的内存地址复制给了 pre。这意味着: pre 和 slow 一开始指向同一个内存地址(同一个节点) 当执行 slow = slow->next 时,只是改变了 slow 存储的地址(让它指向了下一个节点) 但 pre 中存储的地址没有被改变,仍然是 slow 原来指向的那个节点的地址 从地址和值的角度分析head: 地址0x100 节点1: 地址0x200 节点2: 地址0x300 节点3: 地址0x400 ... 执行 pre = slow 之前假设此时: slow 存储的地址是 0x100(指向 vhead) pre 尚未赋值(或存储其他地址) 执行 pre = slow 时 这是地址的复制:把 slow...
寄存器操作
位操作清零//假设为第三位 int val &= ~(1<<3); //假设为第三、四位 int val &= ~(3<<3); 置一int val |=(1<<3); 切换int val ^= (1<<3);// 异或操作:0变1,1变0 获取值int val = readl(GPIO1_DR); val = (val>>3)&0x01; 对寄存器读写数据在修改寄存器数据时,如果是对位进行操作(其他位用于其他功能),则必须要进行先读后修改 否则会影响其他位 #include <linux/io.h> void writel(unsigned char data , unsigned int addr) unsigned char readl(unsigned int addr) //e.g. int val = readl(GPIO1_DR); val |= (1<<3); writel(val,GPIO1_DR); 内存映射从芯片手册得到的是物理地址,需要内存映射为虚...
堆
一、堆是完全二叉树的结构1.只允许最后一行不满 2.最后一行必须从左往右排,中间不能有间隔 二、堆序性1.小根堆,父节点都要更小 2.大根堆,父节点都要更大 三、堆的存储因为是完全二叉树,所以可以根据层序遍历,来得到一个数组,此时,父节点为 i 时,左右子节点一定为 2i+1/2 四、堆有两个基本操作 上滤,通常用于插入新元素到根中时,向上调整位置时 下滤(因为必须要满足堆序性的话,所以对不满足的要操作),把根节点向下调整的操作叫下滤 五、自顶向下建堆法 插入堆 上滤 六、自下而上建堆法对每个父节点进行下滤(从最下面的父节点开始)– 复杂度 O(N) 七、应用 优先队列:弹出最小元素 – 可以用来实现堆排序,用大根堆排序完,弹出的是正序,小根堆反序 插入:就是上滤
