这是一个牛人写的转过来
你要是想写一个完整的TCPserver的程序,你可以把程序写成电脑向你板子发送数据,然后板子将收到的数据回发给电脑。到后期再对收到的数据进行处理以达到你板子的需求。
这个收发程序的结果应该是能够完整的将收到的数据完整的发送给电脑,不论你电脑发送的速度有多快,包有多大,一切的不成功都是你自身的原因,不要试图降低发送频率或者将包变小来解决问题。那都是治标不治本的做法。
我用的板子是stm32+enc28j60,网上有一堆移植教程,不过可能你会在移植中出现各种问题,而且有可能你遇到的问题网上还没有,我建议有时间的话看看lwip的原理,这里推荐老衲五木的《LWIP协议详解》,相当经典,看完之后至少茅斯顿开了一大半。不过有可能你没有多少耐心去看完,就只能彷徨又着急的去四处求救了。
接下来说几个注意事项:
1.硬件:
- 要是初次移植LWIP,最好不要用路由器,因为那样你的板子会收到很多不是你电脑发送的数据包,同时电脑发送包的速度快了之后有的路由器就会根据自己的流量控制可能会丢弃包等,虽然对于可靠性高的TCP来说一切都不是问题,但是初学者会因为很多不明不白的现象晕很久。
- 最好不要用无线网来和板子通信,也就是板子插网线,你的笔记本接无线,然后两个通过路由器进行数据的传送,这样会出现你的ping很不稳定,还容易出现连接超时等。
- 你要是有条件就自己做一根电脑连接电脑形式的网线,直接将板子和电脑相连,这样的通信在前期是很方便和可靠的,至少你可以将基础的东西先调出来。同时要注意,要是笔记本的话,你可能插着网线又上着无线网,两个网卡工作有很大概率出现你没办法连接上你的板子。所以最好在连着板子的时候断掉无线网,或者在连接好板子并且端口之间连接上之后再打开无线网去上外网也是可以得。
2.软件:
[size=+0]最好参考官方lwip包里的APP,我在跑官方的程序的时候没有问题,但是看别人写的程序,拿来用用的时候就出了一堆问题。[size=+0]初级的移植相关的内容我就不说了,主要说在写TCPserver遇到的问题。
我在看别人写的收发数据的server,出现了在传输大量数据且速度很快的情况下丢失数据乃至于多出数据的情况.TCP是一种可靠性非常高的传输协议,在告诉和大量数据的情况下,就算有丢包(肯定有stm32无法应答的情况)TCP也会重传,而且有滑动窗口和阻塞窗口控制,能够知道server还能接受多大的数据包,所以一切的失败都不是TCP协议本身的问题,一定是硬件或者软件上写的有问题。
在这里先讲一个TCP整个工作的大致流程:enc28j60得到包-->ARP层(分析数据包是否更新ARP列表以及是否传输到IP层)-->IP(对收到的包进行重组,因为包太大所以发送的时候进行了分片,现在接收自然要重组)-->TCP(得到一个完整的数据包,所以你就可以在回调函数里的pbuf变量上取你想要的数据进行处理)
写server的时候尽量还是写完整一点,也就是尽量处理好错误和一些意外的情况。现在讲一下官方lwip中app的tcpecho_raw中需要注意的一些语句,这些语句你写server的时候也要注意,不然灾难不断:
- 当你接收到数据包的时候,比如 pbuf*p。你会取出temp->payload做相应的一些处理,然后可能还会做点别的事,当你做完之后你会释放这个包:pbuf_free(p),在此之前你会取出这个包指向的下一个包的地址p->next,但还有一件事需要在pbuf_free之前完成以下,pbuf_ref(p->next)。因为释放了p的话,p->next的引用也会被减一,可能就导致这个包也跟着被释放了,乃至于后面串着的包都被释放了,你就会发现现象就是你发回到电脑上的数据少了很多,发送一万字节可能只有4000多。
- 关于tcp_recved的使用,我看到很多人写程序的时候基本上只要到了接收的回调函数里面,就在最前面加上tcp_recved(pcb,p->tot_len)。这个函数的作用是增加滑动窗口的大小,从而可以接收到更多字节的数据,有的同学在接受一定数量的数据后无法得到数据了就是因为没有调用这句话。但是这句话不能一概写成tcp_recved(pcb,p->tot_len)。大家可以看看这个函数的说明在源代码里面,这个函数应该是你处理了多少的数据包就把len取多大的字节数。而不是一味的将p->tot_len填写进去,那样的画滑动窗口开太大了更加容易照成丢包,然后就老重发,不太好。具体可参考echo_send语句。
- 关于tcp_write,这个函数其实是将你的数据放到一个队列里,等以后再调用tcp_output发送出去,这里需要注意的是当你在你写了多少字节进去不一定在抓包的时候看到多少字节的数据包,因为它有可能会等write几次之后打包一起发送出去,还有个问题,在写之前最后用tcp_sndbuf(tpcb)看看还能发送多少字节的数据,这个是查看发送缓冲区的大小的函数。当你大于这个区域的时候,你就应该等待下次再发送,而能自动让你进行剩余数据发送的一个方法就是用tcp_sent的回调函数,当数据发送出去并得到了应答之后,lwip就会调用tcp_sent,你就可以在其回调函数里面写剩余的发送数据的语句了。这个最好加上,不然小心数据丢失哟。
- 尽量不要再lwip的应用程序里面写printf的函数,也就是串口函数等等,因为这样会拖延时间。因为拖延久了之后没有应答客户端,就会导致他重发,反正又乱了这样,我之前有次加了prinrf,就会出现收发数据一段时间以后,数据就发不出去了,发数据的时候还是一个字节一个字节往外蹦,所以需谨慎。