Rime协议学习笔记:(八)可靠单播块传输rucb

来源:互联网 发布:海岛奇兵战艇升级数据 编辑:程序博客网 时间:2024/05/29 08:40

八.可靠单播块传输rucb

   rucb源码文件:contiki-3.0/core/net/rime/rucb.[c/h]
  rucb:reliable unicast bulk transfer,即可靠单播块传输。


8.1 rucb相关定义

  rucb相关结构体:

struct rucb_conn {/**一个rucb连接*/  struct runicast_conn c; //runicast连接  const struct rucb_callbacks *u; //rucb的回调  linkaddr_t receiver, sender; //接收者和发送者地址  uint16_t chunk; //当前传输数据块的序列号  uint8_t last_seqno; //最后一个片段的序列号  int last_size; //最后一个片段的大小};struct rucb_callbacks {/**用户自定义rucb回调函数的结构体*/  /*写块数据回调函数,offset为已收到数据的长度,flag标志收到的chunk属于第一块、中间块、最后一块*/  void (* write_chunk)(struct rucb_conn *c, int offset, int flag, char *data, int len);  /*读块数据回调函数,offset为已发送的数据的长度*/  int (* read_chunk)(struct rucb_conn *c, int offset, char *to, int maxsize);  //超时回调函数  void (* timedout)(struct rucb_conn *c);};

8.2 主要操作

  rucb相关函数比较多且较为复杂,将其所有操作总结如下:
这里写图片描述

8.2.1 回调函数

  (1)用户自定义回调函数为:read_chunk、write_chunk和timedout。由用户在使用rucb时自己定义并调用,调用时封装在rucb_callbacks里,即rucb的回调类型。(详见8.3rucb实例中的使用方法)

static int read_chunk(struct rucb_conn *c, int offset, char *to, int maxsize){/**用户自定义,将待发送的数据分成多个块(chunk),并调度要发送的chunk将之拷贝到packetbuf中*//**在read_data中调用传递的参数为read_chunk(c,c->chunk * RUCB_DATASIZE,packetbuf_dataptr(),RUCB_DATASIZE);*/}static void write_chunk(struct rucb_conn *c, int offset, int flag, char *data, int datalen){/**用户自定义,处理recv接收到的数据包,此处定义为输出接收到的数据包的内容*//**recv中调用write_chunk的情况:write_chunk(c, 0, RUCB_FLAG_NEWFILE, packetbuf_dataptr(), 0);write_chunk(c, c->chunk * RUCB_DATASIZE,RUCB_FLAG_LASTCHUNK, packetbuf_dataptr(), datalen);write_chunk(c, c->chunk * RUCB_DATASIZE,RUCB_FLAG_NONE, packetbuf_dataptr(), datalen);*/}

