lwip协议栈实现服务器端主动发送,lwip协议栈源码详解说明 - 全文

1、LWIP的结构

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

LWIP(Light weight internet protocol)的主要模块包括:配置模块、初始化模块、Nef模块、mem(memp)模块、netarp模块、ip模块、udp模块、icmp 模块、igmp模块、dhcp模块、tcp模块、snmp模块等。下面主要对我们需要关心的协议处理进行说明和梳理。

配置模块:

配置模块通过各种宏定义的方式对系统、子模块进行了配置。比如,通过宏,配置了mem管理模块的参数。该配置模块还通过宏,配置了协议栈所支持的协议簇,通过宏定制的方式,决定了支持那些协议。主要的文件是opt.h。

初始化模块:

初始化模块入口的文件为tcpip.c,其初始化入口函数为: void tcpip_init(void (* initfunc)(void *), void *arg)

该入口通过调用lwip_init()函数,初始化了所有的子模块,并启动了协议栈管理进程。同时,该函数还带有回调钩子及其参数。可以在需要的地方进行调用。

协议栈数据分发管理进程负责了输入报文的处理、超时处理、API函数以及回调的处理,原型如下:

staTIc void tcpip_thread(void *arg)

NeTIf模块:

NeTIf模块为协议栈与底层驱动的接口模块,其将底层的一个网口设备描述成协议栈的一个接口设备(net interface)。该模块的主要文件为netif.c。其通过链表的方式描述了系统中的所有网口设备。

Netif的数据结构描述了网口的参数,包括IP地址、MAC地址、link状态、网口号、收发函数等等参数。一个网口设备的数据收发主要通过该结构进行。

Mem(memp)模块:

Mem模块同一管理了协议栈使用的内容缓冲区,并管理pbuf结构以及报文的字段处理。主要的文件包括mem.c、memp.c、pbuf.c。

netarp模块:

netarp模块是处理arp协议的模块,主要源文件为etharp.c。其主要入口函数为: err_t ethernet_input(struct pbuf *p, struct netif *netif)

该入口函数通过判断输入报文p的协议类型来决定是按照arp协议进行处理还是将该报文提交到IP协议。如果报文是arp报文,该接口则调用etharp_arp_input,进行arp请求处理。

如果是ip报文,该接口就调用etharp_ip_input进行arp更新,并调用ip_input接口,将报文提交给ip层。

在该模块中,创建了设备的地址映射arp表,并提供地址映射关系查询接口。同时还提供了arp报文的发送接口。如下

err_t etharp_output(struct netif *netif, struct pbuf *q, struct ip_addr *ipaddr)

该接口需要注册到netif的output字段,ip层在输出报文时,通过该接口获取目标机的MAC地址,组合最终报文后,由该接口调用底层设备的驱动接口发送数据。

在etharp_output接口中,判断报文类型,如果是广播包或者组播包,就调用etharp_send_ip(组装目标mac和源mac)接口,etharp_send_ip调用netif结构中的设备驱动注册的linkoutput钩子函数发送最终报文。如果是单播包,etharp_output接口就调用etharp_query进行ip地址和MAC地址的映射,来获取到目标机的MAC地址。并在etharp_query中调用etharp_send_ip来发送最终组合报文。

ip模块:

ip模块实现了协议的ip层处理,主要文件为ip.c。其主要入口函数为: err_t ip_input(struct pbuf *p, struct netif *inp)

该接口通过判断输入报文的协议类型,将其输入到相应的上层协议模块中去。比如,将udp报文送到udp_input。

该模块另外一个接口是输入函数,原型如下:

err_t ip_output(struct pbuf *p, struct ip_addr *src, struct ip_addr *dest, u8_t ttl, u8_t

tos, u8_t proto)

该接口通过路由表或者传输ip后,调用netif的output字段函数钩子发送报文。

udp模块:

udp模块实现了udp协议层的协议处理,主要文件为udp.c。该模块通过PCB控制块将应用端口跟应用程序做了绑定。在接收到新报文时,分析其对应的PCB,找到对应的处理钩子,进行应用的处理。主要入口函数为:

void udp_input(struct pbuf *p, struct netif *inp) 该模块负责输出的接口如下:

err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)

该模块负责将一个PCB跟一个本地端口进行绑定的接口如下:

