lwIP更新记06:申请 TCP 控制块(tcp_alloc)

news/2024/11/25 14:41:18/

从 lwIP-2.0.0 开始,申请 tcp_pcb 控制块的逻辑发生了变化。
每个 tcp 连接都必须有一个 PCB 控制块 ,使用函数 tcp_new() 申请 PCB 控制块。tcp_new 函数代码如下所示:

/*** Creates a new TCP protocol control block but doesn't place it on any of the TCP PCB lists.* The pcb is not put on any list until binding using tcp_bind().** @internal: Maybe there should be a idle TCP PCB list where these* PCBs are put on. Port reservation using tcp_bind() is implemented but* allocated pcbs that are not bound can't be killed automatically if wanting* to allocate a pcb with higher prio (@see tcp_kill_prio())** @return a new tcp_pcb that initially is in state CLOSED*/
struct tcp_pcb *tcp_new(void)
{return tcp_alloc(TCP_PRIO_NORMAL);
}

从代码可以看出,实际申请 tcp_pcb 控制块的是 tcp_alloc 函数。这个函数设计原则是尽一切可能返回一个有效的 tcp_pcb 控制块。因此,当 TCP 控制块数量不足时,该函数可能 “杀死”(kill)正在使用的连接,以释放 tcp_pcb 控制块!
从 lwIP-2.0.0 开始,当 TCP 控制块数量不足时,函数 tcp_alloc “杀死”(kill)正在使用的连接的逻辑发生了变化。

这源于一次 BUG 反馈。

2013 年 7 月 25,lwIP-1.4.1 用户 Roman Trunov 反馈了一个 BUG :他的设备是一个 TCP 服务器,设置了 10 个 PCB 控制块,这意味着可以同时为 10 个客户端提供服务。但在测试过程中他发现,有时会连接不上服务器,一个客户端都连接不上,直到过了 2 分钟后才能恢复连接。

一番调试后,他发现不能连接服务器时,10 个 PCB 控制块都处于 LAST_ACK 状态,新的连接进来后,由于申请不到 PCB 控制块,所以连接不上。等到 2 分钟后, 处于 LAST_ACK 状态的连接超时,协议栈自动释放控制块内存后,才可以连接服务器。

LAST_ACK 状态,这是 TCP 状态机中的一个状态,处于断开 TCP 连接 4 次握手中的最后一步。在这个状态中,只要服务器收到客户端发来的 ACK 标志,服务器就能完成正常的连接关闭步骤,从而释放 PCB 空间。但是 Roman Trunov 的防火墙配置将客户端发来的最后一次 ACK 给拦截了,导致服务器处于 LAST_ACK 状态不得转变,直到超时事件发生。虽然这次是不合理的配置引起的,但现实世界中是有可能出现这个现象的,因为网络数据在现实世界中是可能丢失的。 Roman Trunov 指出,应该实施更积极的 PCB 控制块分配策略,就像处理 TIME_WAIT 状态那样。

2015 年 2 月 18 日,lwIP 开发人员 Simon Goldschmidt 接受了他的提议,认为这是一个 BUG,然后进行了修复,这就是我们在 lwIP-2.0.0 中看到的代码。

两个不同版本,函数 tcp_alloc 的逻辑是什么样的?它们又有什么不同?

lwIP-1.4.1 代码(有简化) :

/*** Allocate a new tcp_pcb structure.** @param prio priority for the new pcb* @return a new tcp_pcb that initially is in state CLOSED*/
struct tcp_pcb *tcp_alloc(u8_t prio)
{struct tcp_pcb *pcb;pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);if (pcb == NULL) {/* Try killing oldest connection in TIME-WAIT. */tcp_kill_timewait();/* Try to allocate a tcp_pcb again. */pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);if (pcb == NULL) {/* Try killing active connections with lower priority than the new one. */tcp_kill_prio(prio);/* Try to allocate a tcp_pcb again. */pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);}}if (pcb != NULL) {// 初始化 pcb 代码}return pcb;
}
  1. 先调用 tcp_kill_timewait 函数,试图找到 TIME_WAIT 状态下生存时间最长的连接,如果找到符合条件的控制块 pcb ,则调用 tcp_abort(pcb) 函数 “杀” 掉这个连接,这会发送 RST 标志,以便通知远端释放连接;
  2. 如果步骤 1 失败了,则调用 tcp_kill_prio(prio) 函数,试图找到小于等于指定优先级(prio)的最低优先级且生存时间最长的有效(active)连接!如果找到符合条件的控制块 pcb ,则调用 tcp_abort(pcb) 函数 “杀” 掉这个连接,这会发送 RST 标志。

