一、使用Lwip协议独立模式开发

最近在STM32F4上边移植了Lwip,Lwip是一个小型开源的TCP/IP协议栈,有无操作系统的支持都可以运行。我当前只测试了TCP Server功能,然后对TCP Server在Lwip协议栈的运行进行了分析。Lwip协议栈提供了有三种API,Raw API使用独立模式来开发,Netconn APISocket API是使用实时操作系统(RTOS)进行多线程来开发,由于我是没有跑操作系统,所以使用独立模式的工作模型,这种工作模型的运行机制是基于轮询模式不停地检查是否收到数据包。

Lwip使用事件回调机制与应用层通信,因此,应在进行通信之前,对相关事件注册回调函数。

二、Lwip协议栈中TCP的应用

对于TCP的应用需要使用以下的TCP Raw API函数接口:

我是使用正点原子的例程来调试的:

//TCP Server 测试
void tcp_server_test(void)
{
	err_t err;  
	struct tcp_pcb *tcppcbnew;  	//定义一个TCP服务器控制块
	struct tcp_pcb *tcppcbconn;  	//定义一个TCP服务器控制块
	
	u8 *tbuf;
 	u8 key;
	u8 res=0;		
	u8 t=0; 
	u8 connflag=0;		//连接标记
	
        printf("Explorer STM32F4 \r\n");	
	printf("TCP Server Test \r\n");
	printf("ATOM@ALIENTEK \r\n");
	printf("KEY0:Send data \r\n");
	printf("KEY_UP:Quit \r\n");
	tbuf=mymalloc(SRAMIN,200);	//申请内存
	if(tbuf==NULL)return ;		//内存申请失败了,直接退出
	sprintf((char*)tbuf,"Server IP:%d.%d.%d.%d",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);//服务器IP
        printf("Server IP:%d.%d.%d.%d\r\n",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]); 
	sprintf((char*)tbuf,"Server Port:%d",TCP_SERVER_PORT);//服务器端口号
	printf("Server Port:%d\r\n",TCP_SERVER_PORT);
	tcppcbnew=tcp_new();	//创建一个新的pcb
	if(tcppcbnew)			//创建成功
	{ 
		err=tcp_bind(tcppcbnew,IP_ADDR_ANY,TCP_SERVER_PORT);	//将本地IP与指定的端口号绑定在一起,IP_ADDR_ANY为绑定本地所有的IP地址
		if(err==ERR_OK)	//绑定完成
		{
			tcppcbconn=tcp_listen(tcppcbnew); 			//设置tcppcb进入监听状态
			tcp_accept(tcppcbconn,tcp_server_accept); 	//初始化LWIP的tcp_accept的回调函数
		}else res=1;  
	}else res=1;

	while(res==0)
	{
		key=KEY_Scan(0);
		//if(key==WKUP_PRES)break;
		if(key==KEY0_PRES)//KEY0按下了,发送数据
		{
			tcp_server_flag|=1<<7;//标记要发送数据
		}
		if(tcp_server_flag&1<<6)//是否收到数据?
		{			
			printf("tcp_server_recvbuf=%s\r\n",tcp_server_recvbuf);
			tcp_server_flag&=~(1<<6);//标记数据已经被处理了.
		}
		if(tcp_server_flag&1<<5)//是否连接上?
		{
			if(connflag==0)
			{ 
				sprintf((char*)tbuf,"Client IP:%d.%d.%d.%d",lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2],lwipdev.remoteip[3]);//客户端IP
                             printf("Client IP:%d.%d.%d.%d \r\n",lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2],lwipdev.remoteip[3]);
				
			  printf("Receive Data: \r\n");	
				connflag=1;//标记连接了
			} 
		}else if(connflag)
		{
			connflag=0;	//标记连接断开了
		}
		lwip_periodic_handle();
		delay_ms(2);
		t++;
		if(t==200)
		{
			t=0;
			LED0=!LED0;
		} 
	}   
	tcp_server_connection_close(tcppcbnew,0);//关闭TCP Server连接
	tcp_server_connection_close(tcppcbconn,0);//关闭TCP Server连接 
	tcp_server_remove_timewait(); 
	memset(tcppcbnew,0,sizeof(struct tcp_pcb));
	memset(tcppcbconn,0,sizeof(struct tcp_pcb)); 
	myfree(SRAMIN,tbuf);
} 