err_t udp_bind(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port) 该模块负责将一个PCB跟一个远端端口绑定的接口如下:

err_t udp_connect(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)

igmp模块:

igmp模块负责分组管理。其主要的接口函数如下:

void igmp_input(struct pbuf *p, struct netif *inp, struct ip_addr *dest) 该接口负责IGMP协议报文的处理,比如分析当前报文是请求还是应答。 err_t igmp_joingroup(struct ip_addr *ifaddr, struct ip_addr *groupaddr) 该接口将一个网口加入一个组。

err_t igmp_leavegroup(struct ip_addr *ifaddr, struct ip_addr *groupaddr) 该接口将一个网口从一个组中移出。

dhcp模块:

dhcp模块用于获取设备ip地址的相关信息。其处理入口主要有这么几个:dpch的启动、dpch的接收报文处理以及定时器模块的处理。

主要的接口原型如下:

err_t dhcp_start(struct netif *netif)

该接口用于设备启动dhcp模块,主要是客户端的功能。该模块实现设备dhcp描述结构生成,并将dhcp的端口绑定到udp协议中,以及将本dhcp模块跟远端服务器端口进行绑定。最后启动dhcp申请。

static void dhcp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, u16_t port)

该接口为一个注册接口,用于dhcp报文接收。在start dhcp时,该接口通过dhcp的udp pcb注册到udp协议层。Udp进行报文处理后,根据端口调用该注册接口。该接口中,实现dhcp报文的协议处理。

Void dhcp_fine_tmr() Void dhcp_coarse_tmr()

这两个函数接口实现了dhcp的相关超时处理监控。上面一个用于请求应答超时处理。下面一个用于地址租用情况的到期处理。

从源码分析看,上述的接口在应用lwip的协议栈时,需要重点关注。对于小内存应用的场合,该协议栈的内存管理以及pbuf应用部分需要自行改写。

2、lwip特征

(1)支持多网络接口下的IP转发;

(2)支持ICMP协议;

(3)包括实验性扩展的UDP(用户数据报协议);

(4)包括阻塞控制、RTT 估算、快速恢复和快速转发的TCP(传输控制协议);

(5)提供专门的内部回调接口(Raw API),用于提高应用程序性能;

(6)可选择的Berkeley接口API (在多线程情况下使用) 。

(7)在最新的版本中支持ppp

(8) 新版本中增加了的IP fragment的支持。

(9) 支持DHCP协议,动态分配ip地址.

3、LWIP的协议流程

下面这张图比较清楚的描述了lwip的报文处理流程,呵呵,借用一下。不过,其对netif-》output描述不够。从代码看,该output实际是arp层的输出,最后通过arp层调用netif中的底层输出接口发送报文。

a2cef6d355b4ec9bcb18808fa0cb4b84.png

四、LwIP的API的实现

LwIP的API的实现主要有两部分组成:一部分驻留在用户进程中,一部分驻留在TCP/IP协议栈进程中。这两个部分间通过操作系统模拟层提供的进程通信机制(IPC)进行通信,从而完成用户进程与协议栈间的通信,IPC包括共享内存、消息传递和信号量。通常尽可能多的工作在用户进程内的API部分实现,例如运算量及时间开销大的工作;TCP/IP协议栈进程中的API部分只完成少量的工作,主要是完成进程间的通讯工作。两部分API之间通过共享内存传递数据,对于共享内存区的描述是采用和pbuf类似的结构来实现。综上,可以用简单的一段话来描述这种API实现的机制:API 函数库中处理网络连接的函数驻留在 TCP/IP 进程中。位于应用程序进程中的API函数使用邮箱这种通讯协议向驻留在TCP/IP 进程中的API函数传递消息。这个消息包括需要协议栈执行的操作类型及相关参数。驻留在 TCP/IP 进程中的API函数执行这个操作并通过消息传递向应用程序返回操作结果。

很早以前描述过结构pbuf,它是协议栈内部用来描述数据包的一种方式。这里介绍数据结构netbuf,它是API用来描述数据的一种方式。netbuf是基于pbuf来实现的,其结构如下所示,很明显,它就是包含了几个典型结构的指针。

struct netbuf {

struct pbuf *p, *ptr;

struct ip_addr *addr;

u16_t port;

};

