Rime协议学习笔记:(七)可靠单播runicast

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/u012325020/article/details/53648742

七.可靠单播runicast


  runicast源码文件:contiki-3.0/core/net/rime/runicast.[c/h]
  根据runicast.h中的官方注释理解如下:
  runicast:the reliable single-hop unicast,即可靠单播。unicast使用确认和重传来保证接收节点成功接收数据包,二者的实现方法如下:
  确认:当接收节点已经确认收到数据包,ruc模块通过回调函数通知发送节点;
  重传:ruc使用stunicast实现重传。(从这里可以看出stunicast的存在是用于实现runicast)因此,ruc没有管理设置定时器和重传的细节,但可以集中实现确认。


7.1 runicast相关定义

  runicast相关结构体:

struct runicast_conn {
/**一个runicast连接*/
  struct stunicast_conn c;
  const struct runicast_callbacks *u;
  uint8_t sndnxt;//标记下一个待发送数据片的序列号
  uint8_t is_tx;//标记传输线路上是否有数据在传输
  uint8_t rxmit;//标记重传次数
  uint8_t max_rxmit;//标记最大重传次数
};

struct runicast_callbacks {
/**用户自定义runicast回调函数的结构体*/
  void (* recv)(struct runicast_conn *c, const linkaddr_t *from, uint8_t seqno);
  void (* sent)(struct runicast_conn *c, const linkaddr_t *to, uint8_t retransmissions);
  void (* timedout)(struct runicast_conn *c, const linkaddr_t *to, uint8_t retransmissions);
//超时函数
};

  runicast数据包相关属性:

#define RUNICAST_PACKET_ID_BITS 2
// PACKETBUF_ATTR_BIT 在packetbuf.h中的定义为:#define PACKETBUF_ATTR_BIT  1
#define RUNICAST_ATTRIBUTES  { PACKETBUF_ATTR_PACKET_TYPE, PACKETBUF_ATTR_BIT },\
                                {PACKETBUF_ATTR_PACKET_ID, PACKETBUF_ATTR_BIT * \
RUNICAST_PACKET_ID_BITS }, STUNICAST_ATTRIBUTES
// PACKETBUF_ATTR_PACKET_ID作为序列号将ACK包匹配到相应的数据包
/*PACKETBUF_ATTR_PACKET_TYPE标志packetbuf属性分类,见2.2.1(2);此处用于区别数据包和ACK包,1代表
ACK,0代表数据包*/

static const struct packetbuf_attrlist attributes[] ={RUNICAST_ATTRIBUTES PACKETBUF_ATTR_LAST};

7.2 主要操作

7.2.1 回调函数

  (1)用户自定义回调函数:由用户在使用runicast时自己定义并调用,调用时封装在runicast_callbacks里,即runicast的回调类型。(详见7.3runicast实例中的使用方法)
  (2)系统回调函数为:接收回调recv_from_stunicast和发送回调sent_by_stunicast。调用系统回调函数时封装在stunicast_callbacks里,即stunicast的回调类型。具体代码如下:

static const struct stunicast_callbacks runicast = {recv_from_stunicast, sent_by_stunicast};

