扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

怎么回事?TCP没有顺序交付数据?

03/05 17:10
268
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

来源:公众号【鱼鹰谈单片机】,ID   :emOsprey

大家好,我是鱼鹰。最近鱼鹰在开发MQTT升级固件相关的功能,发现在获取固件时,不能一次性成功下载固件,因此修改了 lwip 的接收窗口 TCP_WND大小,却因此引发了更大的 Bug。

鱼鹰发现 tcp 数据竟然没有按顺序被应用层读取。

我们知道,TCP 数据是有序的,应用层读取时,应该能按顺序流获取数据,即使底层数据包不是按顺序进入网卡(比如 WIFI),协议栈也会处理好,从而顺序交付。

因此鱼鹰在使用 wireshark 抓包的同时,直接在 lwip 最底层的数据接收函数里面,直接分析数据包是否全部接收了。

uint32_t g_low_level_input_cnt = 0;uint32_t g_tcp_port_8080_cnt = 0;uint32_t g_tcp_seq_numbers[256] = {0};uint16_t g_tcp_seq_index = 0;uint32_t g_custom_pbuf_error;uint32_t g_rx_buf_alloc = 0;// 解析 8080 端口包static void count_tcp_port_8080(const uint8_t *frame, uint32_t framelength){if (frame == NULL){   return;}
if (framelength < (SIZEOF_ETH_HDR + 20U)){   return;}
const struct eth_hdr *eth = (const struct eth_hdr *)frame;if (lwip_ntohs(eth->type) != ETHTYPE_IP){   return;}
const uint8_t *ip = frame + SIZEOF_ETH_HDR;const uint16_t ip_header_len = (uint16_t)((ip[0] & 0x0FU) * 4U);if (ip_header_len < 20U){   return;}
if (ip[9] != 6U){   return;}
if (framelength < (SIZEOF_ETH_HDR + ip_header_len + 20U)){   return;}
const uint8_t *tcp = frame + SIZEOF_ETH_HDR + ip_header_len;const uint32_t seq = ((uint32_t)tcp[4] << 24) | ((uint32_t)tcp[5] << 16) |                      ((uint32_t)tcp[6] << 8) | (uint32_t)tcp[7];
const uint16_t src_port = (uint16_t)((tcp[0] << 8) | tcp[1]);const uint16_t dst_port = (uint16_t)((tcp[2] << 8) | tcp[3]);if (src_port == 8080 || dst_port == 8080){   g_tcp_port_8080_cnt++;   g_tcp_seq_numbers[g_tcp_seq_index] = seq;   g_tcp_seq_index = (uint16_t)((g_tcp_seq_index + 1U) % (sizeof(g_tcp_seq_numbers) / sizeof(g_tcp_seq_numbers[0])));}}

最终确定底层函数已经接收到了所有数据包。

但是非常奇怪的是,上层确实没有按顺序得到数据,因为接收函数 lwip_read  拿到的是后面的数据包,而不是第一个数据包(seq=1)。

而且是服务器发送 【TCP Window Full】的附近。而且一个奇怪的现象是,stm32 的底层函数只介绍到开始的几包数据,而 lwip_read 函数已经取到了更新的数据...

等等,最新数据包?那一刻鱼鹰悟了。

只有一个情况符合这种奇怪现象,DMA

只有它,可以不经过 CPU 处理,直接修改硬件!

查看源码,果然如此,它使用零拷贝技术,导致将之前的缓冲覆盖了,从而让 read 读取的数据看似不是顺序的。

于是鱼鹰将零拷贝改成拷贝方式,果然能顺序接收了,完美!

但是新的问题来了,接收速度太慢了,而且也会偶发非顺序接收。

那是怎么回事?想起几年前好像处理过类似的问题,于是查找提交记录,总算找到了,发现以前确实还解决过这么一个大坑。而且这个坑在完全换了一个项目代码的情况下仍然存在。

LWIP_MEMPOOL_DECLARE(RX_POOL, ETH_RX_DESC_CNT, sizeof(struct pbuf_custom), "Zero-copy RX PBUF pool");

赶紧处理并且将接收窗口等配置恢复,总算搞定了,但是网络的丢包仍然存在,应该是stm32 和 lwip 协议栈配置不合理导致的,仍需继续改善,不过起码能完整下载了。

这里涉及到多个数据流,稍有不慎,就会出现问题,ETH -> DMA -> DESC_LIST -> POOL -> APP。而且很奇怪的是 TCP_WND 竟然可以随便设置,正常情况下应该和 stm32 接收缓存有关才对。

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录