Contiki协议栈Rime:引子introduction

2024-04-08 04:32

本文主要是介绍Contiki协议栈Rime:引子introduction,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

更多的Contiki协议栈知识,请参考索引目录:
《Contiki协议栈:索引目录》

1. 前言

思来想去,既然是程序员,当然还是用一个程序引入比较好。当然,这个程序必须满足以下几点:

  • 足够简单,不会把大家给吓着了
  • 能够引入足够多的知识点,可以串起来
  • 能够说明包如何在网络中传输

然后我就找啊找,找到了Contiki的一个demo例程:examples/rime/example-abc.c

2. 匿名广播例程

#include "contiki.h"
#include "net/rime/rime.h"
#include "random.h"#include "dev/button-sensor.h"#include "dev/leds.h"#include <stdio.h>
/*---------------------------------------------------------------------------*/
PROCESS(example_abc_process, "ABC example");
AUTOSTART_PROCESSES(&example_abc_process);
/*---------------------------------------------------------------------------*/
static void
abc_recv(struct abc_conn *c)
{printf("abc message received '%s'\n", (char *)packetbuf_dataptr());
}
static const struct abc_callbacks abc_call = {abc_recv};
static struct abc_conn abc;
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(example_abc_process, ev, data)
{static struct etimer et;PROCESS_EXITHANDLER(abc_close(&abc);)PROCESS_BEGIN();abc_open(&abc, 128, &abc_call);while(1) {/* Delay 2-4 seconds */etimer_set(&et, CLOCK_SECOND * 2 + random_rand() % (CLOCK_SECOND * 2));PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));packetbuf_copyfrom("Hello", 6);abc_send(&abc);printf("abc message sent\n");}PROCESS_END();
}

  这个例程估计是Contiki中最简单的、能够在节点间传输数据包的例程了。
  abc的全称是Anonymous Broad Cast,即匿名广播。所谓广播,是指该节点发出的广播消息,在无线覆盖范围内的其它节点都需要接收。所谓匿名,是指发出的消息中没有携带发送节点的地址。
  匿名广播是Rime协议栈中最底层的子协议,其它所有协议都直接或间接地建立在该协议之上。Rime协议的框架如下图所示。

这里写图片描述

图 Rime协议栈架构

  咋一看!天,这么多,全是协议吗?我要学到什么时候?我的回答是:对,这些全是协议栈中的子协议,但是不要慌,每一个子协议都非常简单。比如abc协议,代码就 六七十行,能复杂到哪儿去?。我们后面会慢慢讲述这些协议。

3. 基本分析

  先讲讲我们的应用程序如何实现发送、接收匿名广播的。
  整个例程的关键代码就三行:

abc_open(&abc, 128, &abc_call); // 打开一个abc连接
packetbuf_copyfrom("Hello", 6); // 将要发送的消息拷贝到buffer
abc_send(&abc);   // 将buffer中的消息发送出去

  整个过程是不是与传统网络编程类似呢?
  abc_open,创建一个abc连接,类似于创建一个socket
  packetbuf_copyfrom,将消息放到buffer
  abc_send,将消息发送出去,类似于socket编程中的send

  对了,socket编程中有一个receive函数,用于接收对方发送过来消息,在这个例程中为啥没有呢?其实是用的,注意看abc_open(&abc, 128, &abc_call)的第三个参数,它是一个结构体,结构体里面的成员是一个函数指针。当本节点接收到其它节点发送的匿名广播消息时,会自动调用这个函数指针所指向的函数进行接收数据!我们只用将接收函数写好,完全不用考虑在何时需要接收,这真是既高级、又简单啊!

  socket中的关闭套接字,对应本例程中的什么呢?答案是PROCESS_EXITHANDLER(abc_close(&abc);)。但是要注意,这里的abc_close与socket中的close稍有不同,它是放在一个宏PROCESS_EXITHANDLER里面的,且该语句放在线程的最前面,而不是放在最后面。

  简单地说,当abc进程接收到一个退出事件时,会执行函数abc_close(&abc)关闭打开的abc连接。

  简单总结一下,该例程首先打开一个匿名广播连接,然后将要发送的消息拷贝到发送buffer里面,然后发送该消息。此外,程序里加了一个事件定时器,每次进入while循环时先延迟2~4秒,再发送广播消息。如果节点接收到其它节点发送的abc消息,则自动调用回调函数abc_recv()接收消息并做相应的处理。

  在文章最后,我们会在cooja仿真器中运行该例程,看看运行结果。

