目录
上行路由的建立(为了让节点给DODAG Root发送消息也即拓扑创建)
下行路由建立(为了DODAG Root给节点发送消息也即路由表创建)
一、RPL拓扑建立
RPL的核心思想是构建DAG拓扑,DODAG的构建基于邻居发现ND过程
-
主要可以分成两个部分拓扑创建+填充路由表
-
上行路由的建立(为了让节点给DODAG Root发送消息也即拓扑创建)
- DODAG根节点会首先广播DIO消息,(消息中包含DODAG ID、Rank值、所选择的OF等信息。)收到DIO消息的节点会按照以下流程图运行,加入拓扑的节点向DODAG Root发送包含自己路由信息的DAO控制消息,从而构建上行路由。
- DODAG根节点会首先广播DIO消息,(消息中包含DODAG ID、Rank值、所选择的OF等信息。)收到DIO消息的节点会按照以下流程图运行,加入拓扑的节点向DODAG Root发送包含自己路由信息的DAO控制消息,从而构建上行路由。
-
-
-
- 上行路由的建立需要考虑的因素
- 上行路由的建立需要考虑的因素
-
-
-
-
- 候选的邻居节点集合必须是Link-Local多播能到达的节点的子集。
- 父节点的集合需要是候选邻居节点集合满足一定限制条件的子集。
- 最优父节点集合是在上行路由中,考虑父节点集合中最佳下一跳节点的集合。通常来说,最优父节点是一个单独的父节点,不过若有些父节点都具有相同的优先级以及相同的Rank值,它们也可以作为一组最优父节点
-
-
例子
- 上层为节点分布图
- 第一个图
- 为最开始的时候,节点0和节点1为根节点,节点2和节点3在节点0的通信范围内,节点3和节点4在节点1的通信范围内。
- 第二个图
- 根节点开始向周围的节点发送DIO。
- 节点2收到节点0的DIO,同时节点3收到节点0和节点1的DIO,
- 节点4收到节点1的DIO。显然节点2和节点4分别选择了节点0和节点1作为自己的父节点,因为它们只收到了一个DIO。
- 节点3通过OF计算选择了节点0作为自己的父节点。
- 根节点开始向周围的节点发送DIO。
- 第三个图
- 此时RPL Instance的状态就到了下层第三个图,节点2、节点3和节点4修改路由代价及Rank值信息后再向自己的周围节点转发DIO,
- 节点2给节点5和节点6发送了DIO
- 最后节点5将节点2当作父节点
- 节点3和节点4都给节点6发送了DIO。,
- 节点6通过目标函数选择节点4作为父节点,到此时整个拓扑结构都建立起来了。
- 节点2给节点5和节点6发送了DIO
- 当节点5要向节点0发送数据时,会将数据默认发给其父节点2,然后通过节点2转发给节点0。但是此时如果节点0要给节点5发送数据,节点0还不知道发送的路径,因为此时向下路由还没有建立。
- 此时RPL Instance的状态就到了下层第三个图,节点2、节点3和节点4修改路由代价及Rank值信息后再向自己的周围节点转发DIO,
- 第一个图
-
-
下行路由建立(为了DODAG Root给节点发送消息也即路由表创建)
- 加入DODAG的节点会定时地向父节点发送包含自身前缀信息的DAO消息,父节点收到DAO消息之后,缓存子节点的前缀信息,在路由表中加上相应的路由信息,并回应DAO-ACK消息。这样在进行通信时,通过前缀匹配就可以将数据包发送到目的节点
- 针对下行路由的建立,RPL定义了两种模式
- 存储模式(Storing Mode)
意思是对于普通父节点,存储路由表
-
-
-
-
- 一个节点收到DIO消息选择好父节点后,会向父节点发送DAO。DAO消息中包含了通过该节点可以到达的地址或者地址前缀信息。当父节点收到DAO后会处理DAO消息中的地址前缀,然后在路由表中加入相应的路由项。当父节点做完这些后就会向它的父节点发送DAO数据包。如此重复,直到整个向下的路由建立起来。
- 非存储模式(Non-Storing Mode)
-
-
-
意思是对于普通父节点,不存储路由表
-
-
-
-
- 一个节点收到DIO消息后不是向父节点发送DAO,而是向DODAG根节点发送 DAO。当然必须要通过父节点转发,当根节点收到所有节点发送过来的DAO消息后,就会建立到所有节点的路由表。当根节点要向下面的节点发送数据包时,根节点根据路由表构建源路由。
-
-
-
二、仿真文件的制作
- 进入instantcontiki3.0
- cd contiki/tools/cooja
- ant run
- 点击motes
- 点击sky mote
- 文件目录examples/ipv6/rpl-udp/下
- 选择文件 udp-server.c 编译,并点击create节点
- 再一次点击motes,同样步骤选择文件为udp-client.c 编译 并创建create节点
- simulation start
- 注意:也可以直接打开rpl-udp.csc文件,进行仿真
三、仿真结果的分析
network:
udp-server.c分析
- 功能
- RPL-UDP Server负责建立UDP服务器端的连接,程序的主要功能是RPL网络的建立、UDP服务器端的建立和监听并接收客户端发送的信息。
- 流程图
- 程序流程
- 初始化RPL DAG
-
uip_ds6_addr_add(&ipaddr, 0, ADDR_MANUAL); root_if = uip_ds6_addr_lookup(&ipaddr); if(root_if != NULL) { rpl_dag_t *dag; dag = rpl_set_root(RPL_DEFAULT_INSTANCE,(uip_ip6addr_t *)&ipaddr); uip_ip6addr(&ipaddr, UIP_DS6_DEFAULT_PREFIX, 0, 0, 0, 0, 0, 0, 0); rpl_set_prefix(dag, &ipaddr, 64); PRINTF("created a new RPL dag\n"); } else { PRINTF("failed to create a new RPL DAG\n"); } print_local_addresses(); /* 数据接收器以 100% 的占空比运行,以确保高数据包接收率。*/ NETSTACK_MAC.off(1);
static void print_local_addresses(void) { int i; uint8_t state; PRINTF("Server IPv6 addresses: "); for(i = 0; i < UIP_DS6_ADDR_NB; i++) { state = uip_ds6_if.addr_list[i].state; if(state == ADDR_TENTATIVE || state == ADDR_PREFERRED) { PRINT6ADDR(&uip_ds6_if.addr_list[i].ipaddr); PRINTF("\n"); /* hack to make address "final" */ if (state == ADDR_TENTATIVE) { uip_ds6_if.addr_list[i].state = ADDR_PREFERRED; } } } }
-
- 建立UDP连接
-
server_conn = udp_new(NULL, UIP_HTONS(UDP_CLIENT_PORT), NULL); if(server_conn == NULL) { PRINTF("No UDP connection available, exiting the process!\n"); PROCESS_EXIT(); } udp_bind(server_conn, UIP_HTONS(UDP_SERVER_PORT)); PRINTF("Created a server connection with remote address "); PRINT6ADDR(&server_conn->ripaddr); PRINTF(" local/remote port %u/%u\n", UIP_HTONS(server_conn->lport), UIP_HTONS(server_conn->rport));
-
- 等待事件的到来,如果是tcpip事件就调用tcpip_handle函数
-
while(1) { PROCESS_YIELD(); if(ev == tcpip_event) { tcpip_handler(); } else if (ev == sensors_event && data == &button_sensor) { PRINTF("Initiaing global repair\n"); rpl_repair_root(RPL_DEFAULT_INSTANCE); } }
-
- tcpip_handler函数处理到来的tcpip事件,打印输出接收到的信息,并向客户端返还发送的数据信息
-
static void tcpip_handler(void) { char *appdata; if(uip_newdata()) { appdata = (char *)uip_appdata; appdata[uip_datalen()] = 0; PRINTF("DATA recv '%s' from ", appdata); PRINTF("%d", UIP_IP_BUF->srcipaddr.u8[sizeof(UIP_IP_BUF->srcipaddr.u8) - 1]); PRINTF("\n"); #if SERVER_REPLY PRINTF("DATA sending reply\n"); uip_ipaddr_copy(&server_conn->ripaddr, &UIP_IP_BUF->srcipaddr); uip_udp_packet_send(server_conn, "Reply", sizeof("Reply")); uip_create_unspecified(&server_conn->ripaddr); #endif } }
-
- 初始化RPL DAG
- 程序流程
udp-client.c分析
- 流程图
- 程序流程
- 建立UDP连接
/* new connection with remote host */ client_conn = udp_new(NULL, UIP_HTONS(UDP_SERVER_PORT), NULL); if(client_conn == NULL) { PRINTF("No UDP connection available, exiting the process!\n"); PROCESS_EXIT(); } udp_bind(client_conn, UIP_HTONS(UDP_CLIENT_PORT)); PRINTF("Created a connection with the server "); PRINT6ADDR(&client_conn->ripaddr); PRINTF(" local/remote port %u/%u\n", UIP_HTONS(client_conn->lport), UIP_HTONS(client_conn->rport));
- 等待事件的到来
- 如果是tcpip_event事件,
- 通过调用tcpip_event函数处理
- 如果是定时器超时事件
if(etimer_expired(&periodic)) { etimer_reset(&periodic); ctimer_set(&backoff_timer, SEND_TIME, send_packet, NULL);
- 则通过send_packet函数向服务器端发送数据,
static void send_packet(void *ptr) { char buf[MAX_PAYLOAD_LEN]; #ifdef SERVER_REPLY uint8_t num_used = 0; uip_ds6_nbr_t *nbr; nbr = nbr_table_head(ds6_neighbors); while(nbr != NULL) { nbr = nbr_table_next(ds6_neighbors, nbr); num_used++; } if(seq_id > 0) { ANNOTATE("#A r=%d/%d,color=%s,n=%d %d\n", reply, seq_id, reply == seq_id ? "GREEN" : "RED", uip_ds6_route_num_routes(), num_used); } #endif /* SERVER_REPLY */ seq_id++; PRINTF("DATA send to %d 'Hello %d'\n", server_ipaddr.u8[sizeof(server_ipaddr.u8) - 1], seq_id); sprintf(buf, "Hello %d from the client", seq_id); uip_udp_packet_sendto(client_conn, buf, strlen(buf), &server_ipaddr, UIP_HTONS(UDP_SERVER_PORT)); }
- 则通过send_packet函数向服务器端发送数据,
- 如果是tcpip_event事件,
- 建立UDP连接
- motes output分析
ID:2即client节点
00:00.508 ID:2Rime 从地址 0.18.116.2.0.2.2.2 开始
00:00.517 ID:2MAC 00:12:74:02:00:02:02:02 Contiki-2.6-2450-geaa8760 已启动。节点 ID 设置为 2。
00:00.526 ID:2nullsec CSMA nullrdc,通道检查速率 128 Hz,无线电通道 26,CCA 阈值 -45
00:00.537 ID:2 暂定链路本地 IPv6 地址 fe80:0000:0000:0000:0212:7402:0002:0202
00:00.539 ID:2Starting 'UDP 客户端进程'
00:00.543 ID:2UDP 客户端进程启动 nbr:10 路由:10
00:00.547 ID:2Client IPv6 地址:fd00::212:7402:2:202
00:00.550 ID:2fe80::212:7402:2:202
00:00.555 ID:2与服务器建立连接::本地/远程端口8765/5678
ID:1即server节点 与client节点建立udp连接
00:00.654 ID:1Rime 从地址 0.18.116.1.0.1.1.1 开始
00:00.663 ID:1MAC 00:12:74:01:00:01:01:01 Contiki-2.6-2450-geaa8760 已启动。节点 ID 设置为 1。
00:00.672 ID:1nullsec CSMA nullrdc,信道检查率 128 Hz,无线电信道 26,CCA 阈值 -45
00:00.683 ID:1 暂定链路本地 IPv6 地址 fe80:0000:0000:0000:0212:7401:0001:0101
00:00.685 ID:1 开始 'UDP 服务器进程'
00:00.688 ID:1UDP 服务器已启动。编号:10 路线:10
00:00.692 ID:1创建了一个新的 RPL dag
00:00.696 ID:1 服务器 IPv6 地址:fd00::212:7401:1:101
00:00.698 ID:1fd00::ff:fe00:1
00:00.701 ID:1fe80::212:7401:1:101
00:00.707 ID:1使用远程地址::本地/远程端口5678/8765创建服务器连接
发送数据包
00:01.527 ID:2上层校验和 len: 6 from: 40
00:01.543 ID:1uip6:从 fe80::212:7402:2:202 到 ff02::1a 收到 IPv6 数据包
00:01.546 ID:1icmp6_input:长度 46 类型:155
00:01.548 ID:1上层校验和 len: 6 from: 40
00:01.550 ID:1 在 uip 中丢弃数据包
附录:关于RPL专业术语
- RPL是一个距离向量路由协议,其设计高度模块化
- 为了使RPL在LLN应用领域有广泛的应用,RPL将数据处理和数据转发从路由优化目标中分离出来。RPL支持很多不同形式的链路层,包括但不限于有损链路和用在主机或路由设备中资源有限的链路层。
- RPL的核心思想是构建DAG拓扑。DAG拓扑是一个类似于树状拓扑的结构,用它来规定LLN节点之间的默认路由。但与典型树状拓扑中节点只允许有一个父节点的情况不同,DAG结构中节点可以与多个父节点连接。具体来说,一个DAG由一个或多个RPL实例组成,一个RPL实例中又包含一个或多个DODAG。每个DODAG有唯一的DODAG ID。DODAG ID的范围是一个RPL Instance。一个RPL Instance ID和一个DODAG ID唯一地确定了一个DODAG。在DODAG中,目的地节点往往是DAG的根节点,根节点通过骨干网连接到路由器,再由路由器连接到传统的有线网络中。在同一个网络中可以同时运行多个RPL实例,这些RPL实例的操作在逻辑上互相独立。
- RPL的基本术语
- DAG (有向无环图(Directed Acyclic Graph))
- 概念
- DAG是对RPL形成的拓扑的一种描述。拓扑中所有的路径为边,这些边构成了一个有向无环图,即这些边不会形成环路,有效防止路由环路问题,
- DAG Root (DAG 根节点)
- 各个边汇聚于一点,这个点被称作DAG Root,即Sink节点或网关。DAG Root有构建DAG的能力,同时担任着连接LLN和Internet的网关(或边界路由器)角色。DAG的根节点通过广播路由限制条件来过滤网络中一些不满足条件的节点,然后节点通过路由度量来选择最优的路径,(从而消除传统路由协议可能存在的瓶颈问题,解决由网络路由所带来的巨大开销、所引发的网络生存周期缩短等技术难题。)
- DODAG 面向目的地的DAG(Destination-Oriented Directed Acyclic Graph)
- 概念
- DODAG是RPL拓扑的基本单元,一个DAG可以由一个或多个DODAG组成
- DODAG Root (DODAG根节点)
- 一个DODAG有且只有一个根节点,称作 DODAG Root,通常为Sink节点或处理能力比较强的普通节点。DODAG Root最后全部汇聚于目的地节点,即DAG Root。DODAG Root 能起到边界路由器的作用,有构建DODAG的能力,并负责本地DODAG的路由聚集和信息收集。特别地,在需要的情况下,DODAG Root可以更新DODAG拓扑。
- 概念
- 概念
- 向上路由
- 是节点沿着DODAG(或DAG)边朝向根节点方向的路由。
- 向下路由
- 向下路由是节点沿着DODAG(或DAG)边与向上路由方向相反的路由
- 最佳父节点
- 指DODAG拓扑中节点向上路由的默认下一跳节点。最佳父节点的选择规则由目标函数(Object Function,OF)定义
- 等级(Rank)
- 可以看作RPL路由协议的路由“梯度”,表征单个节点相对DODAG根节点的距离,其本质是将节点到DODAG根节点的距离以量化的形式表示出来,Rank值由OF计算得到,可以用来避免路由回环和进行路由回环检测。为了保证环路探测机制,沿着节点到DODAG根节点的方向,节点的Rank值减小,DODAG根节点的Rank值为0,父节点的Rank值大于子节点的Rank值
- 目标函数(Object Function,OF)
- 功能
- 定义了路由度量、路由优化目标和Rank值的计算方式
- 定义了DODAG中选取最佳父节点的方式
- 定义了DODAG的构建方式
- RPL旨在使用OF功能使节点到达DODAG根节点的路径成本最小化。OF可以根据网络应用场景的不同来灵活定义。
- 功能
- RPL实例(RPL Instance)
- 一个DAG可以包含多个RPL实例,同一个RPL实例由一个或多个DODAG组成。一个节点可以属于多个RPL实例,并根据DODAG特点标记数据流量,但在所属的每个RPL实例中只能属于其中一个DODAG
- 一个节点可以有多个目标函数OF,同一个DODAG使用相同的OF,也就是说即使在同一个网络中,也可以根据不同的区域或不同的要求,定义不同的OF来满足目标
- RPL实例号(RPL Instance ID)
- 由DAG Root设定。RPL实例由RPL实例号唯一表征
- DODAG号(DODAG ID)
- DODAG ID由DODAG Root设定。DODAG由RPL实例号和DODAG号唯一表征。
- DODAG版本号(DODAG Version Number)
- 由DODAG Root设定。节点通常会因为自身的原因(如节点电池耗尽)或者环境原因而失去作用,结果导致DODAG的拓扑结构发生变化,因此路由协议必须维护DODAG的拓扑。RPL路由协议通过DODAG版本号来定义不同DODAG的拓扑版本,当DODAG因为某种原因重新建立而变成另外一个拓扑版本时,DODAG版本号都会加1。一个DODAG版本号由RPL实例号、DODAG号和DODAG版本号共同唯一表征
- DAG (有向无环图(Directed Acyclic Graph))