当前位置:首页>科技 >内容

lwip协议栈详解_如何更好的理解LWIP协议

2024-06-02 11:39:56科技漂亮的斑马

LWIP协议和网络分层LwIP(轻量级IP)是一个轻量级的开源TCP IP协议栈,可以在有限的RAM和ROM条件下实现一个完整的TCP IP协议栈。此外,LwIP可

lwip协议栈详解_如何更好的理解LWIP协议

LWIP协议和网络分层

LwIP(轻量级IP)是一个轻量级的开源TCP/IP协议栈,可以在有限的RAM和ROM条件下实现一个完整的TCP/IP协议栈。此外,LwIP可以移植到操作系统上,在没有操作系统的情况下独立运行。

TCP/IP协议栈的模型结构如下图所示。由于TCP/IP协议栈出现较早,并不是按照传统的7层OSI网络模型设计的,它只分为4层,即网络接口层、网络层、传输层和应用层。LwIP协议栈的网络模型类似。

网络接口层主要通过双绞线、光纤和无线在网络上发送和接收数据帧。网络接口层将网络层的数据组装成自己特定的帧发送,同时也接收数据帧进行解析,并将解析后的数据发送给网络层。

网络层负责选择主机间通信过程中数据包的传输路径,并会在收到传入数据报时检查其有效性,并提交给上层。

传输层主要提供应用程序之间的通信服务,它将系统地管理两端数据之间的交互。

应用层只是使用传输层提供的功能将自己的数据发送给另一方。

LWIP协议栈初始化

在数据传输之前,首先要进行一系列的初始化操作。本文以i.MX RT1060 SDK中的demo ' evkmimxrt 1060 _ lwip _ UDP pecho _ BM '为例,代码可以通过MCUXpresso IDE导入。

netif_add函数用于挂载网络接口,完成网络通信前的大部分初始化工作,包括PHY芯片的初始化,i.MX RT1060上ENET外设的初始化以及通信中用到的一些相关数据结构。

PHY芯片的初始化在ethernetif_phy_init中完成,包括MDIO初始化、网口自动协商、网口连接等操作。

ENET外设的初始化在ENET_SetMacController中完成,ENET外设的一些配置在这里执行,比如设置接口速率和接口类型(mii,Rmii)。

PHY初始化函数和ENET初始化函数都是在ethernetif0_init函数中调用的,这个函数作为参数传入并在netif_add中调用,所以netif_add不仅完成了网络接口的挂载,还完成了一系列与接口相关的初始化工作。

另外,在初始化网络接口的同时,一系列的数据结构也被初始化。以下是网络通信中使用的一些结构。

Enet_rx_bd_struct_t,该结构一般用于定义缓冲区描述符,网络接口层接收的数据一般封装在缓冲区描述符中。结构定义如下图所示,其中length表示缓冲区描述符中数据的长度,一些与缓冲区描述符相关的状态信息存储在control中,支持增强缓冲区描述符。

Enet_rx_bd_ring_t结构,如下图所示,每个环由缓冲区描述符组成。环结构中的rxBdBase成员是第一个缓冲区描述符的地址,rxGenIdx是指当前缓冲区描述符的序列号,rxRingLen是指这个环中有多少个缓冲区描述符。

Pbuf结构,pbuf结构用于描述lwip协议栈中的数据包。它以链表的形式存在,pbuf中会有指针指向下一个pubf。

因为万一用到了UDP通信,所以需要一些UDP相关的初始化设置。比如调用udp_bind函数绑定udp控制块中的local_port、local_ip等参数,调用udp_recv绑定udp控制块上的一些回调函数等。至于UDP控制块是什么,后面会介绍。

LWIP网络接口层

网络接口层数据接收

在udpecho demo中,通过轮询实现数据接收,使用raw/callback api。除了这个api,lwip还提供了socket api,但是需要操作系统的支持。

在while循环中,首先会调用ethernetif_input函数,其中会调用ethernetif_linkinput函数,在ethernetif_linkinput中会调用ENET_GetRxFrame和ethernetif_rx_frame_to_pbufs函数。

在ENET_GetRxFrame函数中,网络接口接收到的数据会被传输到RxFrame,然后ethernetif_rx_frame_to_pbufs函数会将RxFrame中的数据传输到pbufs,然后会调用ethernet_input函数。在lwip源代码中的ethernet.c文件中定义,主要用于网络层在没有操作系统的情况下,对接收到的数据帧进行处理,然后提交给上层,对不同的数据包进行不同的处理。如果是ARP包,调用etharp_input函数;如果是ip包,则调用ip4_input函数,通过这些函数将包提交给IP层进行处理。

网络接口层数据传输

在网络层发送数据时,会调用网络接口层的ethernet_output函数,在ethernet_output函数中会调用ethernetif_linkoutput函数。当大数据需要用多个pbuf存储时,pbuf是以链表的形式存在的,所以需要对这些链表中的数据进行合并,如下图所示。

操作完成后,通过ENET_SendFrame函数发送数据;最后,数据将通过网络接口传输。

LWIP网络层

互联网协议