4. 深入分析

  俗话说,麻雀虽小五脏俱全,我们的abc例程就是一个小麻雀。我们需要深入跟踪代码了,很兴奋有木有!

PS: 下面很多术语,会在今后的笔记中慢慢体现,现在只是为了让大家知道今后要学些什么,在脑袋里有个框架,所以不明白也没关系

4.1 struct abc_conn

  先看一个abc连接static struct abc_conn abc;的具体定义:

//相关代码位于core/net/rime/abc.[ch]
struct abc_conn {struct channel channel;  // 通道const struct abc_callbacks *u; // 回调函数
};

  struct abc_conn有两个成员变量,一个struct channel类型的channel,一个const struct abc_callbacks类型的指针u,它们分别啥意思呢?那就得先看看abc_open(&abc, 128, &abc_call)了。

4.2 abc_open

void abc_open(struct abc_conn *c, uint16_t channelno,const struct abc_callbacks *callbacks)
{channel_open(&c->channel, channelno); //打开一个channelc->u = callbacks; // 将回调结构体赋值给abc连接的结构体中的回调结构体指针channel_set_attributes(channelno, attributes); //设置通道属性
}

  例程中的abc_open(&abc, 128, &abc_call);一共有三个参数:

  abc: 是一个struct abc_conn类型的abc连接
  128: 表示通道的通道号
  abc_call: 接收到消息的回调函数

  至此,我们有两个新东西:

  • 通道(通道号)
  • 属性

4.3 通道

  Rime协议栈所有通信都是通过通道channel标识的,即两个应用进程通信需要相同的channel。类似于socket编程,两个进程通信具有相同的端口号。

  struct channel

//相关代码位于core/net/rime/channel.[ch]
struct channel {struct channel *next;  // 指向下一个通道结构体uint16_t channelno; // 通道号const struct packetbuf_attrlist *attrlist; //属性链表uint8_t hdrsize; // 头部尺寸
};

 next:一看就知道用于链表。所有的通道会放在一个通道链表之中
 channelno:通道号,16位无符号整数。类似于socket的端口号,标识一个通道
 attrlist:属性链表。存放属性
 hdrsize:发送、接收数据的缓冲包的头部大小
 
 至此,我们又多了一个新的概念:

  • 缓冲包packetbuf

4.4 包属性

  Rime协议的一个与众不同之处就在于属性。其它的协议,都会定义协议头,在打包的时候安装协议规定组装协议头,在解析包的时候也安装协议规定拆分协议头。而Rime协议不定义任何头部格式,它定义了很多包属性,因此可以兼容各种已经存在的协议,甚至还未开发的协议。正式由于它是如此神奇,所以你只有在慢慢研究代码的时候才能体会到它的奥妙!

4.5 包缓冲packetbuf

  在abc的例程中,我们提过:

packetbuf_copyfrom("Hello", 6);

  在Rime协议栈中,存在一个包缓冲——packetbuf。在发送消息时,将包缓冲里的数据通过底层mac协议发送出去,在接收消息时,底层mac协议会将接收到的数据放到packetbuf中。packetbuf_copyfrom("Hello", 6);就是将字符串“Hello”拷贝到packetbuf中,等待发送。

4.6 缓冲队列queuebuf

  Rime协议中只有一个包缓冲,但是如果多个进程想发送消息该怎么办呢?将这些消息放在缓冲队列里(PS,这是我猜的,因为我还没接触过这部分的代码,后面会慢慢接触到)。

4.8 abc_send

  上面这么多的知识点,都是由abc_open引出的,abc_send会引出多少内容呢?

int abc_send(struct abc_conn *c)
{return rime_output(&c->channel); // 直接调用rime_output
}

  跟踪rime_output:

