基于uIP和uC/OS-II嵌入式网络开发
——uIP主动发送数据分析
摘要:uIP协议栈简单易用,可以为16位单片机或者是更低级的处理器使用,占用的资源很少,相关移植网上有详细介绍,本文主要讨论uIP如何主动发送数据。所用的开发板是STM32系列的,uC/OS-II操作系统,开发板作为服务器端。
关键字:uIP;uC/OS-II;STM32
1 系统要实现的功能
主要功能如下:
开发板作为服务器端,PC做客户端。连接成功以后,服务器始终作为主动的一方,主动与PC发送数据,PC收到后回传数据帧给服务器。服务器不给PC交互数据时,则认为是空闲的,这时需要以3秒的频率给PC发送心跳,维持连接。
系统主要任务如下:
心跳任务:负责心跳的发送,3秒到了,如果服务器不在给PC交互数据中,则主动发送心跳帧给PC机;
网络数据接收任务:负责轮询的方式接收数据,OSTimeDly(1),1毫秒轮询一次,在这个任务中也处理500毫秒ARP超时和10秒TCP超时。收到数据以后交给协议栈处理,协议栈调用应用程序回调函数tcp_demo_appcall,判断是否新数据到来,若是则调用newdata()。在newdata()函数中处理PC发送过来的数据,并通过发送信号量的方式与主任务交互。
主任务:负责与PC的数据交互,合适的时机主动发送数据给PC机,然后通过信号量等待PC客户端的数据,等到以后再传下一包数据。
2 需要解决的问题
2.1 客户端多次重连
UIP协议栈默认提供20个用于listen状态的tcp结构,10个用于连接状态的tcp结构。这样服务器端可以处理多个客户端,现在考虑的是,服务器端只希望处理一个客户端。而客户端连接服务器的时候,不管现在服务器是否已经有客户端连接了,这样就需要服务器在收到新的客户端连接时,若已经与前一个客户端建立连接,需要先将其断开,然后再与这个新的建立连接。
2.1.1 若客户端主动关闭连接,这样很好理解
先看连接过程,3次握手:
UIP_CLOSED收到客户端的连接SYN,则变为UIP_SYN_RCVD,并给客户端发送SYN+ACK,收到客户端的ACK,则变为UIP_ESTABLISHED,表明建立了连接。
关闭过程:
UIP_ESTABLISHED,收到客户端的FIN,变为UIP_LAST_ACK,并给客户端发送FIN+ACK,
收到客户端的ACK,则变为UIP_CLOSED,标示已经断开了连接。
2.1.2 客户端不断开原来的连接,直接来新连接
鉴于上面的分析,此时有两种办法,在程序回调函数tcp_demo_appcall中,判断是连接,然后调用connected函数。
(1)服务器不管客户端,直接自己释放资源,代码如下:
这是方式是,客户端没有执行断开连接,服务器端也没有执行断开连接,服务器端只是单方面释放了自已占用的资源。
(2)服务器端主动关闭与客户端的连接,然后在与新的客户端连接,主要代码如下:
执行pool推送以后,协议栈调用调用回调函数,进行判断,代码如下:
2.2 主动发送数据
网口接收数据任务,收到数据后,交给协议栈处理。协议栈通过发送信号量的方式与主任务同步,这样主任务就可以发送下包数据。这样的流程,前提是网口接收数据任务优先级低,主任务可以在250毫秒内发送应用数据给客户端,若等待网口接收数据任务将ACK包发送给客户端以后,此时主任务再发送数据就算是主动发送了。
Uip协议栈提供了一个poll功能,可以这种方式主动发送数据,主要代码如下:
需要发送数据时,先调用uip_send将数据拷贝到uip_buf缓存,然后利用pool功能,获取要发送数据的IP首部和TCP首部,最后再组建MAC的14个字节,最终通过调用物理层函数实现数据发送。下面讨论主动发送数据带来的问题。
2.2.1 数据冲撞
发送心跳的任务与主任务都在运行,不能保证两者不发生碰撞,实际测试中存在这样一种状况,心跳数据包刚发送完,250毫秒内客户端没有ACK,但此刻主任务也要发送数据,造成冲撞,如下图所示,分析这一过程。
9118序号:这是发送的一个长度为11的心跳包,tcp块结构的发送序列号是230;
9120序号:183毫秒后主任务发送长度为38的数据帧,但是tcp快结构的发送序列号仍然是230;
9121序号:客户端认为这样的发送有误,TCP ACKed unseen segment。
实际客户端的应用层数据收到的数据是这样的,
FF FF 38 B2 00 00 00 00 80 0A FF
BE A9 B1 B1 BE A9 00 00 00 00 00 00 00 01 20 08 01 01 20 10 12 31 10 02 00 AA FF
第一个包是长度为11的心跳包,但是第二包的长度变为了27,即来到应用层的数据被截断了,协议栈把前面的11个字节给扔掉了。
2.2.2 稍加改进
找到了问题的原因,则试图进行改进,主动发送数据时,自定义变量保存这次要发送的数据长度,然后收到客户端对这包数据的ACK时清0。主动发送数据时,判断之前发送的数据是否都被ACK了,若有数据未被ACK,则将tcp块结构的发送序列号增加,实现的主要代码如下图所示。
使用这种方式随然可以避免数据被截断的问题,但是其它的问题还会出现。我发现的就有两个,一是收不到上位机的数据,非要等上位机重发,这样会造成时间的浪费,如下图所示。
84834:客户端已经发送了数据,但是收不到服务器端的ACK,将近300毫秒后,客户端重发这包数据,此时才收到服务器端的ACK。
二是上位机收不到服务器端的数据,但是服务器也不会超时重传,这样就会卡到这里,如下图所示。
145649:服务器端发送的数据,序列号是36996;
145660:收到客户端的ACK,ACK的是36977;
145567:服务器直接从37015开始发送数据,中间正好丢失一个长度为38的数据,下面客户端反复说明之前的一包没有收到,但是服务器端就是没有重传。
3 总结
由上看出,将UIP移植到操作系统以后,可以使用,但是UIP将发送和接收的数据都保存在一个缓存uip_buf中,且发送和接收数据的长度也都是一个变量len,这样会出现一些问题。对于超时重传等,我没有找到合适的解决办法,考虑到UIP和LWIP是同一个团队编写的,直接采用LWIP开发。