static void recv_from_stunicast(struct stunicast_conn *stunicast, const linkaddr_t *from){
/**系统定义接收回调函数,分为ACK处理和数据包处理两部分*/
/**该函数分为ACK处理和数据包处理两部分。函数首先会打印接收到的数据属于哪种类型(ACK、数据包)。如果是ACK包则打印收到ACK信息,再将收到的ACK与数据包进行匹配,若匹配成功则打印ACK对应数据包的序列号,反之打印错误ACK包的信息;如果是数据包则打印受到数据包的信息,再判断队列里还有无数据,若有则发送ACK包并打印相关信息,若没有则打印无法发送ACK的信息。*/
struct runicast_conn *c = (struct runicast_conn *)stunicast;
  //此处打印接收到的数据属于哪种类型
PRINTF("%d.%d: runicast: recv_from_stunicast from %d.%d type %d seqno %d\n",
         linkaddr_node_addr.u8[0],linkaddr_node_addr.u8[1],
         from->u8[0], from->u8[1],
         packetbuf_attr(PACKETBUF_ATTR_PACKET_TYPE),
         packetbuf_attr(PACKETBUF_ATTR_PACKET_ID));

/*处理ACK确认包*/        
  if(packetbuf_attr(PACKETBUF_ATTR_PACKET_TYPE) == PACKETBUF_ATTR_PACKET_TYPE_ACK) {
      //如果收到的数据属于ACK,则此处打印收到ACK信息
PRINTF("%d.%d: runicast: got ACK from %d.%d, seqno %d (%d)\n",
             linkaddr_node_addr.u8[0], linkaddr_node_addr.u8[1],
             from->u8[0], from->u8[1],
             packetbuf_attr(PACKETBUF_ATTR_PACKET_ID),
             c->sndnxt);
      if(packetbuf_attr(PACKETBUF_ATTR_PACKET_ID) == c->sndnxt) {
        RIMESTATS_ADD(ackrx);
        //此处打印ACK包匹配哪一个数据包(利用PACKETBUF_ATTR_PACKET_ID实现ACK包匹配到相应的数据包)
        PRINTF("%d.%d: runicast: ACKed %d\n",
               linkaddr_node_addr.u8[0], linkaddr_node_addr.u8[1],
               packetbuf_attr(PACKETBUF_ATTR_PACKET_ID));
        c->sndnxt = (c->sndnxt + 1) % (1 << RUNICAST_PACKET_ID_BITS);
        c->is_tx = 0;//标记线路上无数据传输
        stunicast_cancel(&c->c);//取消重传
        if(c->u->sent != NULL) {
            c->u->sent(c, stunicast_receiver(&c->c), c->rxmit);
        }
      }
      else { //如果ACK包不能和数据包匹配,则此处打印错误的ACK信息
        PRINTF("%d.%d: runicast: received bad ACK %d for %d\n",
               linkaddr_node_addr.u8[0],linkaddr_node_addr.u8[1],
               packetbuf_attr(PACKETBUF_ATTR_PACKET_ID),
               c->sndnxt);
        RIMESTATS_ADD(badackrx);
      }
  } 

/*处理数据包*/
  else if(packetbuf_attr(PACKETBUF_ATTR_PACKET_TYPE) == PACKETBUF_ATTR_PACKET_TYPE_DATA) {
    uint16_t packet_seqno;//数据包的序列号
struct queuebuf *q;
RIMESTATS_ADD(reliablerx);
    //如果收到的数据属于数据包,则此处打印收到数据包信息
PRINTF("%d.%d: runicast: got packet %d\n",           
       linkaddr_node_addr.u8[0],linkaddr_node_addr.u8[1],
           packetbuf_attr(PACKETBUF_ATTR_PACKET_ID)); 
    packet_seqno = packetbuf_attr(PACKETBUF_ATTR_PACKET_ID);
    q = queuebuf_new_from_packetbuf();//从数据包缓冲区拷贝到队列缓冲区
if(q != NULL) {
  //如果队列中还有数据,则打印发送对应数据包的ACK的信息
      PRINTF("%d.%d: runicast: Sending ACK to %d.%d for %d\n",
             linkaddr_node_addr.u8[0],linkaddr_node_addr.u8[1],
             from->u8[0], from->u8[1],
             packet_seqno);
      packetbuf_clear();//清空数据包缓冲区
      packetbuf_set_attr(PACKETBUF_ATTR_PACKET_TYPE, PACKETBUF_ATTR_PACKET_TYPE_ACK);
      packetbuf_set_attr(PACKETBUF_ATTR_PACKET_ID, packet_seqno);
      stunicast_send(&c->c, from); //发送ACK
      RIMESTATS_ADD(acktx);
      queuebuf_to_packetbuf(q);//将队列缓冲区的数据拷贝到数据包缓冲区中
      queuebuf_free(q);//从内存中释放队列缓冲区
} else {
  //如果队列中没有数据了,则打印不能再发送ACK的信息
      PRINTF("%d.%d: runicast: could not send ACK to %d.%d for %d: no queued buffers\n",
             linkaddr_node_addr.u8[0],linkaddr_node_addr.u8[1],
             from->u8[0], from->u8[1],
             packet_seqno);
    }
    if(c->u->recv != NULL) {
      c->u->recv(c, from, packet_seqno);
    }
  }
}