IP协议是一个经典的网络层协议,IP协议,也称为互联网协议。IP协议工作在IP层,是整个TCP/IP协议栈的核心协议。上层协议依赖于IP协议提供的服务,负责将数据报从源主机发送到目标主机,并使用IP地址作为唯一的标识码。简单来说就是不同主机的IP地址不一样。在发送数据报的过程中,IP协议也可能会对数据报进行分片,同时在接收数据报时可能需要重新安装分片的数据报。

IP协议是一种无连接且不可靠的数据报传送协议。协议本身不提供任何错误检查和恢复机制,但需要传输层协议来完成这些功能。

IP地址;网络地址

在TCP/IP设计过程中,设计者给每台主机分配一个32位的IP地址,只有拥有有效IP地址的主机才能访问互联网与其他主机进行通信。

ip数据报

IP数据包通常由IP报头和数据组成。头一般为20-60字节,其中40字节可选。通常,报头仅由20个字节组成。IP数据报的结构如下图所示。

为了方便地读写ip头,在lwip源代码中定义了一个ip_hdr结构来表示ip数据报头。

IP层数据接收

如上所述,不同的数据包被不同地处理。如果是ARP包,调用etharp_input函数进行处理。如果是IP包,则交给IP相关功能处理。

udpecho演示中使用的是IPV4协议,所以会调用ip4_input函数。

在ip4_input函数中,ip数据报的相关字段,如长度、校验和、版本号等。会进行检查,也会判断数据包是否是本地发送的。如果它不是本地发送的,它可能会被转发或丢弃。如果数据报没有问题,IP层会根据传输层的协议类型,将数据包转移到不同的入口函数,比如UDP _ input和TCP _ input函数。

IP层数据传输

当传输层协议需要通过ip层发送数据时,会在上层函数中调用ip4_output_if_src函数,在这个函数中,会调用ip4_output_if_opt_src函数,将传输的数据封装成一个ip数据报,并在数据报中填入目的ip地址、源IP地址、协议类型等相关信息。然后调用etharp_output(),它将解析MAC地址,组装以太网帧并发送出去。在etharp_output()函数中,最终会调用网络接口层的相关发送函数。

LWIP传输层和应用层

网络层通过IP协议完成了主机间数据报传输的功能,但数据还没有到达最终目的地——主机上的某个特定应用。

IP层通过传输层协议将数据包提交给应用,常用的传输层协议有UDP协议、TCP协议等。

这里以UDP协议为例,它是一个比较简单的传输层协议,常用于局域网环境和文章播放领域。以UDP为例,结合SDK代码,说明传输层如何实现数据交互。

UDP消息

使用UDP传输数据时,会将数据封装在UDP报文中,将数据包封装在IP层的IP报文中,将IP数据包封装在物理层的物理数据帧中。

一条用户数据在发送时已经被封装了三次。

UDP相关数据结构

在LWIP源代码的udp.h中,定义了消息头和udp控制块的数据结构。

LwIP报文头的数据结构为udp_hdr,定义了udp报文头的字段,包括16位源端口号src、16位目的端口号dest、16位用户数据报总长度和16位校验和。

LwIP还定义了一个UDP控制块,记录了与UDP通信的所有相关信息,如源端口号、目标端口号、源IP地址、目标IP地址以及接收数据时的回调函数等。系统会基于UDP协议为每个进程创建一个UDP控制块,绑定到相应的端口,用链表连接所有的UDP控制块。UDP收到消息时,会遍历链表上的所有控制块,通过端口号找到匹配的控制块,通过回调函数将数据传递给上层应用。

UDP消息接收

在IP层,当接收到包含udp报文的数据报时,会调用udp_input函数,在这个函数中会检查一些报文,然后根据报文中的端口信息找到UDP控制块。最后,数据将通过udp控制块中的回调函数recv_udp传递给应用层。如果找不到相应的端口,将返回端口不可达数据包。

UDP消息发送

UDP消息的传输依赖于IP层提供的服务。用户在发送数据时,需要在应用程序中调用udp_send或udp_sendto,将用户数据填入pbuf数据区,并将pubf作为参数传入udp_send或udp_sendto。

udp_send和udp_sendto的区别在于,udp_sendto向指定的ip地址和端口号发送数据,udp_send向udp控制块中定义的ip地址和端口号发送数据。Udp_send实际上是调用udp_sendto发送数据,最终这两个函数会调用udp_sendto_if。

udp_sendto_if函数将完成udp消息的组装和发送,最后调用Ip层的发送函数发送消息。

LWIP应用层

在应用层,通常通过调用传输层的一些函数来编写特定的应用程序,从而实现数据传输。在udpecho演示中,接收到数据后,udp控制块中绑定的接收回调函数中会调用udp _ SendTo函数。

除了上面介绍的一些协议,LWIP还支持ICMP、IGMP、PPP、DHCP等协议,SOCKET API和NETCONN API更容易使用,但RAW/Callback API的使用有助于更好地理解LWIP协议。

对LWIP协议栈感兴趣的读者可以自己了解更多。

审计彭静

声明本站所有作品图文均由用户自行上传分享,仅供网友学习交流。若您的权利被侵害,请联系我们

Top