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

相关文章

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报文通常包含以下部分: 地址域:单个字节,表示从站设备

网络原理之TCP协议(万字详解!!!)

目录 前言 TCP协议段格式 TCP协议相关特性 1.确认应答 2.超时重传 3.连接管理(三次握手、四次挥手) 三次握手(建立TCP连接) 四次挥手(断开连接)  4.滑动窗口 5.流量控制 6.拥塞控制 7.延迟应答 8.捎带应答  9.基于字节流 10.异常情况的处理 小结  前言 在前面,我们已经讲解了有关UDP协议的相关知识,但是在传输层,还有

DNS协议基础笔记

1.定义 DNS(Domain Name System,域名系统)是互联网的一项核心服务,它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。 2.域名解析过程 当用户在浏览器中输入一个域名,浏览器首先会检查自己的缓存中是否有该域名对应的 IP 地址。本地 DNS 服务器收到查询请求后,首先会检查自己的缓存中是否有该域名对应的 IP 地址。根域名服务器收到查询请

4G模块、WIFI模块、NBIOT模块通过AT指令连接华为云物联网服务器(MQTT协议)

MQTT协议概述 MQTT(Message Queuing Telemetry Transport)是一种轻量级的消息传输协议,它被设计用来提供一对多的消息分发和应用之间的通讯,尤其适用于远程位置的设备和高延迟或低带宽的网络。MQTT协议基于客户端-服务器架构,客户端可以订阅任意数量的主题,并可以发布消息到这些主题。服务器(通常称为MQTT Broker)则负责接受来自客户端的连接请求,并转发消

HTTP协议 HTTPS协议 MQTT协议介绍

目录 一.HTTP协议 1. HTTP 协议介绍 基本介绍: 协议:  注意: 2. HTTP 协议的工作过程 基础术语: 客户端: 主动发起网络请求的一端 服务器: 被动接收网络请求的一端 请求: 客户端给服务器发送的数据 响应: 服务器给客户端返回的数据 HTTP 协议的重要特点: 一发一收,一问一答 注意: 网络编程中,除了一发一收之外,还有其它的模式 二.HTT

CAMediaTiming协议

今天看下下CALayer这个类,里面的属性是实现CAMediaTiming这个协议的,这里简单介绍一下CAMediaTiming协议里面的属性。官网链接 如下 beginTime:开始时间(和父类相关) timeOffset:动态的本地时间t,tp是父类事件。t = (tp - begin) * speed + offset.用于暂停一个layer。  fillMode:layer完成后的