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

news2024/12/26 21:36:22

从 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.coloradmin.cn/o/559284.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

进阶篇丨链路追踪(Tracing)很简单:常见问题排查

作者&#xff1a;涯海 经过前面多篇内容的学习&#xff0c;想必大部分同学都已经熟练掌握分布式链路追踪的基础用法&#xff0c;比如回溯链路请求轨迹&#xff0c;定位耗时瓶颈点&#xff1b;配置核心接口黄金三指标告警&#xff0c;第一时间发现流量异常&#xff1b;大促前梳…

公司刚来的测试,00后真卷,上班还没2年,跳到我们公司起薪20k....

都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。 这不&#xff0c;前段时间我们公司来了个00后&#xff0c;工作都没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了…

neo4j图形数据库

目录 1. neo4j简介1.1 什么是图形数据库1.2 什么是neo4j1.3 neo4j的特性1.4neo4j的优点1.5 neo4j的构建元素 2. 安装部署2.1 环境说明2.2 下载安装包2.3 解压安装包2.4 配置安装jdk环境2.5 配置neoj4全局变量2.6 修改neo4j配置文件2.7 服务基本操作2.8 测试访问 3. 使用DBeaver…

上门洗车小程序软件开发所需要的功能有哪些呢?

相信很多企业及投资者都想开发一款属于自己的小程序系统。那么一款专业好用的上门洗车小程序软件开发所需要的功能有哪些呢&#xff1f; 1. 用户注册与登录。 用户可以通过手机号码或微信账号进行注册和登录。注册后可以查看历史订单、评价技师、参加活动等。 …

浅析EasyCVR视频能力在自然灾害风险预警场景中的应用意义

一、方案背景 我国是自然灾害多发的国家&#xff0c;夏季也是灾害多发季节&#xff0c;山洪、泥石流、洪涝、冰雹、飓风、地震等自然灾害每年都给国家经济带来巨大的损失。建设自然灾害风险预警视频监控系统&#xff0c;实现对自然灾害的可视化预警监测和监管&#xff0c;并提…

分布式事务的21种武器 - 2

在分布式系统中&#xff0c;事务的处理分布在不同组件、服务中&#xff0c;因此分布式事务的ACID保障面临着一些特殊难点。本系列文章介绍了21种分布式事务设计模式&#xff0c;并分析其实现原理和优缺点&#xff0c;在面对具体分布式事务问题时&#xff0c;可以选择合适的模式…

【业务架构】业务驱动的推荐系统相关技术总结

什么是推荐系统 推荐系统是一种基于用户历史行为和属性信息为用户推荐个性化内容的技术。而业务驱动的推荐系统&#xff0c;是指根据业务需求&#xff0c;将推荐系统集成进业务流程中&#xff0c;通过推荐系统提高业务效率、提升用户体验等目的。以下是一些相关实现技术。 用户…

Vue--》Vue3打造可扩展的项目管理系统后台的完整指南(一)

今天开始使用 vue3 ts 搭建一个项目管理的后台&#xff0c;因为文章会将项目的每一个地方代码的书写都会讲解到&#xff0c;所以本项目会分成好几篇文章进行讲解&#xff0c;我会在最后一篇文章中会将项目代码开源到我的GithHub上&#xff0c;大家可以自行去进行下载运行&…

C语言隐藏自己源码成lib静态库的和使用lib静态库的方法

首先从头开始创建一个新项目&#xff1a; 这个sub.c内的文件内容很简单&#xff0c;就写一个减法函数 // 定义一个减法函数&#xff0c;传入两个整数&#xff0c;返回差 int sub(int x, int y) { return x - y; } // 定义一个减法函数&#xff0c;传入两个整数&#xff0…

7. 数据库MySQL

本文介绍了数据库mysql的安装配置过程&#xff0c;以及通过VsCode调用数据库实现一些功能 一、安装 如果之前安装过&#xff0c;或者安装失败。清除MySQL缓存并重新安装&#xff1a;运行以下命令以清除所有MySQL缓存文件&#xff0c;并重新安装它们 sudo apt-get remove --pu…