其中TCP连接建立后进入监听进行后注册了接收的回调函数tcp_accept(tcppcbconn,tcp_server_accept);,tcp_server_accept是回调的函数,程序源码如下:

//lwIP tcp_accept()的回调函数
err_t tcp_server_accept(void *arg,struct tcp_pcb *newpcb,err_t err)
{
	err_t ret_err;
	struct tcp_server_struct *es; 
 	LWIP_UNUSED_ARG(arg);
	LWIP_UNUSED_ARG(err);
	tcp_setprio(newpcb,TCP_PRIO_MIN);//设置新创建的pcb优先级
	es=(struct tcp_server_struct*)mem_malloc(sizeof(struct tcp_server_struct)); //分配内存
 	if(es!=NULL) //内存分配成功
	{
		es->state=ES_TCPSERVER_ACCEPTED;  	//接收连接
		es->pcb=newpcb;
		es->p=NULL;
		
		tcp_arg(newpcb,es);
		tcp_recv(newpcb,tcp_server_recv);	//初始化tcp_recv()的回调函数
		tcp_err(newpcb,tcp_server_error); 	//初始化tcp_err()回调函数
		tcp_poll(newpcb,tcp_server_poll,1);	//初始化tcp_poll回调函数
		tcp_sent(newpcb,tcp_server_sent);  	//初始化发送回调函数
		  
		tcp_server_flag|=1<<5;				//标记有客户端连上了
		lwipdev.remoteip[0]=newpcb->remote_ip.addr&0xff; 		//IADDR4
		lwipdev.remoteip[1]=(newpcb->remote_ip.addr>>8)&0xff;  	//IADDR3
		lwipdev.remoteip[2]=(newpcb->remote_ip.addr>>16)&0xff; 	//IADDR2
		lwipdev.remoteip[3]=(newpcb->remote_ip.addr>>24)&0xff; 	//IADDR1 
		ret_err=ERR_OK;
	}else ret_err=ERR_MEM;
	return ret_err;
}

从代码中可以看出回调函数tcp_server_accept里边又注册了四个回调函数。

tcp_recv(newpcb,tcp_server_recv);	//初始化tcp_recv()的回调函数
		tcp_err(newpcb,tcp_server_error); 	//初始化tcp_err()回调函数
		tcp_poll(newpcb,tcp_server_poll,1);	//初始化tcp_poll回调函数
		tcp_sent(newpcb,tcp_server_sent);  	//初始化发送回调函数

只分析tcp_recv回调函数,另外三个也一样的查找方法。回调函数tcp_server_recv进入之后是接收到的数据处理,但是在程序中只是初始化一遍,到底是什么地方有调用,再进行注册的回调函数发现回调函数给函数指针pcb->recv赋值,也就是pcb->recv指向了回调函数tcp_server_recv,在循环的程序中只要找到调用pcb->recv就会对接收到的数据进行处理。

void tcp_recv(struct tcp_pcb *pcb, tcp_recv_fn recv)
{
  LWIP_ASSERT("invalid socket state for recv callback", pcb->state != LISTEN);
  pcb->recv = recv;
}

在程序的搜索pcb->recv,可得出调用的地方:

#define TCP_EVENT_RECV(pcb,p,err,ret)                          \
  do {                                                         \
    if((pcb)->recv != NULL) {                                  \
      (ret) = (pcb)->recv((pcb)->callback_arg,(pcb),(p),(err));\
    } else {                                                   \
      (ret) = tcp_recv_null(NULL, (pcb), (p), (err));          \
    }                                                          \
  } while (0)

也就是使用TCP_EVENT_RECV宏定义的地方会去调用pcb->recvTCP_EVENT_RECV是在最底层的(在tcp层)就是tcp_input中调用,所以从后往前推可以调用了在应用层回调函数:tcp_input() -> * tcp_process() -> tcp_receive() (-> application)
而tcp_input被IP层调用的。

我只能写这么多,对协议的理解还不是很透彻,还没有深入去跟协议里边的东西,只是理解了TCP层的运行,而IP层还没有去跟进。但知道理解LWIP协议独立工作模式用回调函数的机制来与应用层进行通信可以在后面解析其它的协议的理解会有帮助。

三、参考文档

http://blog.csdn.net/houqi02/article/details/52205311
http://blog.csdn.net/morixinguan/article/details/65494239
http://blog.csdn.net/zbychhaozeng/article/details/6561490

by 羊羊得亿
2017-11-03 ShenZhen