(1)、simple_udp_register()函数
contiki\core\net\ip
/*该函数简化contiki系统中基于UDP协议的Socknet接口操作,函数体中包含了udp_new()
与udp_bind()函数;udp_new()函数创建与远程节点地址及port连接;udp_bind()负责绑定接口
该函数将注册一个udp连接,同时指定一个回调函数,专门用于响应射频数据包接收事件。
远程IP地址设为NULL,表示本设备可以接收来自任何IP地址的数据包。若udp端口参数设为0
表示将设置一个临时的UDP接口在应用中使用*/
int
simple_udp_register(struct simple_udp_connection *c,
uint16_t local_port,
uip_ipaddr_t *remote_addr,
uint16_t remote_port,
simple_udp_callback receive_callback)
{
init_simple_udp();
c->local_port = local_port;
c->remote_port = remote_port;
if(remote_addr != NULL) {
uip_ipaddr_copy(&c->remote_addr, remote_addr);
}
c->receive_callback = receive_callback;
PROCESS_CONTEXT_BEGIN(&simple_udp_process);
c->udp_conn = udp_new(remote_addr, UIP_HTONS(remote_port), c);
if(c->udp_conn != NULL) {
udp_bind(c->udp_conn, UIP_HTONS(local_port));
}
PROCESS_CONTEXT_END();
if(c->udp_conn == NULL) {
return 0;
}
return 1;
}
(2)、simple_udp_sendto()
/*该函数负责向指定IP地址发送UDP数据包,
数据包通过在第五个函数simple_udp_register()中注册的UDP端口传送
*/
int
simple_udp_sendto(struct simple_udp_connection *c,
const void *data, uint16_t datalen,
const uip_ipaddr_t *to)
{
if(c->udp_conn != NULL) {
uip_udp_packet_sendto(c->udp_conn, data, datalen,
to, UIP_HTONS(c->remote_port));
}
return 0;
}
可以看到,数据包的发送是有contiki中的Socknet接口函数uip_udp_packnet_sendto()执行的。
contiki使用事件机制实现数据接收,当系统中tcpip_event事件触发时,系统将自动调用响应函数处理收到的数据。
(3)、uip_create_linklocal_allnodes_mcast()函数
uip.h文件中,uip_create_linklocal_allnodes_mcast()函数原型定义如下:
/** \brief set IP address a to the link local all-nodes multicast address */
#define uip_create_linklocal_allnodes_mcast(a) uip_ip6addr(a, 0xff02, 0, 0, 0, 0, 0, 0, 0x0001)
该函数具有一个形参a,表示连接本地所有节点的多播地址,多播地址由8个16进制数表示。
(4)、单播原理
a、单播服务器端
#include "contiki.h"
#include "lib/random.h"
#include "sys/ctimer.h"
#include "sys/etimer.h"
#include "net/ip/uip.h"
#include "net/ipv6/uip-ds6.h"
#include "net/ip/uip-debug.h"
#include "simple-udp.h"
#include "servreg-hack.h"
#include "net/rpl/rpl.h"
#include <stdio.h>
#include <string.h>
#define UDP_PORT 1234
#define SERVICE_ID 190
#define SEND_INTERVAL (10 * CLOCK_SECOND)
#define SEND_TIME (random_rand() % (SEND_INTERVAL))
static struct simple_udp_connection unicast_connection;
/*---------------------------------------------------------------------------*/
PROCESS(unicast_receiver_process, "Unicast receiver example process");
AUTOSTART_PROCESSES(&unicast_receiver_process);
/*---------------------------------------------------------------------------*/
static void
receiver(struct simple_udp_connection *c,
const uip_ipaddr_t *sender_addr,
uint16_t sender_port,
const uip_ipaddr_t *receiver_addr,
uint16_t receiver_port,
const uint8_t *data,
uint16_t datalen)
{
printf("Data received from ");
uip_debug_ipaddr_print(sender_addr);
printf(" on port %d from port %d with length %d: '%s'\n",
receiver_port, sender_port, datalen, data);
}
/*---------------------------------------------------------------------------*/
static uip_ipaddr_t *
set_global_address(void)
{
static uip_ipaddr_t ipaddr;
int i;
uint8_t state;
uip_ip6addr(&ipaddr, 0xaaaa, 0, 0, 0, 0, 0, 0, 0);
uip_ds6_set_addr_iid(&ipaddr, &uip_lladdr);
uip_ds6_addr_add(&ipaddr, 0, ADDR_AUTOCONF);
printf("IPv6 addresses: ");
for(i = 0; i < UIP_DS6_ADDR_NB; i++) {
state = uip_ds6_if.addr_list[i].state;
if(uip_ds6_if.addr_list[i].isused &&
(state == ADDR_TENTATIVE || state == ADDR_PREFERRED)) {
uip_debug_ipaddr_print(&uip_ds6_if.addr_list[i].ipaddr);
printf("\n");
}
}
return &ipaddr;
}
/*---------------------------------------------------------------------------*/
static void
create_rpl_dag(uip_ipaddr_t *ipaddr)
{
struct uip_ds6_addr *root_if;
root_if = uip_ds6_addr_lookup(ipaddr);
if(root_if != NULL) {
rpl_dag_t *dag;
uip_ipaddr_t prefix;
rpl_set_root(RPL_DEFAULT_INSTANCE, ipaddr);
dag = rpl_get_any_dag();
uip_ip6addr(&prefix, 0xaaaa, 0, 0, 0, 0, 0, 0, 0);
rpl_set_prefix(dag, &prefix, 64);
PRINTF("created a new RPL dag\n");
} else {
PRINTF("failed to create a new RPL DAG\n");
}
}
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(unicast_receiver_process, ev, data)
{
uip_ipaddr_t *ipaddr;
PROCESS_BEGIN();
servreg_hack_init();
ipaddr = set_global_address();
create_rpl_dag(ipaddr);
servreg_hack_register(SERVICE_ID, ipaddr);
simple_udp_register(&unicast_connection, UDP_PORT,
NULL, UDP_PORT, receiver);
while(1) {
PROCESS_WAIT_EVENT();
}
PROCESS_END();
}
/*---------------------------------------------------------------------------*/
服务器端unicast_receiver_process进程开始后,生成本地ipv6地址,并将地址添加到本地地址列表中,并且打印出本地地址信息。然后,以本地地址ipaddr创建一个RPL DAG。
程序核心就是建立同客户端的远程连接,并绑定通信端口,指定射频数据接收的回调函数。之后,程序进入无线循环,进行监听系统事件,接收到射频数据后,立刻由receiver()函数响应,将客户端IP,port,lenght,data等回传给PC。
b、单播客户端
#include "contiki.h"
#include "lib/random.h"
#include "sys/ctimer.h"
#include "sys/etimer.h"
#include "net/ip/uip.h"
#include "net/ipv6/uip-ds6.h"
#include "net/ip/uip-debug.h"
#include "sys/node-id.h"
#include "simple-udp.h"
#include "servreg-hack.h"
#include <stdio.h>
#include <string.h>
#define UDP_PORT 1234
#define SERVICE_ID 190
#define SEND_INTERVAL (60 * CLOCK_SECOND)
#define SEND_TIME (random_rand() % (SEND_INTERVAL))
static struct simple_udp_connection unicast_connection;
/*---------------------------------------------------------------------------*/
PROCESS(unicast_sender_process, "Unicast sender example process");
AUTOSTART_PROCESSES(&unicast_sender_process);
/*---------------------------------------------------------------------------*/
static void
receiver(struct simple_udp_connection *c,
const uip_ipaddr_t *sender_addr,
uint16_t sender_port,
const uip_ipaddr_t *receiver_addr,
uint16_t receiver_port,
const uint8_t *data,
uint16_t datalen)
{
printf("Data received on port %d from port %d with length %d\n",
receiver_port, sender_port, datalen);
}
/*---------------------------------------------------------------------------*/
static void
set_global_address(void)
{
uip_ipaddr_t ipaddr;
int i;
uint8_t state;
uip_ip6addr(&ipaddr, 0xaaaa, 0, 0, 0, 0, 0, 0, 0);
uip_ds6_set_addr_iid(&ipaddr, &uip_lladdr);
uip_ds6_addr_add(&ipaddr, 0, ADDR_AUTOCONF);
printf("IPv6 addresses: ");
for(i = 0; i < UIP_DS6_ADDR_NB; i++) {
state = uip_ds6_if.addr_list[i].state;
if(uip_ds6_if.addr_list[i].isused &&
(state == ADDR_TENTATIVE || state == ADDR_PREFERRED)) {
uip_debug_ipaddr_print(&uip_ds6_if.addr_list[i].ipaddr);
printf("\n");
}
}
}
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(unicast_sender_process, ev, data)
{
static struct etimer periodic_timer;
static struct etimer send_timer;
uip_ipaddr_t *addr;
PROCESS_BEGIN();
servreg_hack_init();
set_global_address();
simple_udp_register(&unicast_connection, UDP_PORT,
NULL, UDP_PORT, receiver);
etimer_set(&periodic_timer, SEND_INTERVAL);
while(1) {
PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&periodic_timer));
etimer_reset(&periodic_timer);
etimer_set(&send_timer, SEND_TIME);
PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&send_timer));
addr = servreg_hack_lookup(SERVICE_ID);
if(addr != NULL) {
static unsigned int message_number;
char buf[20];
printf("Sending unicast to ");
uip_debug_ipaddr_print(addr);
printf("\n");
sprintf(buf, "Message %d", message_number);
message_number++;
simple_udp_sendto(&unicast_connection, buf, strlen(buf) + 1, addr);
} else {
printf("Service %d not found\n", SERVICE_ID);
}
}
PROCESS_END();
}
/*---------------------------------------------------------------------------*/
进程开始后,设置本地IPv6地址,并将该地址添加到本地地址列表,并打印出本地地址信息。
该程序核心是建立同接收端的远程连接,并绑定UDP_PORT端口。设置发送定时器,间隔循环触发系统定时事件。之后,程序进入无限循环,监听系统事件,若是发送定时器事件,就重置发送定时器,并设定另一个定时器用于数据发送;若新的定时器触发定时事件,就将获取服务器IP地址,并向服务器发送射频数据包。由receiver()函数响应数据接收事件。
(5)、多播原理
#include "contiki.h"
#include "lib/random.h"
#include "sys/ctimer.h"
#include "sys/etimer.h"
#include "net/ip/uip.h"
#include "net/ipv6/uip-ds6.h"
#include "simple-udp.h"
#include <stdio.h>
#include <string.h>
#define UDP_PORT 1234
#define SEND_INTERVAL (20 * CLOCK_SECOND)
#define SEND_TIME (random_rand() % (SEND_INTERVAL))
static struct simple_udp_connection broadcast_connection;
/*---------------------------------------------------------------------------*/
PROCESS(broadcast_example_process, "UDP broadcast example process");
AUTOSTART_PROCESSES(&broadcast_example_process);
/*---------------------------------------------------------------------------*/
static void
receiver(struct simple_udp_connection *c,
const uip_ipaddr_t *sender_addr,
uint16_t sender_port,
const uip_ipaddr_t *receiver_addr,
uint16_t receiver_port,
const uint8_t *data,
uint16_t datalen)
{
printf("Data received on port %d from port %d with length %d\n",
receiver_port, sender_port, datalen);
}
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(broadcast_example_process, ev, data)
{
static struct etimer periodic_timer;
static struct etimer send_timer;
uip_ipaddr_t addr;
PROCESS_BEGIN();
simple_udp_register(&broadcast_connection, UDP_PORT,
NULL, UDP_PORT,
receiver);
etimer_set(&periodic_timer, SEND_INTERVAL);
while(1) {
PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&periodic_timer));
etimer_reset(&periodic_timer);
etimer_set(&send_timer, SEND_TIME);
PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&send_timer));
printf("Sending broadcast\n");
uip_create_linklocal_allnodes_mcast(&addr);
simple_udp_sendto(&broadcast_connection, "Test", 4, &addr);
}
PROCESS_END();
}
多播broadcast_example_process进程开始后,直接进入核心部分,首先注册UDP连接,建立同等设备的远程连接,并绑定通信端口,指定射频数据接收的回调函数。设置发送定时器,间隔循环触发系统定时事件;然后,系统进入无限循环,监听系统事件,若是发送定时器事件,就重置发送定时器,并设定另一个定时器用于数据发送;若新的定时器触发定时事件,通过uip_create_linklocal_allnodes_mcast()设置一个广播地址,接着调用simple_udp_sendto()函数以多播方式将数据包发送出去,射频数据包依旧由receive()函数响应,该函数将通信端口、数据长度等信息传给PC。
总结:
以上三个程序都是基于simple-UDP简单协议实现的。
单播通信中,收发双方使用会员模型交互通信,服务器与客户端节点注册相同的服务ID号,不需要储存对方节点的IP地址,便于实现动态通信;
接收节点创建一个RPL DAG;接收node在创建RPL DAG后,随即成为DAG 的 root node ,并且使用与发送节点相同的网络前缀。
多播通信中,UDP的简易版本simple-UDP依次使用简单的 连接结构体储存UDP连接,使用register函数初始化UDP连接,使用双定时器避免网络(信息)洪泛(flooding),设置多播IP地址,最后实现多播数据发送。
附录:
RPL协议:
RPL是基于IETF工作组标准的、应用于低功耗有损网络的路由协议;
RPL协议基于树型结构拓扑支持一个或多个 root nodes 向下遍历leaf nodes。使用RPL路由协议的网络中,可以包含一个或多个RPL实例;每个实例中,可以包含多个DAGs;每个DAG中存在唯一的DAG根。一个节点可以加入多个RPL实例,但是在一个实例中只能属于一个DAG。