数据包发送过程
下面讨论发送数据包的流程。发数据包的过程相对复杂。用到了回调机制。
仍旧是两步(如图1中所示):1、高层(应用层)传递消息事件(UDP_POLL、TCP_POLL)给tcpip_process
2、tcpip进程处理函数(即tcpip_process)依据该事件调用数据包输出函数
其中第二步又可分为:产生完整的数据包 和 发送数据包 两部分。下面分别说明:1、产生完整数据包 通过一系列的准备工作,包括填充数据包头等,向我们的自定义应用进程传递tcpip_event消息事件,然后应用进程通过uip_send()函数填充要发送的数据信息;2、发送数据包 tcpip_output()
图1:数据收发的大概流程
以doc/example-program.c 为例,该函数执行的事情是:周期性的广播发送hello。如图2所示:
图2:example-program.c流程
一、网络层UDP传输
PROCESS_THREAD(example_program_process, ev, data)
{
static struct uip_udp_conn *c;
PROCESS_BEGIN();
c = udp_broadcast_new(UIP_HTONS(4321), NULL); ------建立一个udp广播连接
while(1) {
etimer_set(&timer, CLOCK_SECOND); --- 设置定时器
PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&timer)); -----等待定时器到时
tcpip_poll_udp(c); ----向tcpip进程传递UDP_POLL事件消息
PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event); ----等待回传的tcpip_event事件
uip_send("Hello", 5); ----填充要发送的数据
}
PROCESS_END();
}
首先解释:c=udp_broadcast_new(UIP_HTONS(4321), NULL);先创建UDP端口号是port=4321。
接下来从tcpip_poll_udp(c) 开始
void
tcpip_poll_udp(structuip_udp_conn*conn)
{
process_post(&tcpip_process,UDP_POLL,conn); // 给tcpip_process 传递消息 UDP_POLL
}
当tcpip_process 收到消息后,进行的处理如下:
static voideventhandler(process_event_tev, process_data_t data) //core/net/tcpip.c
{
switch(ev){
casePROCESS_EVENT_EXITED:
casePROCESS_EVENT_TIMER:
caseTCP_POLL:
case UDP_POLL:
if(data!= NULL) {
uip_udp_periodic_conn(data); // 产生数据
tcpip_ipv6_output(); // IPv6 发送
}
break;
};
}
其中uip_udp_periodic_conn 用于产生数据包,tcpip_ipv6_output()用于发送。
先看uip_udp_periodic_conn:
#defineuip_udp_periodic_conn(conn) do { uip_udp_conn =conn; \
uip_process(UIP_UDP_TIMER);} while(0)
转到uip_process()。 这是uip的核心处理函数,其实数据接收和发送均有其完成。
voiduip_process(u8_t flag) \core\net\uip6.c
if(flag== UIP_UDP_TIMER) {
if(uip_udp_conn->lport!= 0) {
uip_conn= NULL;
uip_sappdata= uip_appdata = &uip_buf[UIP_LLH_LEN + UIP_IPUDPH_LEN];
uip_len= uip_slen = 0;
uip_flags = UIP_POLL;
UIP_UDP_APPCALL(); /* 产生应用层数据 */
goto udp_send;
}else {
gotodrop;
}
udp_send:
/* 填充udp的包头*/
goto ip_send_nolen;
ip_send_nolen:
/* 填充ip层包头 , 校验和等*/
Return;
}
下面看 UIP_UDP_APPCALL 是如何产生数据的:
#define UIP_UDP_APPCALL tcpip_uipcall
Voidtcpip_uipcall(void) \core\net\uip6.c
{
registeruip_udp_appstate_t *ts;
ts =&uip_udp_conn->appstate;
if(ts->p!= NULL) {
process_post_synch(ts->p,tcpip_event, ts->state); \core\net\tcpip.c
}
}
通过process_post_synch(ts->p,tcpip_event, ts->state) 调回到udp连接相关联的进程,即我们最初的进程:example_program_process,向这个进程发送消息: tcpip_event,因此可以执行uip_send("Hello", 5);
PROCESS_THREAD(example_program_process,ev,data)
{
staticstructuip_udp_conn *c;
PROCESS_BEGIN();
c = udp_broadcast_new(UIP_HTONS(4321), NULL); ------建立一个udp广播连接
while(1) {
etimer_set(&timer, CLOCK_SECOND); --- 设置定时器
PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&timer)); -----等待定时器到时
tcpip_poll_udp(c); ----向tcpip进程传递UDP_POLL事件消息
PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event); ----等待回传的tcpip_event事件
uip_send("Hello", 5); ----填充要发送的数据
}
PROCESS_END();
}
这时,example_program_process 将继续执行,调用 uip_send()。
void uip_send(const void *data, int len) \core\net\uip.c
{
memcpy(uip_sappdata,(data), uip_slen);
}
Uip_send 执行的功能就是把应用层的数据拷贝到数据包的对应位置,然后返回。
至此在eventhandler()函数的 uip_udp_periodic_conn(data)执行完毕,进入tcpip_ipv6_output()处理。
staticvoid eventhandler(process_event_t ev, process_data_t data) //core/net/tcpip.c
switch(ev){
case UDP_POLL:
if(data!= NULL) {
uip_udp_periodic_conn(data); // 产生数据
tcpip_ipv6_output(); // IPv6 发送也许有报头压缩
}
voidtcpip_ipv6_output(void) \core\net\tcpip.c
{
#ifUIP_CONF_IPV6_QUEUE_PKT
if(uip_packetqueue_buflen(&nbr->packethandle)!= 0) {
。。。。。。
tcpip_output(&nbr->lladdr);
}
#endif/*UIP_CONF_IPV6_QUEUE_PKT*/
}
至此,网络层的数据包就已经完全生成了。
下面看发包函数tcpip_output (/core/net/tcpip.c)
u8_t tcpip_output(void)
{
if(outputfunc!= NULL) {
return outputfunc();
}
Return 0;
}
下面转到outputfunc()。这里需要说明下,在main函数中启动了进程 uip_fw_procss,该进程声明如下:
PROCESS_THREAD(uip_fw_process,ev,data) // /core/net/uip_fw_drv.c
{
PROCESS_BEGIN();
tcpip_set_outputfunc(uip_fw_output); 即outputfunc =uip_fw_output。
PROCESS_WAIT_UNTIL(ev== PROCESS_EVENT_EXIT);
PROCESS_END();
}
即:outputfunc= uip_fw_output。
下面进入 uip_fw_output。
u8_tuip_fw_output(void) \core\net\uip-fw.c
{
structuip_fw_netif *netif;
if(uip_len== 0) {
returnUIP_FW_ZEROLEN;
}
netif=find_netif();
if(netif== NULL) {
returnUIP_FW_NOROUTE;
}
Returnnetif->output();
}
该函数中先调用find_netif() 通过查路由表找到合适的网卡,然后调用该网卡的output函数。这就是IP层数据传输。
网卡在最开始的main函数中就定义过:SmeshLink\Platform\Mx2xxcc\contiki-mx2xxcc-main.c
Staticstruct uip_fw_netif meshif ={UIP_FW_NETIF(172,16,0,0,255,255,0,0, uip_over_mesh_send)};
所以,假设找到的网卡就是meshif,那么接下来就要调用uip_over_mesh_send函数。
uint8_tuip_over_mesh_send(void) \core\net\Uip-over-mesh.c
{
// uip_len = hc_compress(&uip_buf[UIP_LLH_LEN], uip_len);
//看不懂用了什么压缩方式。这里不是用来报头压缩,别被忽悠了,其作用待研究。
rt=route_lookup(&receiver);
if(rt == NULL) {
。。。。。。
}else {
route_decay(rt);
send_data(&rt->nexthop); //发数据到下一跳。
}
接着进入send_data(&rt->nexthop)函数,看看6Lowpan是如何实现打包的。
staticvoid send_data(rimeaddr_t *next) \core\net\Uip-over-mesh.c
{
PRINTF("uip-over-mesh: %d.%d: send_data with len%d\n",rimeaddr_node_addr.u8[0],rimeaddr_node_addr.u8[1],packetbuf_totlen());
unicast_send(&dataconn,next); //单播
}
再进入unicast_send(&dataconn,next)
Intunicast_send(structunicast_conn *c, const rimeaddr_t*receiver) \core\net\rime Unicast.c
{
return broadcast_send(&c->c);
}
Intbroadcast_send(structbroadcast_conn *c) \core\net\rimeBrocast.c
{
packetbuf_set_addr(PACKETBUF_ADDR_SENDER,&rimeaddr_node_addr);
return abc_send(&c->c);
}
Int abc_send(struct abc_conn*c) \core\net\rime Abc.c
{
return rime_output(&c->channel);
}
Int rime_output(struct channel *c) \core\net\rime Rime.c
{
RIMESTATS_ADD(tx);
if(chameleon_create(c)) {
packetbuf_compact();
NETSTACK_MAC.send(packet_sent,c);
// NETSTACK_MAC=nullmac_driver core\net\mac\Nullmac.c
//进入packet_sent函数有三种选择,有一种在\core\net\Sicslowpan.c中,该函数实现了报头压缩。
return 1;
} }
staticuint8_t output(uip_lladdr_t *localdest) //报头压缩函数调用体 \core\net\Sicslowpan.c
{
。。。。。。//报头压缩 支持分片。
send_packet(&dest); //发送压缩的报文。
}
staticvoid send_packet(rimeaddr_t *dest) \core\net\Sicslowpan.c
{
NETSTACK_MAC.send(&packet_sent,NULL); //据此进入Nullmac.C发送数据包
}
//检测邻居节点传输情况,是否可以传输
staticvoid packet_sent(void *ptr, int status,inttransmissions) \core\net\Sicslowpan.c
{
#if SICSLOWPAN_CONF_NEIGHBOR_INFO
neighbor_info_packet_sent(status,transmissions);
#endif/*SICSLOWPAN_CONF_NEIGHBOR_INFO */
if(callback != NULL) {
callback->output_callback(status); //callback 回调函数输出数据
}
last_tx_status = status;
}
以上三个函数是6Lowpan适配层的接口函数。根据static void send_packet(rimeaddr_t *dest) 函数的NETSTACK_MAC.send(&packet_sent,NULL); 已知NETSTACK_MAC=Nullmac 因此进入Nullmac.c文件的packet_sent函数,也就是进入了802.15.4 的MAC层了。
进入nullmac 的发送函数
staticvoid send_packet(mac_callback_t sent, void *ptr) \core\net\Mac\nullmac.c
{
NETSTACK_RDC.send(sent,ptr); // NETSTACK_RDC在 sicslowmac_driver \platform\mxusbstick\Contiki-conf.h
}
staticvoid send_packet(mac_callback_t sent, void *ptr)
{
if(packetbuf_hdralloc(len))
{
frame802154_create(¶ms,packetbuf_hdrptr(),len);
ret= NETSTACK_RADIO.send(packetbuf_hdrptr(),packetbuf_totlen());// NETSTACK_RADIO rf2xx_driver
}
NETSTACK_RADIO 网络协议的射频层使用rf2xx_driver 参见\platform\mxusbstick\Contiki-conf.h
至此进入NETSTACK_RADIO所指带的rf2xx_driver ,在该射频芯片下查找send 函数。
在\cpu\AVR\Radio\rf230bb\radio.c 里面包含了rf2xx_driver 所有函数。
staticint
rf230_send(constvoid *payload, unsigned short payload_len)
{
ret =rf230_transmit(payload_len);
}
至此物理层数据发送完毕。