static void sent_by_stunicast(struct stunicast_conn *stunicast, int status, int num_tx){
/**系统定义发送回调函数,只处理数据包,而不处理ACK包(ACK包在接收回调中处理)*/
struct runicast_conn *c = (struct runicast_conn *)stunicast;
  PRINTF("runicast: sent_by_stunicast c->rxmit %d num_tx %d\n", 
c->rxmit, num_tx); //输出重传次数(c->rxmit)和指定传输次数(num_tx)

  if(packetbuf_attr(PACKETBUF_ATTR_PACKET_TYPE) == PACKETBUF_ATTR_PACKET_TYPE_DATA) {
    c->rxmit += 1;//重传次数+1
    if(c->rxmit != 0) {
      RIMESTATS_ADD(rexmit);/*define RIMESTATS_ADD(x) rimestats.x++,即rime状态采集,rimestats定义一个结构体,标志Rime的一些状态参数*/
      PRINTF("%d.%d: runicast: sent_by_stunicast packet %u (%u) resent %u\n",
             linkaddr_node_addr.u8[0], linkaddr_node_addr.u8[1],
             packetbuf_attr(PACKETBUF_ATTR_PACKET_ID),
             c->sndnxt, c->rxmit);
    }
    if(c->rxmit >= c->max_rxmit) {  //当重传次数达到最大重传次数时
      RIMESTATS_ADD(timedout);//设置为超时
      c->is_tx = 0;//标记线路上无数据传输
      stunicast_cancel(&c->c); //取消重传
      if(c->u->timedout) {
        c->u->timedout(c, stunicast_receiver(&c->c), c->rxmit);
      }
      c->rxmit = 0; //将重传次数置零
      PRINTF("%d.%d: runicast: packet %d timed out\n",
             linkaddr_node_addr.u8[0],linkaddr_node_addr.u8[1], 
             c->sndnxt);
      c->sndnxt = (c->sndnxt + 1) % (1 << RUNICAST_PACKET_ID_BITS); //将1左移两位,变为4
    } else {
    }
  }
}

7.2.2 runicast_open

void runicast_open(struct runicast_conn *c, uint16_t channel, const struct runicast_callbacks *u){
/**打开runicast连接*/
stunicast_open(&c->c, channel, &runicast);//打开runicast连接,此处会打开系统定义的回调函数
  channel_set_attributes(channel, attributes);//设置通道属性
  c->u = u;
  /*初始化*/
  c->is_tx = 0;
  c->rxmit = 0;
  c->sndnxt = 0;
}

7.2.3 runicast_close

void runicast_close(struct runicast_conn *c){
/**关闭runicast连接*/
  stunicast_close(&c->c);//关闭stunicast
}

7.2.4 runicast_is_transmitting

uint8_t runicast_is_transmitting(struct runicast_conn *c){
/**判断传输线路上是否有数据在传输,即返回is_tx值*/
  return c->is_tx;
}

7.2.5 runicast_send

