lwIP更新记07:TCP 控制块申请失败可以检测到了

news2024/11/20 8:36:47

从 lwIP-2.0.0 开始,TCP 控制块申请失败可以检测到了。

这个更新应用在 TCP 服务器模式中,处于监听状态的 TCP_PCB ,如果收到客户端发送的 SYN 同步标志,表示一个客户端在请求建立连接了。lwIP 会为这个新连接申请一个 TCP_PCB ,这一过程在 tcp_listen_input 函数中完成的。然而 TCP_PCB 的个数是有限的,如果申请失败,对于失败的处理, lwIP-2.0.0 及以上版本与 lwIP-1.4.1不同。

lwIP-1.4.1 失败的毫无声息,而 lwIP-2.0.0 提供了检测手段。

先看 lwIP 1.4.1 的代码(经简化):

static err_t tcp_listen_input(struct tcp_pcb_listen *pcb)
{
  	// 通过一系列检查 没有错误  	
    npcb = tcp_alloc(pcb->prio);	// 申请新的 TCP_PCB 
    if (npcb == NULL) {				// 内存错误处理
      LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: could not allocate PCB\n"));
      return ERR_MEM;
    }
    // 申请成功,初始化新申请的pcb
    // 发送 ACK|SYN 标志
  	return ERR_OK;
}

可以看到, lwIP 1.4.1 版本 tcp_listen_input 函数具有返回值,如果申请 TCP_PCB 失败,则返回 ERR_MEM 错误码。

哎~~,这不是有检测手段吗?怎么说失败的毫无声息?
且压一压疑惑,我们稍微观察下可以知道,这个函数使用关键字 static 修饰,这意味着它仅供模块内部使用,用户层代码是无权使用的。我们看看 lwIP 是怎么使用它的。 tcp_listen_input 函数会被 tcp_input 函数调用,调用代码简化为:

void tcp_input(struct pbuf *p, struct netif *inp)
{
	// 通过一系列检测,报文是合法的
	// 通过一系列操作, 在 tcp_listen_pcbs 中查找到了控制块
	tcp_listen_input(lpcb);		// <------ 这里 
    pbuf_free(p);
    return;
}

嗯~~ 压根没有用到返回值!!
所以对于 lwIP 1.4.1 版本,当 TCP 控制块申请失败时,服务器不会有任何响应,编程人员也根本没有途径得知这一信息,所以说失败的毫无声息。

2014 年 12 月 02,Joel Cunningham 提交了一个 BUG 报告,指出目前 lwIP-1.4.1 版本中,由于 TCP 控制块耗尽而导致不能接收新的连接时,用户层得不到该信息。Joel 的依据是 Open Group Base Specifications (开放基金基本规范)中关于函数 accept 的描述:接收新连过程失败时返回错误消息。

从应用程序的角度来看,如果无法分配 TCP 控制块,这是应该处理的一种错误状态。否则,当因为申请 TCP 控制块失败而连接不上服务器时,开发人员可能需要大量的调试才能发现问题的原因,因为 lwIP-1.4.1 对这种情况没有提供任何检测点。

2015 年 2 月,lwIP 开发人员 Simon Goldschmidt 接受了 Joel 的提议。

2016 年 3 月 24, Simon Goldschmidt 将修改的代码提交到了 lwIP 代码仓库,然后,在 lwIP-2.0.0 版本中,我们看到了这些更改(经简化):

static void tcp_listen_input(struct tcp_pcb_listen *pcb)
{
  	// 通过一系列检查 没有错误  	
    npcb = tcp_alloc(pcb->prio);	// 申请新的 TCP_PCB 
    if (npcb == NULL) {				// 内存错误处理
      LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: could not allocate PCB\n"));
      TCP_EVENT_ACCEPT(pcb, NULL, pcb->callback_arg, ERR_MEM, err);  	//<----- 这里
      return;
    }
    // 申请成功,初始化新申请的pcb
    // 发送 ACK|SYN 标志
  	return;
}