int rime_output(struct channel *c)
{RIMESTATS_ADD(tx); // 还不知道是啥,我也没看if(chameleon_create(c)) { // 创建一个变色龙包packetbuf_compact(); // 使包缓冲更紧凑,在packetbuf中将会学习到NETSTACK_LLSEC.send(packet_sent, c); // 发送!!return 1;}return 0;
}

  猛然间多了很多新东西:
  chameleon:负责将属性链表中的属性打包到packetbuf的头部
  LLSEC:利用底层协议发送packetbuf中的消息

4.9 变色龙chameleon

  从Rime架构中可以看出,Rime的形状类似于一个沙漏——上下两头大、中间小,而变色龙处于整个沙漏的中心。

这里写图片描述

Rime的变色龙架构图

  chameleon主要负责两件事儿,解析和打包。
  当接收到数据时,chameleon的unpack_header函数会解析存放到packetbuf中的包头,将解析出来的包头属性存放到属性数组中。
  当发送数据时,chameleon的pack_header函数会根据属性链表将包属性数组中的属性打包成packetbuf头。

4.10 LLSEC层

  我们追踪LLSEC的代码:

// 以下代码在core/net/netstack.h
#ifndef NETSTACK_LLSEC
#ifdef NETSTACK_CONF_LLSEC
#define NETSTACK_LLSEC NETSTACK_CONF_LLSEC
#else /* NETSTACK_CONF_LLSEC */
#define NETSTACK_LLSEC nullsec_driver  // 默认的LLSEC驱动
#endif /* NETSTACK_CONF_LLSEC */
#endif /* NETSTACK_LLSEC */

  默认为nullsec_driver, 继续追踪:

const struct llsec_driver nullsec_driver = {"nullsec",init,send,input
};

  再追踪NETSTACK_LLSEC.send(packet_sent, c);,即nullsec_driver.send

static void send(mac_callback_t sent, void *ptr)
{packetbuf_set_attr(PACKETBUF_ATTR_FRAME_TYPE, FRAME802154_DATAFRAME);NETSTACK_MAC.send(sent, ptr);
}

  最后,我们看到,NETSTACK_LLSEC.send会调用到NETSTACK_MAC.send

4.12 MAC层

  继续追踪代码,会发现NETSTACK_MAC.send会依次调用到更底层的NETSTACK_RDC, NETSTACK_FRAME, NETSTACK_RADIO。这些底层的东西目前还没有研究过,等待后续学习再补充。
  MAC层主要负责进行csma算法。
  下面是一张底层的参考图:

这里写图片描述

4.13 RDC层

  rdc层主要负责周期性唤醒。

4.14 FRAME层

  frame层主要负责将数据包封装成帧

4.15 RADIO层

  radio层主要负责将帧通过无线收发器发送出去吧

在Cooja中仿真

  进入cooja仿真器目录:

cd tools/cooja/

  运行cooja仿真器:

ant run

  此时应该出现仿真器界面,依次点击菜单上的:File->New Semulation->,输入仿真器名,然后点击->create,出现仿真界面

这里写图片描述

这里写图片描述

这里写图片描述

添加节点。选择motes->add motes->create new motes type->然后选择一种仿真节点,比如 ->Z1 mote。
这里写图片描述

弹出节点编译、配置界面,依次完成1,2,3,4.
这里写图片描述

在number of new motes处输入2,然后点击create new motes。
这里写图片描述>

此时在network区域将出现两个节点。单机其中一个节点,会显示该节点的覆盖范围。拖动该节点,确保另一个节点出现在其无线覆盖范围内。
这里写图片描述

点击simulation control区域的start按钮,此时两个节点都会开始运行。在左侧的network区域可以看到两个节点在收发数据。点击pause按钮暂停。在mote output区域有两个节点的打印信息。
这里写图片描述

可以在mote output区域内的Filter框内输入过滤条件,比如输入ID:1就只显示节点1的打印消息
这里写图片描述

我们可以从打印消息中看到,节点1发出了广播消息,且收到了节点2发出的广播消息(但是它不知道是节点2发出的)。