int runicast_send(struct runicast_conn *c, const linkaddr_t *receiver, uint8_t max_retransmissions){
/**发送runicast,会先判断当前是否有数据在传输*/
  int ret;

  /*如果有数据在传输则不发送,返回*/
  if(runicast_is_transmitting(c)) {
    PRINTF("%d.%d: runicast: already transmitting\n",
           linkaddr_node_addr.u8[0],linkaddr_node_addr.u8[1]);
    return 0;
  }

  /*如果没有数据在传输则发送数据*/
  packetbuf_set_attr(PACKETBUF_ATTR_RELIABLE, 1);
  packetbuf_set_attr(PACKETBUF_ATTR_PACKET_TYPE, PACKETBUF_ATTR_PACKET_TYPE_DATA);
  packetbuf_set_attr(PACKETBUF_ATTR_PACKET_ID, c->sndnxt);
  packetbuf_set_attr(PACKETBUF_ATTR_MAX_MAC_TRANSMISSIONS, 3);
  c->max_rxmit = max_retransmissions;//设置最大传输次数为指定值
  c->rxmit = 0;
  c->is_tx = 1;//标记当前有数据在传输
  RIMESTATS_ADD(reliabletx);
  PRINTF("%d.%d: runicast: sending packet %d\n",
         linkaddr_node_addr.u8[0],linkaddr_node_addr.u8[1],
         c->sndnxt);
  ret = stunicast_send_stubborn(&c->c, receiver, REXMIT_TIME); //调用tunicast的顽固发送,成功则返回1。
  if(!ret) {  //发送失败
    c->is_tx = 0;
  }
  return ret;
}

7.3 runicast可靠单播实例

7.3.1 Cooja仿真测试

  利用Cooja仿真器创建2个节点,设置二者相互发送1次runicast,不定义runicast_callbacks里的回调函数。
(1)代码如下:

#include <stdio.h>
#include "contiki.h"
#include "net/rime/rime.h"
#include "lib/list.h"
#include "lib/memb.h"
#include "dev/button-sensor.h"
#include "dev/leds.h"
#define MAX_RETRANSMISSIONS 4//定义最大重传次数

PROCESS(test_runicast_process, "runicast test");
AUTOSTART_PROCESSES(&test_runicast_process);

static const struct runicast_callbacks runicast_callbacks = {};//即利用系统定义的回调函数
static struct runicast_conn runicast;

PROCESS_THREAD(test_runicast_process, ev, data)
{
  PROCESS_EXITHANDLER(runicast_close(&runicast);)
  PROCESS_BEGIN();
  runicast_open(&runicast, 144, &runicast_callbacks);//打开runicast,设置通道号为144

  linkaddr_t recv;
  packetbuf_copyfrom("Hello", 6);
  /*设置接收节点地址*/
  recv.u8[0] = 2;//节点2中改为recv.u8[0] = 1
  recv.u8[1] = 0;

  printf("%u.%u: sending runicast to address %u.%u\n",
         linkaddr_node_addr.u8[0],
         linkaddr_node_addr.u8[1],
         recv.u8[0],
         recv.u8[1]);

  runicast_send(&runicast, &recv, MAX_RETRANSMISSIONS);//发送runicast
  PROCESS_END();
}

(2)仿真过程
  将编译所用的contiki目录下的runicast.c文件中的调试信息打开(修改图中的0为1),以方便追踪输出信息由哪些函数打印,从而可以清晰地认识runicast的通信过程。
这里写图片描述
  将节点2设置在节点1的通信范围外,此时二者都收不到对方的发送的数据,即不能返回ACK信号,导致节点重新发送,达到最大重传次数后将超时。
这里写图片描述

这里写图片描述
  将节点2移动到节点1的通信范围内,两个节点可以相互收到对方发送的数据并返回ACK给对方,二者都发送一次runicast结束。
这里写图片描述

这里写图片描述

7.3.2 CC2538实测

  设置两个CC2538节点,其中一个为发送节点一个为接收节点。对于发送节点,设置为循环发送,并且只定义自己的发送和超时回调函数;对于接收节点,保持持续接收状态,只定义自己的接收回调函数,接收到数据包后打印数据包内容。
(1)代码如下:

/**-----------------------------------------------------------*/
/**发送节点,循环发送runicast,用户只定义发送和超时回调函数。*/
/**-----------------------------------------------------------*/
#include <stdio.h>
#include "contiki.h"
#include "net/rime/rime.h"
#include "lib/list.h"
#include "lib/memb.h"
#include "dev/button-sensor.h"
#include "dev/leds.h"
#define MAX_RETRANSMISSIONS 4

PROCESS(test_runicast_process, "runicast test");
AUTOSTART_PROCESSES(&test_runicast_process);