区别很明显,首先 tcp_listen_input 函数不具有返回值(返回类型为 void ),其次,lwIP 处理内存错误是通过宏 TCP_EVENT_ACCEPT(pcb, NULL, pcb->callback_arg, ERR_MEM, err) 调用 accept 回调函数来实现的。宏展开代码(简化后)如下所示,注意第二个参数为 NULL

if(pcb->accept != NULL)
	pcb->accept(pcb->callback_arg, NULL, ERR_MEM);

在分配 TCP 控制块失败时,使用 ERR_MEM 参数调用 accept 回调函数,以通知应用程序有关此错误!

这是一个值得特别重视的更改,因为它改变了 accept 回调函数的处理逻辑:应用程序必须在 accept 回调中处理 pcb 句柄为 NULL 的情况

lwIP-1.4.1 版本的 accept 回调函数可以这么写:

/* 客户端连接时, 回调此函数 */
static err_t telnet_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{
    char * p_link_info = "已连接到Telnet!\r\n";

    tcp_recv(pcb, telnet_recv);
    tcp_err(pcb, NULL);
    pcb->so_options |= SOF_KEEPALIVE;  //增加保活机制
    
    tcp_write(pcb, p_link_info, strlen(p_link_info), TCP_WRITE_FLAG_COPY);
    return ERR_OK;
}

而 lwIP-2.0.0 及以上版本的 accept 回调函数需要这么写:

/* 客户端连接时, 回调此函数 */
static err_t telnet_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{
    char * p_link_info = "已连接到Telnet!\r\n";
    
    if(pcb == NULL)
    {
    	if(err == ERR_MEM)
    		// 处理 TCP 连接个数不足,可选
        return ERR_OK;
    }
    
    tcp_recv(pcb, telnet_recv);
    tcp_err(pcb, NULL);
    pcb->so_options |= SOF_KEEPALIVE;  //增加保活机制
    
    tcp_write(pcb, p_link_info, strlen(p_link_info), TCP_WRITE_FLAG_COPY);
    return ERR_OK;
}

这里对 pcb 句柄是否为 NULL 做了处理,如果检测到 NULLaccpet 回调函数需要提前退出

不这样写会有什么后果?

比如我将 lwIP-1.4.1 升级到了 lwIP 2.1.3,但是我忘记更改 accept 回调函数了,那么绝大部分情况,不会出现什么问题,然后某一天程序突然死机了(假设没有开启看门狗,PS:我的规定是开发期间禁止开启看门狗)。出现死机的原因是那天有很多个客户端连接服务器,出现 TCP 控制块申请失败,协议栈给 accept 回调函数传递了 pcb 为 NULL 的参数,回调函数直接使用该参数调用 tcp_recvtcp_write 等函数,由于此时 pcb 为 NULL,导致内存越界,触发内存 Fault。Fault 在记录错误信息后会进入死循环,表现为程序死机。

使用 C 语言编程时,检查指针是否为空是一个良好的习惯。虽然之前你确切的知道,accept 回调函数的 pcb 参数决不会为 NULL,但这里有一个没有明说的前提,前提是在你使用的当前版本上,这个断言才正确!
虽然我比较反感 NULL 这个概念,但在使用别人提供的代码上,我们不能因为武断而省略对 NULL 的检查,说不定哪天代码逻辑就更改了,就像今天讲的这个例子一样。

另外的话题是,绝不隐藏错误。lwIP-1.4.1 实际上隐藏了 TCP 控制块申请失败的错误信息,这会导致开发人员在出现这个问题时摸不着头脑,哪里出错了?一眼看不出来!

我们很多时候都会不经意间隐藏错误:比如某个参数超出不可能的范围,我们会为它指定一个默认值,然后程序继续执行。这被称为防御性编程。防御性编程不可缺少,但我们有没有想过,这样的代码可能让我放过一个重大错误,参数为什么会超出合理范围?这个问题被毫无声息的掩盖掉了。

我的理念是:有错就死给你看。所以我规定开发期间禁止开启看门狗,因为看门狗可能隐藏错误,它会在很短的时间重启设备,让我们看不到错误已经发生。另外,LOG 记录也能替代禁止看门狗,而我两种方式都要。不用担心出现下发给生产的二进制文件没有开启看门狗的问题,因为下发生产的二进制文件是通过自动化脚本产生的,脚本会编译出开启看门狗的二进制文件。