6 总结

  一个最简单的发送匿名广播的消息的程序,在协议栈方面就涉及到如此多的内容。但是这不是我们的终点,而是中点。我们将走过中点,走到终点!
  最后,我只能想到一句话“路漫漫其修远兮”!

这篇关于Contiki协议栈Rime:引子introduction的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/884569

相关文章

如何在Spring Boot项目中集成MQTT协议

《如何在SpringBoot项目中集成MQTT协议》本文介绍在SpringBoot中集成MQTT的步骤,包括安装Broker、添加EclipsePaho依赖、配置连接参数、实现消息发布订阅、测试接口... 目录1. 准备工作2. 引入依赖3. 配置MQTT连接4. 创建MQTT配置类5. 实现消息发布与订阅

使用Python进行GRPC和Dubbo协议的高级测试

《使用Python进行GRPC和Dubbo协议的高级测试》GRPC(GoogleRemoteProcedureCall)是一种高性能、开源的远程过程调用(RPC)框架,Dubbo是一种高性能的分布式服... 目录01 GRPC测试安装gRPC编写.proto文件实现服务02 Dubbo测试1. 安装Dubb

Nginx中配置HTTP/2协议的详细指南

《Nginx中配置HTTP/2协议的详细指南》HTTP/2是HTTP协议的下一代版本,旨在提高性能、减少延迟并优化现代网络环境中的通信效率,本文将为大家介绍Nginx配置HTTP/2协议想详细步骤,需... 目录一、HTTP/2 协议概述1.HTTP/22. HTTP/2 的核心特性3. HTTP/2 的优

关于WebSocket协议状态码解析

《关于WebSocket协议状态码解析》:本文主要介绍关于WebSocket协议状态码的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录WebSocket协议状态码解析1. 引言2. WebSocket协议状态码概述3. WebSocket协议状态码详解3

Qt 中集成mqtt协议的使用方法

《Qt中集成mqtt协议的使用方法》文章介绍了如何在工程中引入qmqtt库,并通过声明一个单例类来暴露订阅到的主题数据,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录一,引入qmqtt 库二,使用一,引入qmqtt 库我是将整个头文件/源文件都添加到了工程中进行编译,这样 跨平台

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines

【Linux】应用层http协议

一、HTTP协议 1.1 简要介绍一下HTTP        我们在网络的应用层中可以自己定义协议,但是,已经有大佬定义了一些现成的,非常好用的应用层协议,供我们直接使用,HTTP(超文本传输协议)就是其中之一。        在互联网世界中,HTTP(超文本传输协议)是一个至关重要的协议,他定义了客户端(如浏览器)与服务器之间如何进行通信,以交换或者传输超文本(比如HTML文档)。

【Go】go连接clickhouse使用TCP协议

离开你是傻是对是错 是看破是软弱 这结果是爱是恨或者是什么 如果是种解脱 怎么会还有眷恋在我心窝 那么爱你为什么                      🎵 黄品源/莫文蔚《那么爱你为什么》 package mainimport ("context""fmt""log""time""github.com/ClickHouse/clickhouse-go/v2")func main(

2024.9.8 TCP/IP协议学习笔记

1.所谓的层就是数据交换的深度,电脑点对点就是单层,物理层,加上集线器还是物理层,加上交换机就变成链路层了,有地址表,路由器就到了第三层网络层,每个端口都有一个mac地址 2.A 给 C 发数据包,怎么知道是否要通过路由器转发呢?答案:子网 3.将源 IP 与目的 IP 分别同这个子网掩码进行与运算****,相等则是在一个子网,不相等就是在不同子网 4.A 如何知道,哪个设备是路由器?答案:在 A

Modbus-RTU协议

一、协议概述 Modbus-RTU(Remote Terminal Unit)是一种基于主从架构的通信协议,采用二进制数据表示,消息中的每个8位字节含有两个4位十六进制字符。它主要通过RS-485、RS-232、RS-422等物理接口实现数据的传输,传输距离远、抗干扰能力强、通信效率高。 二、报文结构 一个标准的Modbus-RTU报文通常包含以下部分: 地址域:单个字节,表示从站设备