冬菇小雪 发表于 2024-3-12 13:41:54

实际测试了一下LWIP的Keepalive机制

本帖最后由 冬菇小雪 于 2024-3-12 14:05 编辑

搜索LWIP + KEEPALIVE的经验分享,大多数博主分享的内容基本都是将以下4个宏定义配置好,随后在IP控制块选项中开启SOF_KEEPALIVE就生效了。对于TCP_KEEPIDLE_DEFAULT      TCP_KEEPINTVL_DEFAULT      TCP_KEEPCNT_DEFAULT 这三个宏定义的含义大家也都是按照上图理解的,毕竟LWIP源码中的注释也是这么写的。其中有一个博主分享的结果让我非常疑惑,他按照上图所示定义了KEEPALIVE的发送间隔,超时次数,并附上了Wireshark的抓包结果可以看到第7.312秒时TCP的3次握手完成,由于之后没有数据包的收发,因此激活了Keepalive,按照我们对TCP_KEEPIDLE_DEFAULT的理解,应该在第7.312秒的2秒后发送第一帧Keepalive请求帧,如果后续还是没有数据收发,则后续会按照1秒的间隔时间持续发送Keepalive帧。实际抓包结果中,第一帧keepalive出现在第9.559秒,勉强是符合TCP_KEEPIDLE_DEFAULT的定义,然而keepalive的发送间隔很明显是2.5秒一次,完全不符合TCP_KEEPINTVL_DEFAULT定义的1秒一次。
一、关于如何开启Keepalive
一定要定义LWIP_TCP_KEEPALIVE为1吗?之所以能问出这个问题是我在实际使用的时候因疏忽遗漏了修改宏定义LWIP_TCP_KEEPALIVE,但抓包过程中还是能看到Keepalive帧(发送间隔为2.5秒)。我是采用如下方式开启keepalive功能的其中关于SO_KEEPALIVE的内容如下:实际生效的是ip_set_option和ip_reset_option,这是个两个宏定义可以看出实际上就是对IP控制块的选项进行直接修改,与网上经验分享没有本质区别。
重新查看一下对LWIP对宏定义LWIP_TCP_KEEPALIVE的解释实际上定义LWIP_TCP_KEEPALIVE为1仅仅是使能了后续对TCP_KEEPIDLE,TCP_KEEPINTVL和TCP_KEEPCNT这三个选项的处理,与能否开启Keepalive功能是无关的,随便切一处引用位置如下图所示,如果没有定义LWIP_TCP_KEEPALIVE为1,则keep_intvl和keep_cnt的初始化不再被编译,而TCP_KEEPIDLE_DEFAULT仍然是生效的。
二、发送一帧Keepalive的条件
找到void tcp_slowtmr(void)的函数原型,可以看到对该函数的说明这个句柄每500ms被调用一次,做的是对数据重传和处于等待状态定时器的处理工作。往下找我们可以看到tcp_ticks这个计数值,这是LWIP的整个TCP模块公用的计数。
于是我们首先明确,对于TCP的自动重传等机制,其最小周期是500ms,因为这些机制全部是通过tcp_ticks的值触发的。
我们继续往下翻,看到keepalive的部分其中红框内就是keepalive机制开启的条件,可以看出只要IP控制块的SOF_KEEPALIVE被置位,并且当前连接处于建立或等待关闭状态,就可以发送keepalive帧。黄框内是keepalive判断超时的条件,此处先跳过蓝框内是发送一帧keepalive帧的时间间隔条件,其中tcp_ticks的含义上文已经解释,pcb->tmr的值会在接收到对方的数据后被覆写为tcp_ticks,我们可以将其理解为接收到上一帧有效数据回复的时刻;pcb->keep_idle是我们定义的TCP_KEEPIDLE_DEFAULT;pcb->keep_cnt_sent是已发送的keepalive帧的数目,该值会在发送完成一帧keepalive后加一(见绿框),并在接收到ACK后归零;TCP_KEEP_INTVL(pcb)的原型为pcb->keep_intvl也就是我们定义的TCP_KEEPINTVL_DEFAULT;TCP_TMR_INTERVAL         250TCP_SLOW_INTERVAL      2*TCP_TMR_INTERVAL= 500
因此,我们可以总结出keepalive的发送条件为:距离上一帧有效数据的Tick数         大于            (IDLE + cnt_sent * INTVL) / 500以定义   TCP_KEEPIDLE_DEFAULT            2000               TCP_KEEPINTVL_DEFAULT         1000计算,则               Tick数    > (2000 + cnt_sent * 1000) / 500即发送一次keepalive
在网络正常的情况下,keepalive总是能接收到ACK的,所以cnt_sent为0(LWIP为我们写好了这一部分的的调试信息输出,有需要可以开启调试信息输出查看),那么keepalive的发送间隔就仅取决于TCP_KEEPIDLE_DEFAULT的定义了,另外由于判定条件是大于,同样按照上述条件计算的话,不等号右边是4,则Tick数累计为5时触发一次keepalive,间隔时间正好是2.5秒,符合本文开头例程中的测试结果。
三、通过setsockopt为每个TCP连接独立设置keepalive参数。使用TCP_KEEPIDLE_DEFAULT或TCP_KEEPINTVL_DEFAULT只能统一配置所有TCP连接的keepalive参数,如果需要单独对某一个TCP连接的keepalive进行修改,可以使用LWIP提供的setsockopt,这个API在上文中已经出现过一次,通过传入参数可以指定我们需要修改的设置项。
针对keepalive的参数如下,需要注意如果没有定义LWIP_TCP_KEEPALIVE为1,则以下指令是不会生效的。另外,与TCP_KEEPIDLE_DEFAULT或TCP_KEEPINTVL_DEFAULT不同,此处使用的单位是秒,在setsockopt函数内部会将传入参数的值乘以1000,进行单位换算我们可以实际测试一下,保持以下宏定义的值不变我们单独修改这个连接的IDLE时间为1秒,INTVL为100秒,配置过程如下,根据我们上文中的计算结果,理论上这个连接的Keepalive间隔应该是3,对应1.5秒:实际抓包结果间隔时间1.5秒,确认无误,这也说明数据收发正常时,Keepalive的发送间隔时间并不是INTVL指定的100秒。但通过API配置的弊端在于其单位被固定为秒,结合上文我们知道Keepalive的发送间隔会被固定为X.5秒一次,如果实在需要整数间隔,只能通过宏定义实现了如图所示,按照上图配置实现2秒一次的Keepalive
至于KEEPALIVE_INTVL,从源码来看这个参数的含义显然并不是直白的两个Keepalive帧的间隔时间,而是解释为如果上一帧Keepalive没有接收到ACK,则发送下一帧Keepalive时额外的等待时间更为确切。




eric2013 发表于 2024-3-13 09:13:33

谢谢楼主分享,分析的很详细。{:8:}

冬菇小雪 发表于 2024-3-13 11:50:59

eric2013 发表于 2024-3-13 09:13
谢谢楼主分享,分析的很详细。

感谢硬汉哥回复:P
页: [1]
查看完整版本: 实际测试了一下LWIP的Keepalive机制