找错、防错、纠错,然后到自动发现错误,再然后是测试驱动编程,目标是无错,这是我十多年来的开发进化过程。






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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/562479.html

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

相关文章

APP启动页和闪屏的知识点

APP启动页和闪屏的知识点_51CTO博客_app启动屏广告 启动页与闪屏的区别 1.启动页 (launch screen ) 当app被用户打开时,在app启动过程中被用户所看到的过渡页面(或动画)都被我们统称为启动页。 优点&#xff1a; 1.打开一个产品时&#xff0c;需要有一定的时间加载&#xff…

windows sever服务器安装系统新手版

混在learnsite群里已经有几年了吧&#xff0c;但是只有一段时间用了别人现成的一个iso版本还比较老&#xff0c;这次打算自己弄一个&#xff0c;一开始打算linux下弄&#xff0c;结果系统都装完了发现人家说learnsite在linux下有点bug没解决好&#xff0c;只能win下再弄&#x…

爬虫为什么会使用到代理ip?

爬虫使用代理IP的主要目的是为了隐藏自己的真实IP地址&#xff0c;以避免被目标网站封禁或限制访问。如果一个爬虫频繁地向一个网站发送请求&#xff0c;而且每次请求的IP地址都相同&#xff0c;那么这个网站就有可能认为这是一种恶意行为&#xff0c;从而采取封禁或限制访问的…

用 Python 写 3D 游戏

vizard介绍 Vizard是一款虚拟现实开发平台软件&#xff0c;从开发至今已走过十个年头。它基于C/C&#xff0c;运用新近OpenGL拓展模块开发出的高性能图形引擎。当运用Python语言执行开发时&#xff0c;Vizard同时自动将编写的程式转换为字节码抽象层(LAXMI)&#xff0c;进而运行…

如何对项目进度进行跟踪?逐步完善项目计划

我接手了一个小项目&#xff0c;但是无论是我还是领导&#xff0c;都认为这是个简单的项目&#xff0c;最多一月时间就能搞定。但是&#xff0c;随着时间推移&#xff0c;三个月也没有将内容完善。于是我进行了反思总结&#xff0c;我认为存在如下问题&#xff1a; 1、资源协…

vue3在setup中请求数据并使用的几种方式

因为Composition组合式API setup有一点点不同特此举例几种可行的请求数据并使用方式 第一种 Promise 参考代码如下 <template><div>{{ min }}</div> </template><script> import { ref } from vue; import { getUser } from /api/user export d…

应急响应之内存分析方法

应急响应之内存分析方法 1.内存的获取基于内核模式程序的内存获取基于系统崩溃转储的内存获取基于虚拟化快照的内存获取dumpit获取(推荐)2.内存的分析RedlineVolatility1.内存的获取 基于内核模式程序的内存获取 这种获取方法一般需要借助相关的工具来完成。常用的提取工具…

leetcode 2542. Maximum Subsequence Score(最大子串分数)

2个数组&#xff0c;长度一样&#xff0c;从中选k个下标&#xff08;两个数组用同样的下标&#xff09;&#xff0c; 会得到k个nums1中的数字&#xff0c;和k个nums2中的数字。 score k个nums1的数字之和 ✖ min(k个nums2的数字&#xff09;&#xff0c; 找到最大的score。 思…

【Qt】QLocalSocket与QLocalServer问题:接收不到数据、只能收到第一条、数据不完整解决方案【2023.05.24】

简介 Qt很强大,但是Qt的帮助文档、API属实是让我们走不少弯路。QLocalSocket一个很简单的东西,我仅想用来实现一个简单的本地进程通信,就遇到了:客户端循环发送数据,服务端只能接收到一条、接收到数据不完整等奇奇怪怪的现象。 最郁闷的是,网上很多教程说的都是错的😒。…

Web服务器实现|基于阻塞队列线程池的Http服务器|线程控制|Http协议