注意,这里的netbuf只是相当于一个数据头,而真正保存数据的还是字段p指向的pbuf链表。字段ptr也是指向该netbuf的pbuf链表,但与p的区别在于:p一直指向pbuf链表中的第一个pbuf结构,而ptr则不然,它可能指向链表中的其他位置,源文档里面把它描述为fragment pionter,与该指针调整密切相关的函数是netbuf_next和netbuf_first。还有两个字段addr和port分别表示发出netbuf数据端的IP地址和端口号,这两个字段实际用处似乎不大,API定义了宏netbuf_fromaddr和netbuf_fromport分别用于返回某个netbuf结构中这两个字段的值。

与netbuf相关的处理函数很多,在源文档15.2节中对每个函数也有了详细的说明,这里说说比较重要的几个。netbuf_new用于分配一个新的netbuf结构,注意这里只是一个头部结构,而真正需要的存储数据区域是在函数netbuf_alloc中分配的,同理函数netbuf_delete用于删除一个netbuf结构,同时函数pbuf_free会被调用,用以删除数据区域的空间。以上这几个函数使用的简单例子如下:

struct netbuf *buf; // 申明指针

buf = netbuf_new(); // 申请新的netbuf

netbuf_alloc(buf, 200); // 为netbuf分配200 字节的空间

。。。。 // 使用buf做相关事情

netbuf_delete(buf); // 删除buf

讲过了API的内存管理,再来讲具体的API函数。与前面的对应,API函数由两部分组成,分别在文件api_lib.c和api_msg.c中。前者包括了用户程序直接调用的API接口函数,后者包括了与协议栈进程通信的API函数,用户程序不可直接调用。这两部分API函数之间通过邮箱传递的消息进行通信。这里又要涉及到两个重要的数据结构:API应用接口部分提供给上层应用以描述一个网络连接的数据结构netconn,以及描述两部分API函数间传递的消息结构的api_msg。

应用程序要使用API函数建立一个连接连接,首先应该建立一个netconn的结构来描述这个连接的各种属性。netconn结构如下,其中去掉了不必要的编译选项和注释。

struct netconn {

enum netconn_type type; // 连接的类型,包括TCP, UDP等

enum netconn_state state; // 连接的状态

union { // 共用体,内核用来描述某个连接

struct ip_pcb *ip;

struct tcp_pcb *tcp;

struct udp_pcb *udp;

struct raw_pcb *raw;

} pcb;

err_t err; // 该连接最近一次发生错误的编码

sys_sem_t op_completed; // 用于两部分API间同步的信号量

sys_mbox_t recvmbox; // 接收数据的邮箱

sys_mbox_t acceptmbox; // 服务器用来接受外部连接的邮箱

int socket; // 该字段只在socket实现中使用

u16_t recv_avail; //

struct api_msg_msg *write_msg; // 对数据不能正常处理时,保存信息

int write_offset; // 同上,表示已经处理数据的多少

netconn_callback callback; // 回调函数,在发生与该netconn相关的事件时可以调用

};

write_msg是api_msg_msg类型的指针,该结构后续讲到;netconn_callback是API定义的一种函数指针类型,其定义为:

typedef void (* netconn_callback)(struct netconn *, enum netconn_evt, u16_t len);

关键字typedef的功能是定义新的类型。这里它用于定义一种netconn_callback的类型,这种类型为指向某种函数的指针,且这种函数有三个类型的输入参数,返回类型为void。在这定义以后就可以像使用int,char一样使用netconn_callback,也即它也表示一种数据类型了。所以上面的callback字段表示一个函数指针,当把某个函数名赋给该字段后,该字段就记录了这个函数的起始地址。

netconn的其他字段后面用到时再详解。接下来看看两部分API函数间传递的消息结构的api_msg:

struct api_msg {

void (* function)(struct api_msg_msg *msg); // 函数指针

struct api_msg_msg msg; // 函数执行时需要的参数

}

字段function是一个函数指针,通常当消息被构造时,该字段被填充为api_msg.c中的某个与协议栈接口的API函数,然后该消息被投递到协议栈进程中进行处理,协议栈进程解析出并执行该函数,这样应用程序与协议栈之间的通信就完成了。字段msg中包含了这个函数执行时需要的所有参数,api_msg_msg是个枚举类型,所以对于不同的function,msg有着不同的结构。api_msg_msg结构如下所示:

