lwIP 细节之五:accept 回调函数是何时调用的

使用 lwIP 协议栈进行 TCP 裸机编程,其本质就是编写协议栈指定的各种回调函数。将你的应用逻辑封装成函数,注册到协议栈,在适当的时候,由协议栈自动调用,所以称为回调

注:除非特别说明,以下内容针对 lwIP 2.0.0 及以上版本。

向协议栈注册回调函数有专门的接口,如下所示:

tcp_err(pcb, errf);							//注册 TCP 接到 RST 标志或发生错误回调函数 errf
tcp_connect(pcb, ipaddr, port, connected);	//注册 TCP 建立连接成功回调函数 connecter
tcp_accept(pcb, accept);					//注册 TCP 处于 LISTEN 状态时,监听到有新的连接接入
tcp_recv(pcb, recv);						//注册 TCP 接收到数据回调函数 recv
tcp_sent(pcb, sent);						//注册 TCP 发送数据成功回调函数 sent
tcp_poll(pcb, poll, interval);				//注册 TCP 周期性执行回调函数 poll

本节讲述 accept 函数。

accept 回调函数

在 TCP 控制块中,函数指针 accept 指向用户实现的函数,当监听到有新的连接接入时,由协议栈调用此函数,通知用户接受了新的连接或者通知用户内存不足。
函数指针 accept 的类型为 tcp_accept_fn ,该类型定义在 tcp.h 中:

/** Function prototype for tcp accept callback functions. Called when a new
 * connection can be accepted on a listening pcb.
 *
 * @param arg Additional argument to pass to the callback function (@see tcp_arg())
 * @param newpcb The new connection pcb
 * @param err An error code if there has been an error accepting.
 *            Only return ERR_ABRT if you have called tcp_abort from within the
 *            callback function!
 */
typedef err_t (*tcp_accept_fn)(void *arg, struct tcp_pcb *newpcb, err_t err);

协议栈通过宏 TCP_EVENT_ACCEPT(lpcb,pcb,arg,err,ret) 调用 lpcb->accept 指向的函数。宏 TCP_EVENT_ACCEPT 定义在 tcp_priv.h 中:

#define TCP_EVENT_ACCEPT(lpcb,pcb,arg,err,ret)                 \
  do {                                                         \
    if((lpcb)->accept != NULL)                                 \
      (ret) = (lpcb)->accept((arg),(pcb),(err));               \
    else (ret) = ERR_ARG;                                      \
  } while (0)

以关键字 TCP_EVENT_ACCEPT 搜索源码,可以搜索到 2 处使用:

TCP_EVENT_ACCEPT(pcb, NULL, pcb->callback_arg, ERR_MEM, err);
TCP_EVENT_ACCEPT(pcb->listener, pcb, pcb->callback_arg, ERR_OK, err);

1 由 tcp_listen_input 函数调用

处于 LISTEN 状态的 TCP 控制块 ,如果收到客户端发送的 SYN 同步标志,表示一个客户端在请求建立连接了。
lwIP 会为这个新连接申请一个 TCP_PCB ,这一过程在 tcp_listen_input 函数中完成的。然而 TCP_PCB 的个数是有限的,如果申请失败,则会调用错误码为 ERR_MEMaccept 回调函数,向用户报告内存分配失败。简化后的代码为:

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
    
    npcb->state = SYN_RCVD;
    // 发送 ACK|SYN 标志
  	return;
}

这里需要注意,申请 TCP_PCB 失败的处理方法,lwIP 2.1.x 版本与 lwIP 1.4.1 不同
再看看 lwIP 1.4.1 的 tcp_listen_input 函数代码(经简化):

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 错误码。而 lwIP 2.1.x 版本 tcp_listen_input 函数不具有返回值(返回类型为 void ),其次,lwIP 2.1.x 版本处理内存错误是通过调用 accept 回调函数来实现的。宏展开代码(简化后)如下所示,注意第二个参数为 NULL ,错误码为 ERR_MEM

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

这个功能最早是由 Simon Goldschmidt 在 2016-03-23 提交的,提交记录为:

	tcp: call accept-callback with ERR_MEM when allocating a pcb fails on
    passive open to inform the application about this error
	ATTENTION: applications have to handle NULL pcb in accept callback!