基于阻塞队列生产者消费者模型线程池的多线程Web服务器 代码地址&#xff1a;WebServer_GitHub_Addr README 摘要 本实验通过C语言&#xff0c;实现了一个基于阻塞队列线程池的多线程Web服务器。该服务器支持通过http协议发送报文&#xff0c;跨主机抓取服务器上特定资源。与…

火山引擎数智平台VeDI助力某办公软件企业营销线索转化提升14%

一条营销线索&#xff0c;从官网后台下载到完成成交&#xff0c;到底需要经历哪些环节&#xff1f; 在企业级市场的销售场景中&#xff0c;营销线索通常是指用户通过相关产品的官方网站或者营销活动界面&#xff0c;主动留下的联系方式&#xff1b;而根据线索价值的不同&#…

2023京东618全民拆快递互动活动玩法规则!

2023京东618全民拆快递&#xff0c;瓜分20亿活动规则&#xff01; 618无门槛红包29号开领&#xff01; ​手机京东搜索&#xff1a;好运红包210&#xff0c;领最高20618&#xff0c;每天可领三次&#xff01; ​手机京东搜索&#xff1a;能省就省50&#xff0c;领最高23888…

深度学习进阶篇-预训练模型[1]:预训练分词Subword、ELMo、Transformer模型原理;结构;技巧以及应用详解

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

算法设计与分析期末总结

前言&#xff1a;基本是为了我自己看的一些我容易忘记的东西&#xff0c;为考试作准备把&#xff0c;主要使后半部分的知识&#xff0c;前半部分请看算法设计与分析阶段考总结 第五章 回溯算法是一种系统地搜索问题的解的方法。某个问题的所有可能解的称为问题的解空间&#xf…

百度工程师移动开发避坑指南——Swift语言篇

作者 | 启明星小组 上一篇我们介绍了移动开发常见的内存泄漏问题&#xff0c;见《百度工程师移动开发避坑指南——内存泄漏篇》。本篇我们将介绍Swift语言部分常见问题。 对于Swift开发者&#xff0c;Swift较于OC一个很大的不同就是引入了可选类型&#xff08;Optional&#…

Install Redis Cluster(1master-2slave) on Kubernetes

目录 Node & Software & Docker Images Lists Prerequisites Architecture Setting up your Redis cluster Creating Namespace Creating StorageClass Creating Persistent volumes Creating ConfigMap Creating StatefulSet Creating Headless Service …

中创|警惕AI骗局,10分钟被骗430万,AI诈骗正在全国爆发!

眼见为实&#xff1f;耳听为真&#xff1f;当心AI诈骗&#xff01; 只需要提供一张带脸的照片&#xff0c;就可以把自己置换成视频、电视剧中的男&#xff08;女&#xff09;主角&#xff0c;拟真度非常高&#xff0c;毫无违和感&#xff0c;这是最近爆火的AI换脸。 然而随着人…

浏览器数据存储方式

浏览器数据存储方式 常用的前端数据存储方法笼统来说有 3 种&#xff1a; local/session storagecookiesindexeddb 3 种方法各有各的优点和使用范围。 local/session storage local/session storage 保存的格式都为键值对&#xff0c;并且用法都是差不多&#xff0c;如下&…

如何选择高品质SPD浪涌保护器

了解了SPD的原理和技术参数和选型方法&#xff0c;但是面对市场上形形色色的SPD品牌&#xff0c;相差无几的参数&#xff0c;该如何去筛选高品质的SPD呢&#xff1f; 作为一个SPD开发人员&#xff0c;谈一下我的看法。前面提到&#xff0c;选择SPD时&#xff0c;有几个重要的参…

探索 Python Web 后端技术的发展之路

导语 Python 在 Web 后端开发领域中有着广泛的应用&#xff0c;它简洁的语法和强大的功能使得开发者们青睐有加。本文将更深入地探讨 Python Web 后端技术的发展趋势和路线&#xff0c;以及相关技术如何影响了 Web 开发的未来。 一、Python Web 框架的演变 Flask&#xff1a…