JAVA基础(各种类)

Object类 1、Object类中有许多方法是用native修饰的&#xff0c;这些方法是本地方法&#xff0c;用C语言实现。 2、Object类的一些方法&#xff1a; clone()方法&#xff1a;需要继承Clonable接口&#xff0c;并覆写clone()&#xff0c;修改为public权限&#xff0c;原来为p…

Halcon中从两组点的对应关系生成仿射矩阵,及思考原理和代码实现

有几个算子有点类似&#xff0c;看了下区别 1.vector_to_rigid( : : Px, Py, Qx, Qy : HomMat2D) 2.vector_to_similarity( : : Px, Py, Qx, Qy : HomMat2D) 3.vector_to_aniso( : : Px, Py, Qx, Qy : HomMat2D) vector_to_rigid 这个是刚性变换&#xff0c; 只有旋转和平移…

参考企业微信日程 通过vue+elementUi编写一个按月统计会议的日程计划组件

这个组件的话 需要三个第三方依赖 npm install --save chinese-lunar-calendar sass sass-loader element-uisass因为我这里 还是习惯写sass样式 毕竟真的方便啊 chinese-lunar-calendar 是一款将日期转为农历的工具 element-ui主要是表格真的方便 在 项目src下的main.js入口…

如何学好人工智能?

“在主流的视频直播教学之外&#xff0c;直接与老师在课件上互动能够更快地让我进入学习状态。” 大家的时间都很宝贵&#xff0c;当我们好不容易下定决心要学习一门新知识时&#xff0c;各种影响学习的噪声会在本不富裕的耐心上大打折扣&#xff0c;万事开头难变成了真理。特别…

[技术分享]Android平台实时音视频录像模块设计之道

实现背景 录像有什么难的&#xff1f;无非就是数据过来&#xff0c;编码保存mp4而已&#xff0c;这可能是好多开发者在做录像模块的时候的思考输出。是的&#xff0c;确实不难&#xff0c;但是做好&#xff0c;或者和其他模块有非常好的逻辑配合&#xff0c;确实不容易。 好多…

考研复试第十六天:合并果子 【哈佛曼树】

前置知识 哈佛曼树&#xff1a;我们先来复习一下啥叫做哈佛曼树 1.背景 我们有下面这样一个字符串需要编码&#xff0c;就是将下面的字符转为二进制。我们采用的方法是前缀编码&#xff0c;用一颗树的叶节点来放字符。 2.前缀编码 编码是咋样的呢&#xff1f;看下面这个例子…

BurpSuite—Project options模块(项目选择)

本文主要BurpSuite—Project options模块(项目选择)介绍的相关内容 关于BurpSuite的安装可以看一下之前这篇文章&#xff1a; http://t.csdn.cn/cavWt 一、简介 Project options主要用来对Project的一些设置。 二、模块说明 Project options主要由五个模块组成: 1.Connect…

Zabbix“专家坐诊”第192期问答汇总

问题一 Q&#xff1a;请问下&#xff0c;客户机snmptrap发告警为啥server web收不到&#xff0c;关键是snmptrap日志已经收到&#xff0c;zabbix server配置以及开启snmptrap1 snmptrapvar log snmptrap snmptrap.log&#xff1f; A&#xff1a;有配置trap的监控项吗&#xff…

Install ELK+Filebeat on Kubernetes Cluster with Helm

目录 Node & Software & Docker Images Lists Prerequisites Install ELK Manually Deploying ElasticSearch Create deployment Expose deployment Deploying Kibana Create deployment Accessing the Kibana UI Deploying Logstash Create ConfigMap and…

快手上市后首次盈利,直播电商业务成造血利器

5月22日盘前&#xff0c;快手业绩还没有发布&#xff0c;股价却先涨为敬&#xff0c;中信证券、彭博、中金公司等多家机构给出超预期业绩的预测。盘后公布的业绩确实超过市场的一致预期&#xff0c;市场在今天也给出正面回应&#xff0c;股价再次上扬&#xff0c;最高点达57.10…