tcp:在被动打开分配 pcb 失败时,使用 ERR_MEM 参数调用 accept 回调函数,以通知应用程序有关此错误
注意:应用程序必须在 accept 回调中处理 pcb 句柄为 NULL 的情况!

这就告诉我们一个重要的信息:lwIP 2.1.x 版本的 accept 回调函数编写方式与 lwIP 1.4.1 版本不同。lwIP 2.1.x 版本的 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.1.x 版本的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 做了处理,如果检测到 NULL,accpet 回调函数需要提前退出!。

2 由 tcp_process 函数调用

处于 SYN_RCVD 状态的 TCP 控制块,如果接收的正确的 ACK 标志,则调用错误码为 ERR_OKaccept 回调函数,向用户报告接受了新的连接。简化后的代码为:

static err_t
tcp_process(struct tcp_pcb *pcb)
{
  switch (pcb->state) {
    case SYN_RCVD:
      if (flags & TCP_ACK) {
        /* expected ACK number? */
        if (TCP_SEQ_BETWEEN(ackno, pcb->lastack + 1, pcb->snd_nxt)) {
          pcb->state = ESTABLISHED;
		  
          /* Call the accept function. */
          TCP_EVENT_ACCEPT(pcb->listener, pcb, pcb->callback_arg, ERR_OK, err);
          
          if (err != ERR_OK) {
            /* If the accept function returns with an error, we abort the connection. */
            if (err != ERR_ABRT) {
              tcp_abort(pcb);
            }
            return ERR_ABRT;
          }
          tcp_receive(pcb);
        } 
      }
      break;
  }
  return ERR_OK;
}






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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多维时序 | Matlab实现GA-LSTM-Attention遗传算法优化长短期记忆神经网络融合注意力机制多变量时间序列预测

多维时序 | MATLAB实现BWO-CNN-BiGRU-Multihead-Attention多头注意力机制多变量时间序列预测 目录 多维时序 | MATLAB实现BWO-CNN-BiGRU-Multihead-Attention多头注意力机制多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 多维时序 | Matlab实…

C#上位机与欧姆龙PLC的通信01----项目背景

最近,【西门庆】作为项目经理负责一个70万的北京项目,需要在工控系统集成软件开发中和欧 姆龙PLC对接,考虑项目现场情况优先想到了采用FinsTCP通讯协议,接下来就是记录如何一步步实现这些通讯过程的,希望给电气工程师&…

鸿蒙app获取文本控件按钮控件_修改控件名称_按钮触发事件_提示信息显示

鸿蒙app获取文本控件按钮控件_修改控件名称_按钮触发事件_ 点击启动:提示信息显示 package com.example.myapplication.slice;import com.example.myapplication.ResourceTable; import ohos.aafwk.ability.AbilitySlice; import ohos.aafwk.content.Intent; impor…

vivado约束方法8

无交互的逻辑互斥时钟组 逻辑排他性时钟是指在不同源点上定义但共享部分的时钟由于多路复用器或其他组合逻辑,它们的时钟树。时间限制向导识别此类时钟,并建议在它们这样做时直接对其进行时钟组约束除了连接到其共享时钟的逻辑之外,彼此之间…

ADUM1200ARZ数字隔离器:重新定义技术标准

ADUM1200ARZ数字隔离器成为技术进步领域的关键组件。其创新设计和多方面功能重新定义了数字隔离技术的格局,提供了满足不同工业需求的众多功能。让我们通过本文直观的了解ADUM1200ARZ的功能与技术标准。 窄体且符合ROHS:设定新基准 该数字隔离器采用窄体…

【ITRA】赛事方收费标准-2024

一、查看ITRA收费情况 1、赛事方 必须注册赛事方的账户进入ITRA 看到的这个100欧,只是一个起步价格,并不是所有价格 不过这个对于一个赛事方可以cover一年的费用 2、更多费用 想当更大的怨种可以 往下拉满

MySQL数据存储、索引记录

行格式(每行记录) 行格式(每行记录): 以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方式也被称为 行格式 或者 记录格式。 InnoDB 存储引擎4种不同类型的 行格式 ,分别是 Compact 、 Redundant 、Dynamic 和 Compressed 行格式。组…

Github与Gitlab

学习目标 能够使用GitHub创建远程仓库并使用能够安装部署GitLab服务器能够使用GitLab创建仓库并使用掌握CI/CD的概念掌握蓝绿部署, 滚动更新,灰度发布的概念 GitHub是目前最火的开源项目代码托管平台。它是基于web的Git仓库,提供公有仓库和私有仓库,但私…