lwIP-2.0.0 代码(有简化):

/*** Allocate a new tcp_pcb structure.** @param prio priority for the new pcb* @return a new tcp_pcb that initially is in state CLOSED*/
struct tcp_pcb *tcp_alloc(u8_t prio)
{struct tcp_pcb *pcb;pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);if (pcb == NULL) {/* Try to send FIN for all pcbs stuck in TF_CLOSEPEND first */tcp_handle_closepend();/* Try killing oldest connection in TIME-WAIT. */tcp_kill_timewait();/* Try to allocate a tcp_pcb again. */pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);if (pcb == NULL) {/* Try killing oldest connection in LAST-ACK (these wouldn't go to TIME-WAIT). */tcp_kill_state(LAST_ACK);/* Try to allocate a tcp_pcb again. */pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);if (pcb == NULL) {/* Try killing oldest connection in CLOSING. */tcp_kill_state(CLOSING);/* Try to allocate a tcp_pcb again. */pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);if (pcb == NULL) {/* Try killing oldest active connection with lower priority than the new one. */tcp_kill_prio(prio);/* Try to allocate a tcp_pcb again. */pcb = (struct tcp_pcb *)memp_malloc(MEMP_TCP_PCB);}}}}if (pcb != NULL) {// 初始化 pcb 代码}return pcb;
}
  1. 先调用 tcp_kill_timewait 函数,试图找到 TIME_WAIT 状态下生存时间最长的连接,如果找到符合条件的控制块 pcb ,则调用 tcp_abort(pcb) 函数 “杀” 掉这个连接,这会发送 RST 标志,以便通知远端释放连接;这一步与 lwIP-1.4.1 相同。
  2. 如果第 1 步失败了,则调用 tcp_kill_state 函数,试图找到 LAST_ACKCLOSING 状态下生存时间最长的连接,如果找到符合条件的控制块 pcb ,则调用 tcp_abandon(pcb, 0) 函数 “杀” 掉这个连接,注意这个函数并不会发送 RST 标志,处于这两种状态的连接都是等到对方发送的 ACK 就会结束连接,不会有数据丢失;这一步与 lwIP-1.4.1 不同。
  3. 如果第 2 步也失败了,则调用 tcp_kill_prio(prio) 函数,试图找到小于指定优先级(prio)的最低优先级且生存时间最长的有效(active)连接!如果找到符合条件的控制块 pcb ,则调用 tcp_abort(pcb) 函数 “杀” 掉这个连接,这会发送 RST 标志。要特别注意这一步与 lwIP-1.4.1 也不同。lwIP-1.4.1 会杀死 小于等于指定优先级的连接,而 lwIP-2.0.0 只会杀死 小于指定优先级的连接。

两个版本的不同之处到这里已经讲完了,但是从中得出的 TCP 编程注意事项需要再强调一下,那就是:当 TCP 控制块数量不足时,新的连接可能 “杀死”(kill)正在使用的连接

“杀死”(kill)正在使用的连接,意味着在无声无息之间,我们正常通讯的连接可能会被意外中止掉。
比如,我有一个服务器提供重要的数据通讯功能,还有一个 Telnet 服务器提供一些不重要的状态查询,当服务器正在提供数据通讯时,多个 Telnet 客户端进行了连接,那么正在数据通讯的连接有可能会被中止掉!这是不能允许的。

有什么解决方案?