  (2)系统回调函数为:接收回调recv、发送回调asked以及超时回调timedout。调用系统回调函数时封装在runicast_callbacks里,即runicast的回调类型。具体代码如下:

static const struct runicast_callbacks ruc = {recv, acked, timedout};static void recv(struct runicast_conn *ruc, const linkaddr_t *from, uint8_t seqno){/**系统接收回调函数,调用用户自定义的write_chunk来处理接收到的chunk*/  struct rucb_conn *c = (struct rucb_conn *)ruc;  PRINTF("%d.%d: rucb: recv from %d.%d len %d\n",     linkaddr_node_addr.u8[0],linkaddr_node_addr.u8[1],     from->u8[0], from->u8[1], packetbuf_totlen());  /*如果接收到的片段为上一个片段,则返回结束*/  if(seqno == c->last_seqno) {    return;  }  c->last_seqno = seqno;  /*如果发送者地址为空,则为第一次收到chunk,拷贝发送者地址*/  if(linkaddr_cmp(&c->sender, &linkaddr_null)) {    linkaddr_copy(&c->sender, from);//将from地址拷贝给发送者地址    c->u->write_chunk(c, 0, RUCB_FLAG_NEWFILE, packetbuf_dataptr(), 0);    c->chunk = 0;//将数据块序列号置零  }  /*如果拷贝发送者地址成功*/  if(linkaddr_cmp(&c->sender, from)) {    int datalen = packetbuf_datalen();//packetbuf_datalen()为packetbuf中已使用的长度    /*如果已使用的长度小于rucb最大长度,则表示收到最后一块chunk,输出传输完成的信息*/    if(datalen < RUCB_DATASIZE) {      PRINTF("%d.%d: get %d bytes, file complete\n",            linkaddr_node_addr.u8[0], linkaddr_node_addr.u8[1],            datalen);      c->u->write_chunk(c, c->chunk * RUCB_DATASIZE,                       RUCB_FLAG_LASTCHUNK, packetbuf_dataptr(), datalen);} /*如果不是最后一块chunk*/else {      c->u->write_chunk(c, c->chunk * RUCB_DATASIZE,            RUCB_FLAG_NONE, packetbuf_dataptr(), datalen);    }    c->chunk++;  }  /*如果packetbuf已使用的长度小于rucb最大长度,则表示收到最后一块chunk,将发送者地址置零*/  if(packetbuf_datalen() < RUCB_DATASIZE) {    linkaddr_copy(&c->sender, &linkaddr_null);  }}static void acked(struct runicast_conn *ruc, const linkaddr_t *to, uint8_t retransmissions){/**自动发送数据块chunk*/  struct rucb_conn *c = (struct rucb_conn *)ruc;  int len; //调度到packetbuf中的chunk的长度  PRINTF("%d.%d: rucb acked\n",linkaddr_node_addr.u8[0],linkaddr_node_addr.u8[1]);  c->chunk++;  len = read_data(c);//利用read_data读取拷贝到packetbuf中的chunk长度  /*如果长度为0,则返回结束*/  if(len == 0 && c->last_size == 0) {    return;  }  /*如果长度不为0,则调用runicast_send发送数据*/  if(len >= 0) {    runicast_send(&c->c, &c->receiver, MAX_TRANSMISSIONS);//MAX_TRANSMISSIONS默认定义为8    c->last_size = len;  }}static void timedout(struct runicast_conn *ruc, const linkaddr_t *to, uint8_t retransmissions){/**超时回调函数,如果收不到ACK则超时*/  struct rucb_conn *c = (struct rucb_conn *)ruc;  PRINTF("%d.%d: rucb timedout\n",         linkaddr_node_addr.u8[0],linkaddr_node_addr.u8[1]);  if(c->u->timedout) {    c->u->timedout(c);//调用用户自定义的超时回调函数  }}

8.2.2 read_data

static int read_data(struct rucb_conn *c){/**每次准备发数据前都要调用,读取本次要发送的数据块(chunk)来对packetbuf进行初始化工作*/  int len = 0;  packetbuf_clear();  /*如果读取到数据块,调用read_chunk*/  if(c->u->read_chunk) {    len = c->u->read_chunk(c, c->chunk * RUCB_DATASIZE,                packetbuf_dataptr(), RUCB_DATASIZE);//read_chunk返回的是发送的数据块的长度  }  packetbuf_set_datalen(len);//设置packetbuf已使用的长度  return len;}

8.2.3 rucb_open

void rucb_open(struct rucb_conn *c, uint16_t channel, const struct rucb_callbacks *u){/**打开rucb,并会依次打开系统回调和用户自定义回调*/  linkaddr_copy(&c->sender, &linkaddr_null);//将发送者地址设置为0  runicast_open(&c->c, channel, &ruc);//打开runicast连接,调用的是系统定义的回调函数  c->u = u;//将用户自定义回调传递给rucb结构体  //last_seqno和last_size初试化  c->last_seqno = -1;  c->last_size = -1;}

8.2.4 rucb_close

void rucb_close(struct rucb_conn *c){/**关闭rucb*/  runicast_close(&c->c);//关闭runicast}8.2.5 rucb_sendint rucb_send(struct rucb_conn *c, const linkaddr_t *receiver){/**发送rucb,只发送第一个chunk*/   c->chunk = 0;//准备发送,设置数据块序列号为0  read_data(c);//准备发送  linkaddr_copy(&c->receiver, receiver);//设置接收者地址  linkaddr_copy(&c->sender, &linkaddr_node_addr);//设置发送者地址为节点地址  runicast_send(&c->c, receiver, MAX_TRANSMISSIONS);//调用runicast发送第一个chunk  return 0;}

8.3 rucb可靠单播块传输实例

8.3.1 观察rucb分块传输结果

  利用Cooja仿真器创建2个节点,其中一个为发送节点一个为接收节点。对于发送节点,设置待发送文件数据的长度,定义read_chunk回调函数调度chunk;对于接收节点,保持持续接收状态,定义write_chunk回调函数处理recv收到的chunk。
(1)代码如下:

/**------------------------------------------------------------*//**发送节点,设置待发送文件数据的长度,定义read_chunk回调函数。*//**------------------------------------------------------------*/#include "contiki.h"#include "net/rime/rucb.h"#include "dev/button-sensor.h"#include "cfs/cfs.h"#include "lib/print-stats.h"#include <stdio.h>#define FILESIZE 200  //FILESIZE为待发送的数据(文件)的长度static unsigned long bytecount; //bytecount定义为(已发送数据的长度+将要发送的数据长度)static char file[FILESIZE]; //待发送的数据(文件)static char * fileptr; //指向待发送的数据(文件)的指针PROCESS(example_rucb_process, "Rucb example");AUTOSTART_PROCESSES(&example_rucb_process);static int read_chunk(struct rucb_conn *c, int offset, char *to, int maxsize){/**用户自定义回调函数,将待发送的数据分成多个块(chunk),并调度要发送的chunk将之拷贝到packetbuf中*//**在read_data中调用传递的参数为read_chunk(c,c->chunk * RUCB_DATASIZE,packetbuf_dataptr(),RUCB_DATASIZE);*/  int size; //本次要发送的数据块的长度  size = maxsize; //maxsize为每次发送的最大长度,即默认的64字节  /*用于控制每次发送的数据不超过64字节*/  /*如果最后一个数据块长度小于每次发送的最大字节数(maxsize)*/  if(offset + maxsize >= FILESIZE) {  //offset为偏移量的意思,即已发送的数据长度    size = FILESIZE - offset;  }  bytecount += size; //bytecount为本次发送完成后已发送的数据长度(offset+size)  /*没有chunk调度时,输出发送完成信息,详见8.3.2分析没有chunk调度时的条件*/  if(bytecount < FILESIZE && size < maxsize) {    printf("send complete\n");  }  /*将要发送的数据拷贝到packetbuf中*/  packetbuf_copyfrom(fileptr + offset, size);//fileptr+bytecount指向待发送数据块,见rucb实例  return size;}const static struct rucb_callbacks rucb_call = {NULL, read_chunk, NULL};static struct rucb_conn rucb;PROCESS_THREAD(example_rucb_process, ev, data){  PROCESS_EXITHANDLER(rucb_close(&rucb);)  PROCESS_BEGIN();  /*将fileptr指向待发送的数据(文件)*/    fileptr=file;  rucb_open(&rucb,137,&rucb_call);//打开rucb,设置信道号137,会依次打开系统回调和用户自定义回调  static struct etimer et;  etimer_set(&et, CLOCK_SECOND);  PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));  linkaddr_t recv;  /*设置接收者地址*/  recv.u8[0] = 2;  recv.u8[1] = 0;  rucb_send(&rucb, &recv);  PROCESS_END();}/**------------------------------------------------------------*//**接收节点,打开后一直处于接收状态,定义write_chunk回调函数。*//**------------------------------------------------------------*/#include "contiki.h"#include "net/rime/rucb.h"#include "dev/button-sensor.h"#include "dev/leds.h"#include "cfs/cfs.h"#include "lib/print-stats.h"#include <stdio.h>PROCESS(example_rucb_process, "Rucb example");AUTOSTART_PROCESSES(&example_rucb_process);static void write_chunk(struct rucb_conn *c, int offset, int flag, char *data, int datalen){/**用户自定义,处理recv接收到的数据包*//**recv中调用write_chunk的情况:write_chunk(c, 0, RUCB_FLAG_NEWFILE, packetbuf_dataptr(), 0);write_chunk(c, c->chunk * RUCB_DATASIZE,RUCB_FLAG_LASTCHUNK, packetbuf_dataptr(), datalen);write_chunk(c, c->chunk * RUCB_DATASIZE,RUCB_FLAG_NONE, packetbuf_dataptr(), datalen);*/  if(datalen>0){    printf("received: datalen=%d\n", datalen);  }}const static struct rucb_callbacks rucb_call = {write_chunk,NULL,NULL};static struct rucb_conn rucb;PROCESS_THREAD(example_rucb_process, ev, data){  PROCESS_EXITHANDLER(rucb_close(&rucb);)  PROCESS_BEGIN();  rucb_open(&rucb,137,&rucb_call);//打开rucb,设置信道号137,会依次打开系统回调和用户自定义回调  PROCESS_END();}

(2)仿真过程
  a.将runicast.c和rucb.c文件中的调试信息都打开,将接收节点2设置在发送节点1的通信范围外。观察Mote output窗口可知,rucb调用runicast(runicast调用stunicast)发送数据包,此时接收节点2收不到发送节点1发送的数据,即不能返回ACK信号,导致发送节点重新发送,达到最大重传次数后将超时。
这里写图片描述

这里写图片描述
  
  b.将runicast.c文件中的调试信息关闭,rucb.c文件中的调试信息打开,将接收节点2设置在发送节点1的通信范围内。此时观察分块传输的流程,即发送节点1发送200字节的数据包,分成3个大小为64字节的chunk和一个8字节的chunk依次传输,最后输出完成信息。
这里写图片描述

这里写图片描述

8.3.2 梳理rucb通信过程

  节点配置同8.3.1中,利用Cooja仿真器创建两个节点,发送节点1和接收节点2。在8.3.1的基础上修改代码,其中发送节点1的read_chunk函数增加每次调度时输出状态信息、调度第一块时输出信息,以及进程中定义具体待发送的数据内容;接收节点2的write_chunk函数打印收到的数据内容和长度信息。
(1)代码如下:

/**------------------------------------------------------------*//**发送节点,设置待发送文件数据的长度,定义read_chunk回调函数。*//**------------------------------------------------------------*/#include "contiki.h"#include "net/rime/rucb.h"#include "dev/button-sensor.h"#include "cfs/cfs.h"#include "lib/print-stats.h"#include <stdio.h>#define FILESIZE 200static unsigned long bytecount=0;static char * fileptr;static char file[FILESIZE];PROCESS(example_rucb_process, "Rucb example");AUTOSTART_PROCESSES(&example_rucb_process);static int read_chunk(struct rucb_conn *c, int offset, char *to, int maxsize){/**用户自定义回调函数,将待发送的数据分成多个块(chunk),并调度要发送的chunk将之拷贝到packetbuf中*//**在read_data中调用传递的参数为read_chunk(c,c->chunk * RUCB_DATASIZE,packetbuf_dataptr(),RUCB_DATASIZE);*/  int size; //本次要发送的数据块的长度  size = maxsize; //maxsize为每次发送的最大长度,即默认的64字节  /*用于控制每次发送的数据不超过64字节*/  /*如果最后一个数据块长度小于每次发送的最大字节数(maxsize)*/  if(offset + maxsize >= FILESIZE) { //offset为偏移量的意思,即已发送的数据长度    size = FILESIZE - offset;  }  bytecount += size; //bytecount为本次发送完成后已发送的数据长度(offset+size)  /*调度第一个chunk时,输出发送第一个数据块的信息*/  if(c->chunk == 0){    printf("send the first chunk\n");  }  /*没有chunk调度时,输出发送完成信息*/  if(bytecount < FILESIZE && size < maxsize) {    printf("send complete\n");  }  //每次调度chunk时输出状态信息  printf("size = %d,FILESIZE = %d,offset = %d,chunk = %d,maxsize = %d,  bytecount=%d\n",size,FILESIZE,offset,c->chunk,maxsize,bytecount);  printf("[size=FILESIZE-offset,offset=chunk*maxsize]\n");  /*将要发送的数据拷贝到packetbuf中*/  packetbuf_copyfrom(fileptr + offset, size);//fileptr+bytecount指向待发送数据块,见rucb实例  return size;}const static struct rucb_callbacks rucb_call = {NULL, read_chunk, NULL};static struct rucb_conn rucb;PROCESS_THREAD(example_rucb_process, ev, data){  PROCESS_EXITHANDLER(rucb_close(&rucb);)  PROCESS_BEGIN();  /*设置待发送数据的具体内容*/  int i;  for(i=0;i<FILESIZE;i++){      file[i]='d';  }  fileptr=file;  rucb_open(&rucb,137,&rucb_call);//打开rucb,设置信道号137,会依次打开系统回调和用户自定义回调  static struct etimer et;  etimer_set(&et, CLOCK_SECOND);  PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&et));  linkaddr_t recv;  /*设置接收者地址*/  recv.u8[0] = 2;  recv.u8[1] = 0;  rucb_send(&rucb, &recv);  PROCESS_END();}/**------------------------------------------------------------*//**接收节点,打开后一直处于接收状态,定义write_chunk回调函数。*//**------------------------------------------------------------*/#include "contiki.h"#include "net/rime/rucb.h"#include "dev/button-sensor.h"#include "dev/leds.h"#include "cfs/cfs.h"#include "lib/print-stats.h"#include <stdio.h>PROCESS(example_rucb_process, "Rucb example");AUTOSTART_PROCESSES(&example_rucb_process);static void write_chunk(struct rucb_conn *c, int offset, int flag, char *data, int datalen){/**用户自定义,处理recv接收到的数据包*//**recv中调用write_chunk的情况:write_chunk(c, 0, RUCB_FLAG_NEWFILE, packetbuf_dataptr(), 0);write_chunk(c, c->chunk * RUCB_DATASIZE,RUCB_FLAG_LASTCHUNK, packetbuf_dataptr(), datalen);write_chunk(c, c->chunk * RUCB_DATASIZE,RUCB_FLAG_NONE, packetbuf_dataptr(), datalen);*/  if(datalen>0){    char buf[datalen+1];    strncpy(buf,data,datalen); //将接收到的数据拷贝到设定的缓冲里    buf[datalen] = 0; //在最后加上字符串结束符    printf("received: datalen=%d, data=%s\n", datalen,buf); //输出数据内容长度信息  }}const static struct rucb_callbacks rucb_call = {write_chunk,NULL,NULL};static struct rucb_conn rucb;PROCESS_THREAD(example_rucb_process, ev, data){  PROCESS_EXITHANDLER(rucb_close(&rucb);)  PROCESS_BEGIN();  rucb_open(&rucb,137,&rucb_call);//打开rucb,设置信道号137,会依次打开系统回调和用户自定义回调  PROCESS_END();}

(2)仿真过程
  打开rucb.c中的调试信息,根据发送节点1和接收节点2的输出信息追踪打印这些信息的函数,来梳理节点间利用rucb通信的过程:
对于发送节点1:
  ①read_chunk函数将待发送的数据文件分为不同的数据块chunk,默认每一块chunk的最大长度为64字节;
  ②read_chunk调度第一块chunk,将之拷贝到packetbuf中,并输出发送第一块chunk的信息,rucb_send函数调用runicast_send发送第一块chunk;
  ③read_chunk继续调度剩余的chunk,而这些数据块都由asked函数发送(即rucb_send只发送第一块chunk,而剩余的chunk全部由asked函数自动发送);
  ④当read_chunk检查到没有chunk可供调度以后输出发送完成的信息。
对于接收节点2:
  ①recv函数自动接收数据包,并且调用write_chunk处理数据包;
  ②当收到最后一块chunk时,recv检测到该chunk的长度小于最大长度,判定为最后一块chunk,输出接收完成的信息。
这里写图片描述

将rucb的工作流程总结如下:
这里写图片描述

0 0