struct api_msg_msg {

struct netconn *conn; // 与消息相关的某个连接

union {

struct netbuf *b; // 函数do_send的参数

struct { // 函数do_newconn的参数

u8_t proto;

} n;

struct { // 函数do_bind和do_connect的参数

struct ip_addr *ipaddr;

u16_t port;

} bc;

struct { // 函数do_getaddr的参数

struct ip_addr *ipaddr;

u16_t *port;

u8_t local;

} ad;

struct { // 函数do_write的参数

const void *dataptr;

int len;

u8_t apiflags;

} w;

struct { // 函数do_recv的参数

u16_t len;

} r;

} msg;

};

这个结构体只包含了两个字段:描述连接信息的conn和枚举类型msg。在api_msg_msg中保存conn字段是必须的,因为conn结构中包含了与该连接相关的信箱和信号量等信息,协议栈进程要用这些信息来完成与应用进程间的同步与通信。枚举类型msg的各个成员与调用它的函数密切相关。因此在构建一个消息时,API应首先填充消息的function字段,并针对特定的function种类往api_msg_msg的枚举字段写入参数,函数function被协议栈解析执行时,直接按照自己已定的格式取参数。这意味着,对于构造消息的API函数和解析消息的function函数,它们对参数个数、结构的认识是预先约定的,不能有其他变数。这一点体现出LwIP呆板僵硬的一面。

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

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

相关文章

中希尔排序例题代码_超全面分析十大排序算法

点击上方“零一视界”,选择“星标”公众号资源干货,第一时间送达作者 | 不该相遇在秋天责编 | 程序员小吴前言本文全长 14237 字,配有 70 张图片和动画,和你一起一步步看懂排序算法的运行过程。预计阅读时间 47 分钟,强…

eslint规范_代码规范化之Vue篇

写在前面代码规范化的重要性不在这里展开了。这一篇讲了Vue项目下如何做代码规范化的事情,主要涉及了eslint、prettier、huskylint-staged、onchange、editorConfig这几个角色。另外,虽然配置限于Vue项目,但整个思路也可以作为其他项目代码规…

人类为什么不会被人工智能取代?

来源:人机与认知实验室〔摘要〕文章旨在对人工智能的技术本质进行分析,以回应为什么人类不会被人工智能取代的问题。通过历史分析的方法,以“器官投影说”等技术哲学思想作为分析工具,回顾了人工智能技术的历程。发现在理论上&…

工作组服务器操作系统,工作组服务器操作系统

工作组服务器操作系统 内容精选换一换弹性云服务器操作系统无法正常启动时,或云服务器系统运行正常,但需要对系统进行优化,使其在最优状态下工作时,用户可以使用重装弹性云服务器的操作系统功能。重装操作系统后弹性云服务器IP地址…

270 扩展固态硬盘_游戏人的扩展坞应该是怎样?

为何现在越来越多的游戏爱好者开始使用游戏扩展坞?原因其实很简单,如今游戏笔记本也慢慢往轻薄本发展,拓展坞的加入可让笔记本实现台式机的玩机体验,通过扩展坞可以实现现在许多游戏本不具备的功能,比如外接超高清显示…

几十亿打水漂!世界最大移动通信展MWC因疫情33年来首次取消,多方损失惨重...

资料来源:新智元、AI前线物联网智库 整理发布转载请注明来源和出处北京时间2月13日早晨(今晨),世界移动通讯展(MWC)主办方GSMA正式宣布取消原定于本月24日至27日在西班牙巴塞罗那举办的MWC2020展会。这也是…

eslint vscode 自动格式化_vscode保存代码,自动按照eslint规范格式化代码设置

vscode保存代码,自动按照eslint规范格式化代码设置编辑器代码风格一致,是前端代码规范的一部分。我们现在前端绝大部分都在使用eslint,或者将要把代码改为eslint,那么此时我们怎么方便使用这个规范呢,下面我来介绍一下…

虚拟化服务器类型,虚拟化服务器类型

虚拟化服务器类型 内容精选换一换本次Ceph集群使用TaiShan服务器部署,三个Ceph节点采用三台为TaiShan 200服务器(型号2280);K8s节点两台均采用TaiShan 200服务器(型号2280)。每台服务器配备4个SAS HDD,一块用做OS盘,三块用作存储盘…

异常01