/**自定义发送回调函数*/
static void sent_runicast(struct runicast_conn *c, const linkaddr_t *to, uint8_t retransmissions)
{
  printf("runicast message sent to %d.%d, retransmissions %d\n",
         to->u8[0], to->u8[1], 
         retransmissions);
}

/**自定义超时回调函数*/
static void timedout_runicast(struct runicast_conn *c, const linkaddr_t *to, uint8_t retransmissions)
{
  printf("runicast message timed out when sending to %d.%d, retransmissions %d\n",
         to->u8[0], to->u8[1], 
         retransmissions);
}

static const struct runicast_callbacks runicast_callbacks = {NULL, sent_runicast, timedout_runicast};
static struct runicast_conn runicast;

PROCESS_THREAD(test_runicast_process, ev, data)
{
  PROCESS_EXITHANDLER(runicast_close(&runicast);)
  PROCESS_BEGIN();

  runicast_open(&runicast, 144, &runicast_callbacks);//打开runicast,设置通道号为144

  while(1) {
    static struct etimer et;
    etimer_set(&et, 10*CLOCK_SECOND);//设置etimer定时器
    PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));

    if(!runicast_is_transmitting(&runicast)) {
      linkaddr_t recv;
      packetbuf_copyfrom("Hello", 6);

/*设置接收节点地址,和硬件有关*/
      recv.u8[0] = 0x7f;
      recv.u8[1] = 0x5b;//节点2中改为recv.u8[1] = 0xb3

      runicast_send(&runicast, &recv, MAX_RETRANSMISSIONS);//发送runicast
    }
  }

  PROCESS_END();
}


/**-----------------------------------------------------------*/
/**接收节点,打开后一直处于接收状态,用户只定义接收回调函数,接收到数据包后打印数据包内容。*/
/**-----------------------------------------------------------*/
#include <stdio.h>
#include "contiki.h"
#include "net/rime/rime.h"
#include "lib/list.h"
#include "lib/memb.h"
#include "dev/button-sensor.h"
#include "dev/leds.h"
#define MAX_RETRANSMISSIONS 4

PROCESS(test_runicast_process, "runicast test");
AUTOSTART_PROCESSES(&test_runicast_process);

/**自定义接收回调函数**/
static void recv_runicast(struct runicast_conn *c, const linkaddr_t *from, uint8_t seqno)
{
  printf("runicast message received from %d.%d, seqno %d:%s\n",
         from->u8[0], from->u8[1], 
         seqno,
         (char *)packetbuf_dataptr());
}

static const struct runicast_callbacks runicast_callbacks = {recv_runicast};
static struct runicast_conn runicast;

PROCESS_THREAD(test_runicast_process, ev, data)
{
  PROCESS_EXITHANDLER(runicast_close(&runicast);)
  PROCESS_BEGIN();

  runicast_open(&runicast, 144, &runicast_callbacks);//打开runicast,设置通道号为144

    static struct etimer et;
    etimer_set(&et, 10*CLOCK_SECOND);//设置etimer定时器
    PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));

  PROCESS_END();
}

(2)实测结果
  分别测试调试信息打开和关闭情况下两节点的通信。根据发送节点和接收节点的输出信息追踪打印这些信息的函数,来梳理节点间利用runicast通信的过程:(以send代表发送节点,recv代表接收节点)
  ①send:调用runicast_send发送数据包;
  ②send:触发系统发送回调sent_by_stunicast;
  ③recv:收到数据包,触发系统接收回调recv_from_stunicast;
  ④recv:recv_from_stunicast判断收到的为数据包;
  ⑤recv:recv_from_stunicast向send发送ACK;
  ⑥recv:用户自定义接收回调函数被触发,打印出收到的数据包内容;
  ⑦recv:系统发送回调send_by_stunicast被触发,但只输出重传次数等信息;
  ⑧send:收到ACK,触发系统接收回调recv_from_stunicast处理ACK;
  ⑨send:触发用户自定义发送回调输出已发送数据的信息。
这里写图片描述

这里写图片描述

展开阅读全文

没有更多推荐了,返回首页