ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LwIP TCP ZeroWindow排查:从内存管理误区到应用层根治

LwIP TCP ZeroWindow排查:从内存管理误区到应用层根治 开这个坑之前先交代一下背景一块基于STM32的工业网关跑的是LwIP协议栈做TCP透传。设备长时间、大流量跑了一天多之后对端抓包突然出现了TCP WINDOW FULL紧接着就是TCP ZeroWindow传输直接卡死。当时的第一反应和大多数人一样——LwIP的内存管理出毛病了是不是pbuf池不够、堆碎片化、MEMP耗尽然后一头扎进源码里看了好几天排查方向却越走越偏。最终的结论有点反直觉问题虽然表现为窗口耗尽但根子不在内存管理而在应用层对接收数据的处理和窗口更新时机。这篇文章就把整个排查过程、源码分析思路、踩过的坑和最终的解决套路完整复述一遍给同样在做LwIP网关或者协议栈移植的朋友一个参考。先说清楚这篇内容不是LwIP教程更像是从一次真实故障里倒推出来的排查笔记。里面涉及的源码分析点、参数配置方法、调试手段都是我实际验证过的。对刚接触协议栈的读者也可以当一份从现象到源码的阅读路线图来用。1. 问题现象与初判1.1 现象复现抓包里看到的双W警告故障发生在一个很普通的场景网关作为TCP客户端持续向服务器上传打包后的传感器数据。单包体积不大约300字节但发送频率很高平均每秒几十个包。运行十几个小时后服务器端抓包出现了两个醒目的告警标记——TCP Window Full和TCP ZeroWindow。TCP Window Full的意思是当前发送方的待发送数据量超过了接收方通告的可用窗口发送方不得不暂停发送。而TCP ZeroWindow则更进一步接收方通告的窗口已经变为0告诉发送方我这里一点接收空间都没有了你别再发了。正常情况下这两个状态出现都是短暂的因为TCP的滑动窗口机制会随着接收方处理数据、释放缓冲而恢复。但抓包显示这两个告警从出现开始就一直持续发送方反复发送TCP ZeroWindow Probe零窗口探测接收方不断回复ACK但窗口始终为0链路进入了假死状态。这个现象用一句话描述就是接收方向停摆传送通道被窗口卡死。1.2 第一直觉为什么人人都先怀疑内存管理LwIP在嵌入式网络里口碑不错但内存管理抠门也是出了名的。默认配置下pbuf池的数量、TCP段内存的数量、堆的大小都是用宏定死的数量级往往小得可怜。一旦运行期内存耗尽表现就是收包失败、连接复位、异常卡顿。所以当看到窗口一直为0时我的第一反应就是是不是接收方向的pbuf pool被耗尽导致TCP无法继续接收数据进而通告窗口为0顺着这个思路我马上检查了这几个宏PBUF_POOL_SIZE报文池pbuf个数PBUF_POOL_BUFSIZE每个池pbuf的大小MEMP_NUM_TCP_SEGTCP段内存数量TCP_WND接收窗口大小TCP_SND_BUF发送缓冲区大小。检查的结果是这些参数都不算极端PBUF_POOL_SIZE给了30MEMP_NUM_TCP_SEG给了32TCP_WND设了16KB。以单包300字节的量级看理论上足够。但现象不会骗人窗口就是卡死在0于是我开始怀疑是不是代码路径里某个地方把内存池耗完了。1.3 排查中的首个陷阱被宏定义带偏方向在深挖代码之前我犯了一个典型的错把应当够用当成了肯定没问题。我反复修改内存相关宏定义把MEMP_NUM_TCP_SEG从32加到64PBUF_POOL_SIZE从30加到64TCP_WND从16KB加到32KB然后重新编译跑压力测试。结果窗口还是会周期性卡死只是出现的时间延后了一些。这个现象本身就是个重要的线索如果是纯粹的内存池耗尽调大池子数量后问题应当彻底消失或者至少大幅缓解。但实测只是延后说明根子不在这。不过也不能完全怪自己被宏定义带偏。LwIP的源码结构确实容易让人往内存方向深挖——TCP接收路径上最明显的失败点就是pbuf分配失败然后就是MEMP_NUM_TCP_SEG耗尽后的段分配错误。这类错误在日志里会表现为pbuf_alloc(length) failed或者tcp_seg_free: no more segments而我当时的固件日志没有打印这些内容所以排查时少了一条关键线索。提示怀疑内存问题时第一步不是改宏而是先把LwIP的LWIP_DEBUG调试输出全部打开确认实际运行中内存分配是否真的失败。没有依据的加内存只会掩盖问题不会解决问题。2. LWIP TCP窗口机制与内存管理的底层关系2.1 TCP窗口其实是接收方剩余空间的通告要弄清ZeroWindow的根因得先理清LwIP里窗口是怎么维护的。TCP协议中接收方向发送方通告的窗口值本质上是接收方当前还能接收多少数据的承诺。发送方根据这个值控制自己可以发多少字节绝不能超过这个限制。在LwIP源码的tcp_structs.h里TCP控制块struct tcp_pcb中有几个关键字段rcv_wnd接收窗口的当前剩余值rcv_ann_wnd上次通告给对端的窗口值rcv_ann_right_edge通告窗口的右边界snd_wnd发送窗口即对端通告给本机的值snd_queuelen当前已排队但未确认的段数。这里有个关键点rcv_wnd和rcv_ann_wnd并不是同一回事。rcv_wnd表示本地真正剩余的接收能力而rcv_ann_wnd是已经告诉对端的窗口值。只有重新发送ACK并更新通告窗口时rcv_ann_wnd才会被更新为新的rcv_wnd。如果应用层没有及时取走数据即便内核缓冲区腾出来了也不会通告给对端对端看到的窗口还是那个旧小值。这正是ZeroWindow产生的直接机制接收方缓冲区满了通告窗口为0随后应用层开始慢慢取数据但LwIP内核没有主动发送新的窗口更新通告对端就一直以为窗口还是0。这里就涉及一个重要函数——tcp_recved()。2.2 tcp_recved()窗口恢复的开关LwIP通过tcp_next_iss()、tcp_receive()等函数管理接收状态但窗口的释放完全依赖应用层调用tcp_recved()。这个函数在tcp.c里源码很短作用却至关重要void tcp_recved(struct tcp_pcb *pcb, u16_t len) { if (pcb-flags TF_WND_SCALE) { pcb-rcv_wnd (len pcb-rcv_scale); } else { pcb-rcv_wnd len; } }它做的事情就是把已经交给应用层的数据长度从已占用状态回收到rcv_wnd中。注意光是调用tcp_recved()还不够LwIP不会立刻发送窗口更新包只有当下一次发送ACK或者数据时rcv_ann_wnd才会被同步或者满足特定条件触发窗口更新。在tcp_process()中有这么一段逻辑if (pcb-rcv_ann_right_edge - pcb-rcv_ann_wnd pcb-rcv_wnd) { // 需要更新通告窗口 }这段话的意思是只有当接收缓冲右边界与实际窗口范围的差值小于当前rcv_wnd时系统才有机会发送窗口更新。如果应用层长时间不读取数据、不调用tcp_recved()rcv_wnd一直为0更新条件永远不满足窗口就卡死在0。所以怀疑内存管理确实沾点边因为TCP接收缓冲区不管是pbuf还是tcp_seg确实被数据占满了。但真正的锁扣是应用层没有及时调用tcp_recved()来释放这些缓冲区。2.3 内存池耗尽会不会伪装成ZeroWindow这个问题值得单独展开。内存池耗尽和窗口耗尽在LwIP中确实会产生相似的表象但产生的路径不同。当MEMP_NUM_TCP_SEG耗尽时接收路径上的tcp_receive()在尝试获取新的TCP段时会发生assert或直接丢弃数据数据没进入接收队列窗口本身不会变成0。此时对端看到的往往是发了但没ACK或者ACK重复的现象。而PBUF_POOL_SIZE耗尽时网络接口层的收包会失败表现为丢包、重传甚至是连接重置。ZeroWindow的充分条件是接收队列确实有数据占用了rcv_wnd而且这些数据没有被应用层取走。换句话说缓冲区满和窗口0是同步发生的。内存池耗尽可以让缓冲区无法被占用但不会让窗口归0恰恰相反当缓冲区无法容纳新数据时LwIP根本不更新窗口而是让数据在协议栈外被丢弃。这就能解释我当时的困惑为什么内存宏改了又改窗口还是卡死。因为真正的因素根本不在内存宏而在应用层读取频率和tcp_recved()的调用时机。2.4 LWIP的窗口更新节奏不是即时的很多从Linux网络编程转过来的开发者对LwIP有一个误解认为TCP窗口更新是内核自动的、即时的。实际在LwIP中窗口更新的触发条件相当保守甚至可以说懒惰。LwIP的窗口更新主要发生在两种情况下本端发送数据或者ACK时顺带把新的窗口通告出去当通告窗口连续两次被填满且接收方继续收到数据时在应答中更新窗口。并没有一个专门的定时器去定期检查是否需要发送窗口更新。这意味着如果应用层长时间不读取数据LwIP本身不会主动告诉对端我的缓冲区又空了。这个问题在Linux协议栈里也有类似机制但LwIP的实现更依赖应用层配合。所以排查ZeroWindow时首要检查点应当是应用层代码而不是内存参数。这也是我踩坑之后的第一个深刻教训。3. 源码层面排查内存管理误区与真实原因3.1 第一轮源码阅读被pbuf分配路径牵着走最初的源码分析全程围绕内存管理展开。我把tcp_in.c里接收一条TCP数据段的路径翻了一遍整理出关键的几个节点eth_input/ip_input从网络接口收到报文tcp_input判断TCP段有效性tcp_receive处理接收队列和窗口tcp_seg_alloc为接收段分配内存。tcp_receive()里有一段典型的接收流程if (pcb-rcv_wnd 0) { // 接收窗口为0对端不应发送数据 // 但允许处理零窗口探测等特殊情况 }这段代码让我一度以为问题找到了只要rcv_wnd为0什么都进不来那肯定是rcv_wnd没有恢复而rcv_wnd不恢复是因为之前的段没释放没释放又是因为应用层没读。但当时的我停在缓冲占满这个浅层结论上直接去翻了内存池配置绕了一大圈。3.2 真正的转折点UipStack与netconn API隐性阻塞真正让我停下来的是日志。我把LwIP的LWIP_DEBUG打开后看到TCP_DEBUG输出里反复出现tcp_receive: rcv_wnd 0, right edge ... tcp_recved: recv advanced ...这说明应用层明明在接收数据rcv_wnd却一直没有恢复。于是我回到自己的应用层代码发现了一个致命的问题接收回调函数里我把数据拷贝出来的过程是同步的而且中间还夹了一个外部flash写入操作。这个写操作在极端情况下会阻塞数秒期间整个tcpip_thread卡住LwIP的接收处理没法继续。用raw API时数据到达后tcp_recv_fn回调是在tcpip_thread上下文中执行的。如果在回调里做耗时操作尤其是flash写入、加解密、文件系统操作等于把整个协议栈的接收路径锁死。数据虽然进了内核缓冲但应用层还没把数据彻底取走窗口自然无法释放。更隐蔽的是即使用了netconn API如果调用netconn_recv()之后没有及时调用netbuf_delete()或者不把数据从netbuf中取出同样会导致内核缓冲被占用窗口卡死。3.3 关键函数调用链从tcp_input到应用程序为了把这个问题彻底讲透我把一条完整的数据接收链画在脑子里也建议读者遇到此类问题都按这个链路自查网卡收到报文经过ethernet_input进入IP层tcp_input()找到对应的tcp_pcb调用tcp_process()和tcp_receive()tcp_receive()将数据放入接收队列并占用rcv_wnd触发tcp_recv_fn回调raw API或者唤醒netconn线程应用层取出数据调用tcp_recved()释放rcv_wnd下次发送ACK时更新通告窗口。这七步中任意一步被拖慢或者阻塞都会导致窗口不能及时恢复。我当时的卡点在第4步和第5步之间回调把数据交给应用层后要等flash写完才算结束而在flash写入期间第6步tcp_recved()不会执行第7步更是无从谈起。还有一个小细节值得提如果应用层压根没调用tcp_recved()那么第6步永远缺失。在netconn模式下内部其实会自动调用tcp_recved()但前提是应用层确实通过netconn_recv()把数据取出来了。如果只是把netconn_recv()拿到的指针存起来等到缓冲区快满时才处理窗口同样会卡死。3.4 内存管理与ZeroWindow的同病不同因既然内存管理不是根因为什么改大内存宏会推迟问题出现这背后的逻辑需要说透。LwIP的接收缓冲机制是多重嵌套的网络接口的pbuf池负责容纳原始报文TCP层会把pbuf中的数据整理到tcp_seg的队列中应用层再从tcp_seg中读取数据。每一次搬运都涉及内存分配。如果某级内存不足数据搬运就会停滞即使应用层疯狂调用tcp_recved()也没有数据可读窗口仍然不会恢复。所以加大内存宏之所以有效是因为它延长了应用层来不及处理之前的缓冲时间。但应用层的处理能力终归是瓶颈内存加到一定程度后瓶颈依然是应用层。这就解释了为什么我调到64个TCP段后故障只是从十几个小时推迟到一整天。内存管理和ZeroWindow更像是上游流水线堵塞和下游仓库爆仓的关系。你可以不停扩大仓库容量但只要下游出货速度跟不上仓库总会被填满。真正要做的是让出货环节跑起来。3.5 从源码反推的排查结论打开源码、看完整个窗口更新机制后我最终整理出的排查结论是ZeroWindow的直接原因应用层未及时调用tcp_recved()或者应用层处理速度跟不上接收速度ZeroWindow的加速因素接收回调中做了耗时操作阻塞了LwIP线程内存管理只是背锅侠内存池不足会提前触发类似表现但不是根因。有了这个结论后面的修复就非常清晰了。4. 实操从现象到修复的完整步骤4.1 第一步打开调试输出确认内存是否真的耗尽遇到类似问题第一件事不是改宏而是把LwIP的调试开关打开。推荐在lwipopts.h里至少开启以下选项#define LWIP_DEBUG 1 #define TCP_DEBUG LWIP_DBG_ON #define TCP_INPUT_DEBUG LWIP_DBG_ON #define TCP_OUTPUT_DEBUG LWIP_DBG_ON #define PBUF_DEBUG LWIP_DBG_ON #define MEM_DEBUG LWIP_DBG_OFF #define MEMP_DEBUG LWIP_DBG_OFF重点观察两类输出是否有pbuf_alloc失败、tcp_seg_alloc失败之类的日志是否频繁出现tcp_recved相关的窗口更新日志。如果有前者说明内存确实不足如果有后者说明应用层取数逻辑有问题。我当时的日志里没有前者反而反复出现接收回调被阻塞的信息通过自己添加的日志确认等于实锤了应用层问题。注意TCP_DEBUG非常啰嗦生产环境不要常开排查时开一下即可。输出走串口或者日志缓冲区耗时会增加但只要不长期在线运行影响可以接受。4.2 第二步检查应用层接收代码的三件套排查LwIP窗口问题时应用层代码只需要检查三件事第一raw API模式下接收回调里是否调用了tcp_recved()。正确的做法是static err_t tcp_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { if (p NULL) { // 对端关闭释放连接 tcp_close(pcb); return ERR_OK; } // 把数据拷贝到应用层缓冲 app_buffer_write(p-payload, p-len); // 及时释放该pbuf pbuf_free(p); // 关键告知内核这len字节的数据已经取走 tcp_recved(pcb, p-len); return ERR_OK; }注意pbuf_free(p)和tcp_recved(pcb, p-len)的先后顺序。这里有一个细节tcp_recved记录的是应用层已经收下的字节数与pbuf是否free没有必然关系但实际项目中几乎总是先释放pbuf再调用tcp_recved。如果你的代码只调用了pbuf_free而没调tcp_recved内核会认为数据还占着窗口窗口必然卡死。这是我见过最多的低级错误。第二netconn模式或者使用socket API下确认是否及时读取了数据。调用netconn_recv()之后要立刻把数据取出并调用netbuf_delete()。如果只读不删内部netbuf不会释放tcp_recved()永远不会被调用。第三检查接收回调里有没有做耗时操作。如果有flash写入、文件写入、复杂解析必须重构为回调只负责搬数据到应用层环形缓冲耗时操作放到独立任务/线程。4.3 第三步对耗时操作进行解耦别让协议栈线程背锅这一步是修复的核心。我的方案是引入一个接收环形缓冲区和处理任务数据到达时tcp_recv_cb只把数据拷入环形缓冲区立刻标记中断标志独立的应用任务轮询环形缓冲区把数据打包写入flash写flash期间LwIP完全不受影响后续数据照常接收tcp_recved()照常调用。改造之后窗口卡死问题从十几个小时出现一次变成了连续跑一周都没再出现。这里要特别强调一下方案听起来简单但实际实现时有个坑——环形缓冲区的读写互斥。如果tcp_recv_cb在tcpip_thread上下文写缓冲应用任务在另一个线程读缓冲必须保证原子的写操作否则缓冲区索引错乱。我使用了关中断短临界区的做法实测可靠。如果你的RTOS支持mutex用mutex保护也可以但临界区时间一定要短不要占用太久。4.4 第四步结合抓包验证修复效果修复完成后不要急着看业务数据先抓包验证窗口行为是否正常。用Wireshark打开抓包文件加两个过滤表达式tcp.analysis.zero_window tcp.analysis.window_full修复前这两个过滤结果会刷屏且持续很长时间。修复后即使出现也只是零星几个且很快会看到后续的ACK窗口更新恢复正常。更直观的验证方式是观察TCP流中Window字段的变化修复后窗口值应该在生产过程中动态升降而不是长期停留在0。另外可以开启接收方的窗口缩放TCP Window Scale确保大带宽场景下窗口上限不被16位限制卡住。开启方法是在lwipopts.h中#define LWIP_WND_SCALE 1 #define TCP_RCV_SCALE 2注意TCP_RCV_SCALE的单位是2的幂次设置2等于把窗口放大4倍。比如TCP_WND配置为32KB实际通告窗口就是128KB。这能显著改善高吞吐场景下的窗口压力但前提是应用层处理能力跟得上否则只是把故障延后。4.5 参数调整心得什么时候才真正需要改宏既然前面说内存宏不是根因那是不是永远不用改不是。正确的思路是根据应用层处理能力来判断。用公式来理解接收缓冲总容量可以粗略等于TCP_WND 若干MEMP_NUM_TCP_SEG对应的段内存应用层平均消费速率每秒取出并处理多少字节网络峰值接收速率。如果峰值接收速率远大于消费速率缓冲总容量就是扛峰值的底气。应用层处理越慢缓冲总容量就应该越大否则窗口很容易到0。但这里有个度——缓冲再大也只是给应用层争取处理时间处理速度上不去终归会卡。基于实测我建议在修复应用层问题后再把参数调到与业务流量匹配的水平。我的最终配置是参数原值调整后说明TCP_WND16KB32KB提升单连接接收窗口上限TCP_SND_BUF16KB32KB提升发送侧缓冲避免发送窗口受限PBUF_POOL_SIZE3040增加网卡层收包池MEMP_NUM_TCP_SEG3248增加TCP段内存MEMP_NUM_NETBUF1016netconn模式下的缓冲数量这些数值不能盲目照抄。调整原则是先解决应用层消费问题再评估缓冲容量最后才动内存参数。5. 常见问题与排查技巧实录5.1 问题速查表这里整理了一份实战问题对照表基本覆盖了LwIP TCP窗口相关的典型故障现象可能原因快速排查方法解决方案抓包持续ZeroWindow应用层未调用tcp_recved检查接收回调代码补上tcp_recved调用ZeroWindow周期性出现且时间越来越长应用层接收处理耗时阻塞tcpip_thread打印回调耗时日志耗时操作移出协议栈线程偶发ZeroWindow后很快恢复消费速率略低于峰值速率观察窗口恢复时间适当加大TCP_WND和缓冲池窗口为0且伴随大量丢包重传pbuff pool或者tcp_seg内存不足开启MEM_DEBUG/MEMP_DEBUG调大PBUF_POOL_SIZE或MEMP_NUM_TCP_SEG窗口长期很小如几百字节未开启Window Scale查看TCP握手选项开启LWIP_WND_SCALE只有发送方向出现Window Full发送缓冲区不足发送速度受限查看对端窗口大小调大TCP_SND_BUF对端收到数据但应用层不出现回调中未拷贝数据只存指针检查数据生命周期及时拷贝数据并释放pbuf5.2 Wireshark过滤器的正确用法排查窗口问题Wireshark有两个非常好用的过滤器但要配合正确的抓包位置。抓包一定要在对端或者中间交换机镜像端口抓因为TCP Window Full和ZeroWindow是发送方根据对端通告值标出来的分析结果只有在对端侧才能准确看到通告值的变化。如果只抓本端的包也能看到但分析逻辑容易混乱。推荐组合用法tcp.analysis.zero_window || tcp.analysis.window_full先看这两个状态出现的频率和持续时间再右键某个ZeroWindow包选择TCP Conversation查看完整的时间线。如果零窗口持续时间超过几百毫秒基本可以断定是应用层消费出现问题。另外不要忽略TCP握手阶段的Window Scale选项。如果对端支持窗口缩放而本端没有开启最大窗口只有64KB高带宽场景下很容易触顶。5.3 用内存统计宏判断内存到底够不够想知道内存是否真的不足除了看日志LwIP还提供了内存统计选项。在lwipopts.h中开启#define MEM_STATS 1 #define MEMP_STATS 1 #define MEMP_MEM_MALLOC 0然后在运行期调用extern struct stats_mem mem_stats; extern struct stats_memp memp_stats[];打印mem_stats-used、mem_stats-max、mem_stats-err以及memp_stats[MEMP_TCP_SEG]-used、memp_stats[MEMP_TCP_SEG]-max、memp_stats[MEMP_TCP_SEG]-err。我当时看到的结果让我印象很深MEMP_TCP_SEG的max只有20出头远远没有达到32的上限err为0。也就是说TCP段内存从未耗尽。这个数据直接证明内存管理不是瓶颈也帮我彻底放弃了对内存宏的执念。这也算是个排查技巧当现象指向内存时先量化不要靠猜。LwIP的内存统计机制虽然简单但足够说明问题。5.4 零窗口探测别把它当成异常ZeroWindow发生之后发送方会周期性地发送一个字节的探测包Zero Window Probe这是RFC 793规定的正常行为不是协议错误。LwIP在tcp_zero_window_probe()里实现了这套逻辑。排查时看到探测包不要慌反而可以通过探测包的响应节奏判断接收方是否存活如果接收方每次都能回复ACK窗口仍为0说明连接还在只是应用层没消费数据如果探测包没有ACK说明连接已经断或者对端死机。这个特性在单向调试时很有用。我正常靠它区分连接卡住和对端崩溃。5.5 一个容易忽视的低级错误窗口更新包被防火墙丢弃顺带说一个排查过程中的花絮。有一次窗口卡死后对端明明已经恢复了但本端迟迟没有反应。检查后发现本端发出的ACK带窗口更新信息由于TCP segment太小、触发不了Nagle算法被中间防火墙策略丢弃了。LwIP中Nagle算法的实现默认开启TCP_OVERRIDE_NB_Nagle 0小包可能会被合并延迟。虽然这个坑在窗口卡死排查中不是主角但如果你遇到窗口已恢复但传输没有继续的情况记得检查一下Nagle和延迟ACK的交互。解决办法是确认应用层发送的包是否过小或者临时关闭Nagletcp_pcb-flags | TF_NODELAY;这个操作一般只在调试时用生产环境不建议全局关Nagle。6. 最后的实操体会与小技巧这次排查给我的最大收获是学会了一条排查协议栈问题的主线从现象往调用链上推不要被表面参数带偏。TCP ZeroWindow这个现象直接对应的数据结构是rcv_wnd影响rcv_wnd的只有两件事——内核接收了多少数据、应用层取走了多少数据。内存池只是数据的载体不是窗口状态的决定因素。如果你现在正被LwIP的窗口问题困扰我建议按下面的顺序做先抓包确认ZeroWindow出现的上下文打开TCP_DEBUG和MEMP_STATS确认内存没有耗尽检查应用层接收代码看tcp_recved()是否被调用检查接收回调中是否有耗时操作最后再调整TCP_WND、PBUF_POOL_SIZE这类参数。另外分享一个小技巧在接收回调里加上入站时间戳和出站时间戳打印每次回调的耗时。这个日志平时看没什么用但出现问题的时候它比任何调试器的信息都直观。我这次就是靠着一行毫秒级耗时日志把锅从内存管理甩回给了flash写入逻辑。排查过程中我还有一个体会不要一上来就怀疑协议栈本身。LwIP作为使用量极大的开源协议栈基础收发和窗口管理逻辑经过千锤百炼出问题的概率远低于应用层的误用。真正有问题的往往是自己的代码——回调处理慢了、缓冲没释放、软件定时器里的数据没及时取走。最终修复方案落地以后我又按同样的思路复查了项目中其他几个TCP连接果然发现还有一个拨号连接存在同样的隐患——回调里做了base64编码编码后的数据没有及时tcp_recved。这也能说明窗口问题不是偶发现象而是一类容易被忽略的编码风格问题。如果你在代码评审中看到有人把业务逻辑写进TCP接收回调可以直接把这篇文章甩给他。
返回列表