一、使用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

基于Lwip协议栈中独立模式下回调函数的使用的更多相关文章

  1. python中进程池和回调函数

    一.数据共享 1.进程间的通信应该尽量避免共享数据的方式 2.进程间的数据是独立的,可以借助队列或管道实现通信,二者都是基于消息传递的. 虽然进程间数据独立,但可以用过Manager实现数据共享,事实 ...

  2. Unity C# 调用 C++ DLL 并在 DLL 中调用 C# 的回调函数

    Unity C# 调用 C++ DLL 并在 DLL 中调用 C# 的回调函数~~~    呵呵... 看着有点晕.. 再解释一下就是 在Unity中 使用 C# 调用 C++ 写的 DLL, 但是在 ...

  3. 基于Frida框架打造Art模式下的脱壳工具(OpenMemory)的原理分析

    本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80956614 作者dstmath在看雪论坛公布一个Android的art模式下基 ...

  4. vs2010中release模式下调试程序

    debug模式调试信息全,但是速度很慢,在数据量比较大的时候非常影响调试效率,release模式速度快,但是没有调试信息.所以在编译的时候很多编译器会提供一种折中的编译方式,在release下提供调试 ...

  5. vue组件中—bus总线事件回调函数多次执行的问题

    在利用vue组件进行事件监听时发现,如果对N个vue组件实例的bus总线绑定同一事件的回调函数,触发任意组件的对应事件,回调函数至少会被执行N次,这是为什么呢? 为此,调研了普通对象的事件绑定和触发实 ...

  6. CXF 入门:创建一个基于WS-Security标准的安全验证(CXF回调函数使用,)

    http://jyao.iteye.com/blog/1346547 注意:以下客户端调用代码中获取服务端ws实例,都是通过CXF 入门: 远程接口调用方式实现 直入正题! 以下是服务端配置 ==== ...

  7. 关于as中的事件与回调函数

    对于Observer模式, 在as中object(被观察者)既可以用事件(event),也可以用回调函数(caller)来通知观察者(observer).那在实际的开发中到底应该选择用event还是用 ...

  8. PHP – 在类中使用array_filter时回调函数的问题

    了一个类处理好友,其中有一个方法用来同步好友,而这个方法中需要从微博传来的关注列表和粉丝列表中,找到互相关注的用户,记录一下经验,主要还是关于回调函数. 按照我最初的理解,这样写就可以了 privat ...

  9. CentOS 6.5安装在VMWare中Bridge模式下网卡eth0不能自动激活的问题

    VMWare 12.5.2 CentOS 6.5 basic VMWare网卡配置选择Bridge方式 问题: 默认情况下ifconfig命令只能看到网络设备lo,看不到eth0,也没有分配合理的IP ...

随机推荐

  1. Objective-C - NSInteger转换NSString

    NSInteger不是对象, 转换为long匹配64位系统, 再组成字符串(%ld). NSString *inStr = [NSString stringWithFormat: @"%ld ...

  2. eclipse C开发添加自己的头文件搜索路径

    eclipse编译C程序时提示: ..\src\main.c:8:21: fatal error: my_type.h: No such file or directory 如图: 需要添加自己的头文 ...

  3. 1.Apache Axis配置文件WSDD详解

    转自:https://jyao.iteye.com/blog/1285516 1. Aapche Axis的Web Service Deployment Descriptor(WSDD)文件参考文档. ...

  4. Ubuntu16.04 “有线未托管”有线网络不可用问题解决

    Ubuntu16.04 “有线未托管”问题解决 电脑上安装的Ubuntu16.04 是通过先安装Ubuntu Server后在通过命令 sudo tasksel 安装的Gnome桌面环境,安装完成后发 ...

  5. Vue常见面试题汇总

    Vue框架常见面试题   1.active-class是哪个组件的属性?嵌套路由怎么定义? 答:vue-router模块的router-link组件. 2.怎么定义vue-router的动态路由?怎么 ...

  6. 参考分享《Python深度学习》高清中文版pdf+高清英文版pdf+源代码

    学习深度学习时,我想<Python深度学习>应该是大多数机器学习爱好者必读的书.书最大的优点是框架性,能提供一个"整体视角",在脑中建立一个完整的地图,知道哪些常用哪些 ...

  7. 【使用uWSGI和Nginx来设置Django和你的Web服务器】

    目录 安装使用uWSGI 配置Nginx结合uWSGI supervisor Django静态文件与Nginx配置 @ *** 所谓WSGI . WSGI是Web服务器网关接口,它是一个规范,描述了W ...

  8. 两种方法解决 "The License CNEKJPQZEX- has been cancelled..." 问题

    今天在使用 2017 的 IDEA 和 Pycharm 等IDE的时候,提示了如题的问题.之前实在 http://idea.lanyus.com/ 网站点击生成注册码,复制粘贴到 IDEA 中就好了, ...

  9. Python协程一点理解

    协程,又称微线程,纤程.英文名Coroutine. 线程是系统级别的它们由操作系统调度,而协程则是程序级别的由程序根据需要自己调度.在一个线程中会有很多函数,我们把这些函数称为子程序,在子程序执行过程 ...

  10. 紫书 例题 9-2 UVa 437 ( DAG的动态规划)

    很明显可以根据放不放建边,然后最一遍最长路即是答案 DAG上的动态规划就是根据题目中的二元关系来建一个 DAG,然后跑一遍最长路和最短路就是答案,可以用记忆化搜索的方式来实现 细节:(1)注意初始化数 ...