rucb是单跳单播的最顶层,将数据以块为单位进行传输(Bulk transfer)。
ruc,Reliable communication,保证可靠通信,主要实现确认和序列功能。
suc,Stubborn transmission,可靠通信的另一层。suc这一层在给定的时间间隔不断地重复发送数据包,直到上层让其停止。为防止无限重发,需要指定最大重发次数。
uc,unicast abstraction,将上层的数据包添加一个接收者头部。
ibc,identified sender best-effort broadcast,将上层的数据包添加一个发送者身份头部
abc,anonymous broadcast,匿名广播。即将数据包通过无线射频驱动(radio driver)发出去,接收来自无线射频驱动所有的包并交给上层。
建立连接
建立连接的实质是保存该连接一些信息(如发送者、接收者),Rime协议栈用一系列结构体保存这些连接状态信息。Rime每一层都有相应的连接结构体(以_conn结尾),上层嵌套下层,如下:
rucb_conn --> runicast_conn --> stunicast_conn --> unicast_conn --> broadcast_conn --> abc_conn
每个连接结构体都有相应的回调结构体(以_callbacks后缀结尾),该结构体的成员变量实为发送、接收函数指针。当接收到一个数据报,会调用该结构体相应的函数。回调结构体层次如下:
rucb_callbacks --> runicast_callbacks --> stunicast_callbacks --> unicast_callbacks --> broadcast_callbacks --> abc_callbacks
综上,连接建立_open、连接结构体_conn、回调结构体_callbacks间的关系如下图:
1>连接结构体
建立连接,实质是初始化结构体rucb_conn各个成员变量,结构体rucb_conn定义如下:
struct rucb_conn {
struct runicast_conn c;
const struct rucb_callbacks *u;
rimeaddr_t receiver, sender;
uint16_t chunk;
uint8_t last_seqno;
int last_size;
};
c
下一层连接结构体
u
结构体rucb_callbacks有3个函数指针成员变量,写数据块write_chunk、读数据块read_chunk、超时timeout,需要用户自己实现。
receiver、sender
用于标识接收者和发送者。receiver是指目的节点的接收地址
chunk
数据块数目
last_seqno
一次数据发送多个片段的最后一个序列号,当接收端接收到数据时,判断其序列号是否等于最后一个序列号,若等于则不接收(即,接收到最后一个数据块,停止接收)
数据发送
Rime协议栈建立连接后,就可以通信了(发送、接收数据),下面介绍单跳单播的发送数据情形。
Rime是层次型协议栈,整个发送数据过程是通过上层调用下层服务来完成的,具体如下:
rucb_send --> runicast_send --> stunicast_send_stubborn --> unicast_send --> broadcast_send --> abc_send -->rime_output -->NETSTACK_MAC.send
rucb是块传输层,可以理解成传输层,数据发送函数rucb_send的源代码如下:
int rucb_send(struct rucb_conn *c, const rimeaddr_t *receiver)
{
c->chunk = ;
read_data(c);
rimeaddr_copy(&c->receiver, receiver);
rimeaddr_copy(&c->sender, &rimeaddr_node_addr);
runicast_send(&c->c, receiver, MAX_TRANSMISSIONS);
return ;
}
c->chunk将数据块数目初始化为0,read_data进行一些Rime缓冲区初始化相关工作。rimeaddr_copy函数设置接收者receiver和发送者sender的Rime地址,rimeaddr_node_addr用于标识本节点的Rime地址。接下来调用下一层的发送函数runicast_send完成发送。
数据接收
Rime协议栈建立连接后,就可以调用数据接收函数recv来接收数据,整个接收数据过程是通过上层调用下层服务来完成的,具体如下:
recv --> recv_from_stunicast --> recv_from_uc --> recv_from_broadcast -->recv_from_abc
函数recv首先判断该数据包是不是最后一个序列(数据包被拆分的情况下),若不是,将收到的数据写入物理存储介质。recv函数流程图如下:
static void recv(struct runicast_conn *ruc, const rimeaddr_t *from, uint8_t seqno)
{
struct rucb_conn *c = (struct rucb_conn *)ruc; PRINTF("%d.%d: rucb: recv from %d.%d len %d\n",
rimeaddr_node_addr.u8[],rimeaddr_node_addr.u8[],
from->u8[], from->u8[], packetbuf_totlen()); if(seqno == c->last_seqno) {
return;
}
c->last_seqno = seqno; if(rimeaddr_cmp(&c->sender, &rimeaddr_null)) {
rimeaddr_copy(&c->sender, from);
c->u->write_chunk(c, , RUCB_FLAG_NEWFILE, packetbuf_dataptr(), );
c->chunk = ;
} if(rimeaddr_cmp(&c->sender, from)) {
int datalen = packetbuf_datalen(); if(datalen < RUCB_DATASIZE) {
PRINTF("%d.%d: get %d bytes, file complete\n",
rimeaddr_node_addr.u8[], rimeaddr_node_addr.u8[],
datalen);
c->u->write_chunk(c, c->chunk * RUCB_DATASIZE,
RUCB_FLAG_LASTCHUNK, packetbuf_dataptr(), datalen);
} else {
c->u->write_chunk(c, c->chunk * RUCB_DATASIZE,
RUCB_FLAG_NONE, packetbuf_dataptr(), datalen);
}
c->chunk++;
} if(packetbuf_datalen() < RUCB_DATASIZE) {
rimeaddr_copy(&c->sender, &rimeaddr_null);
}
}
函数recv首先判断接收到的包是不是最后一个序列(数据包太大时,需要拆分),如果是最后一个就返回(用最后序列号标识包传递完毕)。若不是最后一个序列,意味着还有数据要接收。接着判断发送者地址是否为空,若是,说明节点未曾接收到该包的任何序列,则建立文件以存放数据。确保发送地址无误之后,若块小于块的最大值(RUCB_DATASIZE),即这是数据包的最后的一块,写入最后一块,否则正常写入这块的数据。把块的数目累加,接着判断这块是否是最后一块(最后一块意味着数据包传输完毕),若是则将发送者地址设为空,否则返回。
释放连接
数据通信完毕之后,需要释放连接,以供其他进程是哟个。关闭连接实质上是将相应的连接结构体从连接表中删除。整个调用过程如下:
rucb_close --> runicast_close --> stunicast_close --> unicast_close --> broadcast_close --> abc_close --> channel_close -->list_remove