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;}// 申请成功,初始化新申请的pcbnpcb->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 onpassive open to inform the application about this errorATTENTION: 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.mzph.cn/news/226189.shtml

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

相关文章

k8s-deployment initcontainers提前监测svc可用

背景:项目deploy需要依赖svc启动,但是k8s启动deploy是无序的,就需要人为控制 Init Container 应用场景 讲解了 Init Container 的使用,接下来介绍 Init Container 有哪些应用场景。 在很多的应用场景中,应用在启动之…

车载以太网-DoIP

文章目录 车载以太网DoIP协议(Diagnostics over Internet Protocol)DoIP协议的报文格式DoIP报文类型DoIP协议的完整流程车载以太网DoIP协议(Diagnostics over Internet Protocol) 车载以太网DoIP协议(Diagnostics over Internet Protocol)是一种用于车辆诊断的网络协议,…

Python中类与对象

一、前言 面向对象是一种设计哲学和方法论。它并非必需,不使用面向对象也可以编程。但有了面向对象,可以把程序组织得更有条理,让设计过程更加愉悦和轻松。面向对象:再解决问题的时候关注的是解决问题所需要的对象。 Python是一种…

2024GoCN线下活动全面启动,赠送深圳MeetUp门票

2024年GoCN社区将全面启动一系列令人期待的线下活动---不仅将在北京、上海、深圳、武汉、成都、杭州、广州、西安等地举办 meetup,还将在北京和上海举办 GopherChina 大会。 2024议题征集通道已开启,欢迎各位有实战经验、独特观点的Gopher前来分享~ 2024…

@RestController

1、RestController说明 RestController是 Spring Framework 4.0 引入的一个注解,用于将一个类标记为 Rest 控制器。它是Controller和ResponseBody注解的组合,使得使用该注解的类在处理 HTTP 请求时更加方便和简洁。 RestController注解由以下几个部分组…

Anaconda中使用Jupyter出现’No module named ‘pymysql‘问题解决

问题截图: 解决办法: 一.找到Anaconda所在文件夹,文件夹处输入 cmd 进入命令控制 二. 在打开的cmd中输入‘conda install pymysql’ 三、输入y 安装完成~ 测试: import pandas as pd from sqlalchemy import create_engine …

EAM系统在地铁设备管理中的应用

在现代城市的交通系统中,地铁作为一种高效、快速、可靠的公共交通工具,扮演着至关重要的角色。为了确保地铁系统的正常运行和可靠性,地铁管理部门需要有效地管理大量的设备和设施。在这个过程中,企业资产管理(EAM&…

Python个人代码随笔(观看无益,请跳过)

异常抛错:一般来说,在程序中,遇到异常时,会从这一层逐层往外抛错,一直抛到最外层,由最外层把错误显示在用户终端。 try:raise ValueError("A value error...") except ValueError:print("V…

MVC框架和Spring MVC的基本流程

MVC(Model-View-Controller)是一种设计模式,用于将应用程序的逻辑分离为三个不同的组件:模型(Model)、视图(View)和控制器(Controller)。MVC框架的原理是基于…

linux源码包管理

8.2 源码包管理 source code需要经过GC,C编译环境编译才能运行 ​ 可以设定个人设置,但配置复杂 ​ 软件包示例: nginx-1.8.1.tar.gz -----------------------------------------------------------------------------------------------------------------------…

企业与员工

相信大家最近都关注到了董宇辉这件事,在看“热闹”的同时,我们也要关注到事情的本质。最后回到根本,归根到底就是企业与员工。 在此之前,我们先一起回顾一下历史,当然,以下内容都是我根据老一辈的分享以及历史资料做出来的总结: 我们新中国成立,经历了很多事情,特别是…

Event Driven设计模式

EDA(Event-Driven Architecture)是一种实现组件之间松耦合、易扩展的架构方式。一个最简单的EDA设计需要包含如下几个组件: Events:需要被处理的数据。一个Event至少包含两个属性,类型和数据,类型决定了Eve…

知识付费小程序开发:构建个性化学习平台的技术实践

随着在线学习和知识付费的兴起,开发一款知识付费小程序成为了创新的热点之一。本文将通过使用Node.js、Express和MongoDB为例,演示如何构建一个基础的知识付费小程序后端,并实现用户认证和知识内容管理。 1. 初始化项目 首先,确…

CSS复合选择器(在基础选择器上元素选择的方式不同)

后代选择器: ------------ 此情况下,红色的可以划去 子(元素)选择器: 并集选择器: 伪类选择器: 如放上字符会变色。 链接伪类选择器: foucus伪类选择器:

matplotlib如何在label中加入字符并换行【已解决】

最近在跑一个超参数的实验,但是发现x轴的刻度就很丑 显然,lr和theta在一行显得很冗余 这个是此时的label x_labels [$\t{lr0.05}\ \tθ10}$, 40, 60] 正常加换行符即可,但是要加上$$ x_labels [$\t{lr0.05}\ $\n$ \tθ10}$, 40, 60] 大…

听力健康“吃”出来

大多数的研究报告都指出,听力下降的最常见原因是年龄和噪音暴露。然而,近年来越来越多的文章开始探讨其他因素对听力的影响。食物不仅是维持人类基本生存的必需品,随着营养学的进步,人们也逐渐认识到食物中的营养与保持健康之间存…

zabbix6入门到精通(3) 预处理

zabbix6入门到精通(3) 预处理 配置 — 主机 文件系统主项目 vfs.fs.get 测试一下 添加预处理 $[?(.fsname ‘/’)] $[0].inodes.pfree JSONPath参照: https://www.zabbix.com/documentation/6.0/zh/manual/config/items/preprocessi…

多线程中的单例模式

单线程中的单例模式 在单线程中,实现一个单例模式是简单的: class Singleton { public:static Singleton* get_instance() {if (instance_ nullptr) {instance_ new Singleton();}return instance_;}private:Singleton() default;static Singleton*…

【halcon深度学习】目标检测的数据准备过程中的一个库函数determine_dl_model_detection_param

determine_dl_model_detection_param “determine_dl_model_detection_param” 直译为 “确定深度学习模型检测参数”。 这个过程会自动针对给定数据集估算模型的某些高级参数,强烈建议使用这一过程来优化训练和推断性能。 过程签名 determine_dl_model_detection…

Codeforces Round 914 (Div. 2) A~E

A.Forked!(思维) 题意: 给出骑士的跳跃能力 ( x , y ) (x, y) (x,y) 以及国王和皇后的位置,问有多少个位置可以让骑士可以直接攻击到国王和皇后。 分析: 棋盘非常大 ( 1 0 8 1 0 8 ) (10^{8} \times 10^{8}) (1…