时序分解 | Matlab实现SSA-ICEEMDAN麻雀算法优化ICEEMDAN时间序列信号分解

时序分解 | Matlab实现SSA-ICEEMDAN麻雀算法优化ICEEMDAN时间序列信号分解 目录 时序分解 | Matlab实现SSA-ICEEMDAN麻雀算法优化ICEEMDAN时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现SSA-ICEEMDAN麻雀算法优化ICEEMDAN时间序列信号分解 可…

【基于Python的新闻文本分类系统设计与实现】

基于Python的新闻文本分类系统设计与实现 摘要:1. 引言2. 数据获取与预处理3. 数据分析与可视化4. 文本分类模型设计与实现5. 结果与讨论6. 总结与展望结尾 摘要: 本文介绍了一种基于Python语言、Flask技术以及贝叶斯算法的新闻文本分类系统的设计与实现…

为了吃鸡苦练狙击,避免坑队友自己造一个狙击游戏!

引言 一文教会你造一个简易的狙击游戏。 说到狙击,相信大家都不陌生,无论是影视作品还是网络游戏,都经常能看到狙击枪的身影,最深刻的是它能够从百里之外,一枪爆头。 本文将介绍如何在Cocos Creator中造一个简易的狙…

基于C/C++的libcurl多协议文件传输库dll二次封装开发使用

libcurl 可能是最便携、最强大和最常用的 这个星球上的网络传输库。官方提供的示例,需要在项目中引用到libcurl-imp.lib才能使用。 这里我改造了下工程,将常用的接口导出到了libcurl.dll中方便直接在后续的工程代码中应用,下面可以看到dll常用…

RNN和LSTM学习笔记-初学者

提示: 目录 前言一、RNN介绍二、LSTM介绍总结 前言 提示: 提示: 一、RNN介绍 RNN是一种短时记忆,而LSTM是长短时记忆网络 二、LSTM介绍 总结

用python+opencv+PySimpleGUI实现了一款视频播放器

目录 前言准备工作主要思路主界面视频读取进度条拖拽 源码 前言 本篇将用python实现一个mp4播放器,可以通过windows资源管理器选择需要播放的mp4视频文件或者图片,然后提供播放条的快进回放,播放和暂停功能: 准备工作 python所…

5G工业网关视频传输应用

随着科技的不断进步,5G网络技术已经成为了当前最热门的话题之一。而其中一个引人注目的领域就是5G视频传输和5G工业网关应用。在传统网络通信中,由于带宽和延迟的限制,视频传输常常受到限制,而工业网关应用也存在着链路不稳定、数…

http正向代理测试,nginx反向代理中转正向代理服务器

有3台服务器如下: 192.168.111.201(反向代理到正向代理服务器) 192.168.111.202(正向代理服务器) 192.168.111.203(目标WEB系统) 防火墙网络策略如图所示: 1、192.168.111.200 只能访问 192.168…

主宰无双H5:WIN学习手工服务端通用视频教程及GM授权物品后台,支持三网H5玩法介绍

标题:主宰无双H5(游戏源码):WIN学习手工服务端通用视频教程及GM授权物品后台,支持三网H5玩法的百科 一、引言 随着互联网的快速发展,H5游戏逐渐成为人们休闲娱乐的重要方式。主宰无双H5游戏源码作为一款深…

深入理解LightGBM

1. LightGBM简介 GBDT (Gradient Boosting Decision Tree) 是机器学习中一个长盛不衰的模型,其主要思想是利用弱分类器(决策树)迭代训练以得到最优模型,该模型具有训练效果好、不易过拟合等优点。GBDT不仅在工业界应用广泛&#…

初识Redis缓存,一文掌握Redis重要知识文集。

🏆作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。 🏆多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。 🎉欢迎 👍点赞✍评论…

QWebEngineView 透明色 设置白屏闪烁的问题 已解决

在项目开发中。由于qt5.15 升级到qt6.5 不知道因为什么,QWebEngineView 加载出现白屏, 网上大神给的方案 五花八门,没有一个解决问题。 代码 旧代码QWebEngineView* pWebEngineView new QWebEngineView();//pWebEngineView->page()->…