用lwip发送大量数据时,遇到的问题解答记录;

  这两天,师弟在两台电脑上搭建了lwipwin32通信平台,目的是能够不断发送一幅幅图片图像大小为1280*720大小的图片。如果不考虑压缩情况且是256色即1字节,这样大小的一幅图片需要发送大约发送1M字节。具有李逵性格的山东师弟目标是:客服端发送一次请求,服务器端能够通过TCP方式一次发送大约1M的数据;我震惊了, 我说你可知道MTU(1500)的上限,以及内存的考虑。当然让每次发送一次数据量尽可能大,这种想法是好的;看着他每天哀声叹气,临近毕业的我深表同情,另外我对lwip也蛮干感兴趣的,一直都想玩玩这个技术,但一直没有机会,言归正传,稍微介绍下lwip。

                          

1、概述:lwip是轻量型的TCP/IP实现,只需10几KB的RAM40几KB的ROM就能够跑起来,适合应用于嵌入式设备的网络通信。有牛人Adam Dunkels发明,提供给用户上那种接口

RAW TCP/IP、Squential API, BSD API(也就是常说的socket编程),前者编程稍微复杂点,协议和应用程序在一个进程里面,但是效率高。中者首先要操作系统的支持,但是一旦实现了操作系统模拟层的实现,编程妥妥的。后者是为了符合人们使用socket编程的习惯而模拟的socket实现。所以王者乃RAW TCP/IP。网上提供了源码的下载,现在已经更新至1.4.x(doc 开发与移植指导文档, src 源码 test 测试例子),doc里面罗列了RAW TCP/IP的开发接口函数。

2、demo搭建:

服务器端:
0 open_tap()里面选择适合的网卡

1 开辟新进程 sys_thread_new("http thread",server_init, NULL, 0, 0);

2 设定网址和默认路由器 netif_add(&netif, &ip, &mask, &gw, NULL, ethernetif_init, tcpip_input);

3 在server_init里面 分别调用pcb = tcp_new(); tcp_bind(pcb, IP_ADDR_ANY, 8000);pcb = tcp_listen(pcb);

tcp_accept(pcb, server_accept); tcp_poll(pcb, tcp_poll, 10);// 每5s执行一次tcp_poll;

4 当有新的连接到来时,即有新的数据接收时, server_accept 就好调用。在server_accept里面注册调用函数tcp_recv(pcb, server_recv);

此后每次新的数据过来时,就会调用server_recv函数。

5 在函数 static err_t server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)里面对pbuf进行分析,要注意p->next的值。

并且借鉴其他资料知道立即要调用tcp_recevd;很神秘的告诉你要调用否则不好使。后面这里有重要发现!!!。

6 调用pbuf_free函数,来释放pbuf;

客服端:

0~2步基本相同

3在client_init里面 分别调用pcb = tcp_new(); ret_val = tcp_connect(pcb, &dest, 8000, client_connected);

4 在client_connected里面发送连接请求。同时调用tcp_accept(pcb, client_recv)来注册数据接收处理回调函数。

5 在static err_t client_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);对接收到的数据进行处理

类似server_recv。

搭建完成之后:师弟发现,在server_recv 函数里面只能够tcp_write 128个字节的数据。当发送大于128个数据时,就会只能够接收128个数据。

找了半天发现[2]是由opt.h里面的参数MSS 128决定的, 另外还要注意一个参数即TCP_SND_WND 256。

改变这两个后,发送比TCP_SND_WND小且比MSS大的数据时, 客户端可以接受到一次等于MSS的数据。

试了各种策略后,客户端只接收一次。我们甚至怀疑是回调函数注册一次只能使用一次的问题,都无效。

后面查询各种资料[3], 后面发现英文资料lwip-user给出了很多启示:

包括分析TCP segment(底层自行封装成不同packet,不带ACK) 和 IP segment(>MTU时,会带ACK)

了解了opt.h中:

MSS (the smaller, the better) 128
TCP_SND_BUF 256
MEM_SIZE (1600) HEAP SIZE 如果tcp_write 用COPY的方式时是需要设置较大的值;
TCP_SND_QUEUELEN  4*(TCP_SND_BUF/tcp_mss)(最小为除式的两倍);
MEMP_NUM_TCP_SEG: 至少跟上面一样大
MEMP_NUM_PBUF(16) --->32 

TCP_WND 2048 接收窗口,接收多少个数据包的问题。

今天通过wireshark抓包发现,1.15 端发送给了 1.111端后面多于MSS的数据报文,而且发送端一直发送MSS长度的报文,

可以得知应该是接收端的没有应答正确。前面提到的tcp_recevd调用的是tcp_ack函数。其实就是对接收到的数据进行应答

但是同时发现有一个好用的函数 tcp_ack_now(pcb),是直接发送应答,因为后面跟了tcp_output(pcb)函数。

并且要了解flags 设置 pcb->flags |= TF_NODELAY | TF_ACK_NOW; len = tcp_sndbuf(pcb);会告知剩余的buf的长度

并且多次发送的时候可以设置这个值,从而手动告知系统还有多少buf可以发送,从而实现了多次发送的操作。

改了这两处之后,实验可以发送32000个字节,不错。

 参考文档:

[1] http://lwip.wikia.com/wiki/Category:LwIP_Application_Developers_Manual
[2] https://groups.google.com/forum/#!topic/osdeve_mirror_tcpip_lwip/4GAbtXFwlvk

[3] 百度:关于LWIP协议栈连续多次tcp_write后失败的解决过程

[4] lwip design and implementation

posted on 2014-04-16 22:47  山水懒人  阅读(11117)  评论(0编辑  收藏  举报

导航