有的,lwIP 协议栈已经提供避免这种情况的机制:每个 tcp 连接 都是有优先级的。tcp 连接优先级共有 127 级,lwIP 定义了其中 3 个:

#define TCP_PRIO_MIN    1
#define TCP_PRIO_NORMAL 64
#define TCP_PRIO_MAX    127

其中,使用函数 tcp_new() 新建 PCB 控制块时,默认的优先级是 TCP_PRIO_NORMAL, 如果你的 tcp 连接比较重要,需要在连接回调函数(accept)中,修改连接的优先级:

static err_t xxxx_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{if(pcb == NULL)return ERR_OK;tcp_setprio (pcb,TCP_PRIO_MAX);				// <--- 这里,修改连接优先级tcp_recv(pcb, xxxx_recv);pcb->so_options |= SOF_KEEPALIVE; return(ERR_OK);
}

上面反馈 BUG 的 Roman Trunov 也用类似方法修改了服务器连接的优先级,新的优先级高于 TCP_PRIO_NORMAL ,所以当 10 个连接都处于 LAST_ACK 状态时,再申请 PCB 时,内核不能中止处于更高优先级的 LAST_ACK 状态连接,PCB 数量不够,连接失败。






读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃‘▽’〃)
千金难买知识,但可以买好多奶粉


http://www.ppmy.cn/news/145466.html

相关文章

shims.tsx.d.ts 文件在 Vue-Typescript 项目中有什么作用?

本文介绍了shims.tsx.d.ts 文件在 Vue-Typescript 项目中有什么作用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 使用 typescript 创建 Vue 项目时,包含两个声明文件:shims-vue.d.ts 和 shims.tsx.d.ts. When creatin…

基于ATMEGA16单片机的空调控制器

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/87853101 源码获取 主要内容&#xff1a; 本系统采用AVR单片机实现汽车空调的自动控制&#xff08;双位控制&#xff09;&#xff0c;具有电路结构简单、分立元件…

Jetson 硬件 安装SSD固态作为启动盘以及安装CUDA等

Jetson硬件的自带闪存一般较小&#xff0c;只能安装jetpack等基本的环境&#xff0c;所以需要额外增加SSD固态或SD卡作为存储空间&#xff0c;很明显SSD的读取速度远远大于SD卡&#xff0c;所以为更好发挥出Jetson 的计算性能&#xff0c;我们选择使用SSD固态作为存储 1. 安装…

子凡的歌单

子凡&#x1f3a4;的歌单 带** 整首**俩字的不用看歌词 这列可以是序号歌曲名歌手决不投降&#xff08;整首&#xff09;杨和苏吹又生&#xff08;开头一段中文verse&#xff09;杨和苏都走了 &#xff08;Live&#xff09;杨和苏麒麟&#xff08;整首&#xff09;早安&#xf…

冰箱

尺寸说明

DIY冰箱温控器

花了约一个星期时间&#xff0c;用单片机做了一个电子温控器&#xff0c;有点丑&#xff0c;但很好用。 包括三部分&#xff1a;电源、单片机、可控硅输出执行。 主要功能&#xff1a;开机5分钟保护、自动学习调整温度上下限&#xff0c;当然也可手调&#xff0c;还能累计、显…

家电清洗服务预约小程序

人们生活质量的提高&#xff0c;离不开智能的家居电器&#xff0c;用的时间久了&#xff0c;特别是像冰箱&#xff0c;油烟机&#xff0c;以及微波炉等都需要清洗&#xff0c;自己清洗又不干净&#xff0c; 这时候就要找家电清洗人员来进行清洗&#xff0c;在以前的话&#xff…

日立双十一买R-ZXC750KC冰箱送R-BF330JC冰箱?

2022年双十一即将来临&#xff0c;电商又将进入狂欢季&#xff0c;日立作为家电行业的重点品牌之一&#xff0c;抢先抛出双十一大促玩法&#xff0c;《1000L冰箱”购“不够》的主题迅速吸引到了冰箱消费者的购买视线&#xff0c;1000升的大容量到底怎么买&#xff1f;日立本次双…