一、异常对象的产生原因和处理方式 二、异常的抛出 1 public class Demo01 {2 /*3 * Throwable:Exception、Error4 * Exception->RuntimeException5 * 异常中的关键字:throw,在方法内部,抛出异常6 * 7 * 方法中声明…

核酸和CT同时用, 听谁的?——兼释一天新增一万多

笔者两天前的文章 新冠病毒检验的可信度和概率预测分析了为什么核酸检验会漏报,为什么要使用CT辅助确诊。今天爆出新闻,新增确诊约15000人,但是其中临床13332人。临床以前是疑似和确诊之间的病人,为什么这么多临床病人都算是确诊&…

access update语句执行_统一VBA中SQL语句执行的方法

要在 Access 中用 VBA 中执行操作查询,在不创建查询对象的前提下,一般主要有3种方法:1. Access本身的方法:DoCmd.RunSQL strSQL2. DAO的方法:CurrentDb.Execute strSQL3. ADO的方法:CurrentProject.Connect…

一个人越聪明他大脑皮层神经元之间的联系就越少

来源:科学杂志 这是波鸿鲁尔大学的神经科学家与ErhanGen博士和Christoph Fraenz博士合作进行的一项研究的结果。这项研究是使用特定的神经影像技术进行的,该技术可在微观结构水平上洞悉大脑的连线。波鸿生物心理学研究小组的团队与阿尔伯克基新墨西哥大学…

git查询当前目录下的文件列表_linux下查找文件,看这篇就够了

linux下文件查找命令用法总结。前言我们经常需要在linux系统中查找一个文件,或需要知道哪些文件包含已知的特有信息,便于快速对比排查、分析问题,那么如何准确高效查找呢?其实在linux下可查找文件的命令不止一个,命令附…

人工智能如何推动神经科技发展?

来源 | Forbes作者 | Margaretta Colangelo编译 | 科技行者神经科技以人类神经系统原理为基础,旨在研究人类大脑这一极为复杂的模型架构。在实际作用方面,神经科技将帮助研究人员了解大脑功能与引发功能障碍的原因,并助力医生治疗各类神经系…

tomcat lifecyclelistener_继续,来聊聊Tomcat的容器

作者:不学无数的程序员链接:https://urlify.cn/jYZFFf在这篇文章《Tomcat是如何运行的?整体架构又是怎样的?》中我们简单介绍了容器的概念,并且说了在容器中所有子容器的父接口是Container。在死磕Tomcat系列(2)——En…

如何与病毒搏斗?这部BBC“史诗级大片”告诉你答案

来源:惠在湖北 我们知道,在目前没有特效药和疫苗的情况下,被治愈的新型冠状病毒肺炎患者,离不开医学的帮助,而治愈的关键,依靠的是人体自身的免疫力。病毒是如何入侵的?免疫力从何而来&#xff…

markdown 行内公式_使用Markdown快速编辑公众号技巧之mdnice

请使用 Chrome 浏览器。请阅读下方文本熟悉工具使用方法,本文可直接拷贝到微信中预览。1 Markdown Nice 简介支持自定义样式的 Markdown 编辑器支持微信公众号、知乎和稀土掘金欢迎扫码回复「排版」加入用户群2 主题https://preview.mdnice.com/themes/欢迎提交主题…

AI人必看!89页全网最全清华知识图谱报告(附PDF)

来源:智东西知识图谱(Knowledge Graph)是人工智能的重要分支技术,它在2012年由谷歌提出,成为建立大规模知识的杀手锏应用,在搜索、自然语言处理、智能助手、电子商务等领域发挥着重要作用。知识图谱与大数据…

AI战“疫”!人工智能在疫情中的重要作用

来源:腾讯新闻网新冠肺炎疫情牵动着全国人民的心!在防控疫情部署落实工作中,上海着力将人工智能等现代信息技术深入应用于疫情态势研判、传播路径分析、精准防控、有效治疗及后续治理等各工作环节。通过提供更加精准有效的科学决策依据&#…

linux 判断指针是否可读_Linux进程间通信——消息队列

概念什么是消息队列?消息队列亦称报文队列,也叫做信箱。是Linux的一种通信机制,这种通信机制传递的数据具有某种结构,而不是简单的字节流。消息队列的本质其实是一个内核提供的链表,内核基于这个链表,实现了…