lwip代码分析

news2024/11/26 0:40:19

lwIP(Lightweight IP)是一个为嵌入式系统设计的轻量级TCP/IP协议栈。它旨在为资源受限的环境提供完整的网络协议功能,同时保持低内存使用和代码大小。由于其模块化的设计,开发者可以根据需要选择包含或排除特定功能,以满足特定应用的资源要求。

Xilinx的lwIP是基于开源lwIP TCP/IP协议栈的一个适应版本,专门为Xilinx的硬件平台,如Zynq-7000和MicroBlaze,进行了优化和集成。Xilinx为其硬件平台提供了lwIP的库,使得开发者可以轻松地在其FPGA和SoC设计中实现网络通信功能。

lwip TCP Perf Client为例,这是一个fpga作为TCP Client,像TCP Server发送批量数据,并测试传输性能的例程。

image-20231008155050017

TCP参数

先看几个TCP相关的参数

  • TCP_CONN_PORT表示TCP的端口号,在Server中,需要指定该端口号,如果发现tcp一直不通,但ping是可以通的,多半原因是这个端口被占用了;
  • TCP_SERVER_IP_ADDRESS表示TCP Server的IP地址

image-20231008155222432

FPGA的IP地址是在main.c里面指定的:

image-20231008155653659

如果TCP Server使用网络调试助手接收数据,设置如下:(需要注意,本地端口号应该是5001,跟代码中匹配)

image-20231008155850927

main函数

main函数的内容如下:

int main(void)
{
	struct netif *netif;

	/* the mac address of the board. this should be unique per board */
	unsigned char mac_ethernet_address[] = {
		0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };

	netif = &server_netif;
#if defined (__arm__) && !defined (ARMR5)
#if XPAR_GIGE_PCS_PMA_SGMII_CORE_PRESENT == 1 || \
		XPAR_GIGE_PCS_PMA_1000BASEX_CORE_PRESENT == 1
	ProgramSi5324();
	ProgramSfpPhy();
#endif
#endif

	/* Define this board specific macro in order perform PHY reset
	 * on ZCU102
	 */
#ifdef XPS_BOARD_ZCU102
	IicPhyReset();
#endif

	init_platform();

	xil_printf("\r\n\r\n");
	xil_printf("-----lwIP RAW Mode TCP Client Application-----\r\n");

	/* initialize lwIP */
	lwip_init();
	
	/* Add network interface to the netif_list, and set it as default */
	if (!xemac_add(netif, NULL, NULL, NULL, mac_ethernet_address,
				PLATFORM_EMAC_BASEADDR)) {
		xil_printf("Error adding N/W interface\r\n");
		return -1;
	}
	
	netif_set_default(netif);

	/* now enable interrupts */
	platform_enable_interrupts();

	/* specify that the network if is up */
	netif_set_up(netif);
	assign_default_ip(&(netif->ip_addr), &(netif->netmask), &(netif->gw));
	print_ip_settings(&(netif->ip_addr), &(netif->netmask), &(netif->gw));
	
	xil_printf("\r\n");

	/* print app header */
	print_app_header();

	/* start the application*/
	start_application();
	xil_printf("\r\n");

	while (1) {
		if (TcpFastTmrFlag) {
			tcp_fasttmr();
			TcpFastTmrFlag = 0;
		}
		if (TcpSlowTmrFlag) {
			tcp_slowtmr();
			TcpSlowTmrFlag = 0;
		}
		xemacif_input(netif);
		transfer_data();
	}

	/* never reached */
	cleanup_platform();

	return 0;
}

在main函数中,首先就是定义各种网口接口相关的变量,并定义了MAC地址。

netif

这个netif的指针,需要多关注一下。

在lwIP中,netif(网络接口)是一个核心的结构体,它代表了一个网络接口,例如以太网接口、Wi-Fi接口等。netif结构体用于定义和管理这些接口,使lwIP可以在多个接口上运行并进行路由决策。

具体来说,netif结构体包括了以下几个主要的部分:

  1. 硬件地址:例如MAC地址。
  2. IP地址、子网掩码和网关:这些用于IP层的路由和地址决策。
  3. 状态标志:表示接口的状态,例如是否激活、是否为默认接口等。
  4. 输入和输出函数指针:这些函数用于处理从该接口接收到的数据包或向该接口发送数据包。
  5. 其他驱动特定的数据:例如用于DMA的描述符、缓冲区等。

当你在lwIP中添加一个新的网络接口时,你通常会初始化一个netif结构体并使用netif_add()函数将其添加到lwIP的接口列表中。这样,lwIP就可以开始在该接口上接收和发送数据包了。

简而言之,netif是lwIP中用于表示和管理网络接口的关键结构体。

init_platform

init_platform()函数中,初始化定时器和中断。

image-20231008161317195

接下来就是lwip的初始化,这三个初始化都是在platform的库里面写好的,直接调用就行。

xemac_add

后面xemac_add的原型如下,可以简单理解为设置网口的mac地址,此处没有设置IP的信息,可以看到传进去的参数都是NULL。

struct netif *
xemac_add(struct netif *netif,
	ip_addr_t *ipaddr, ip_addr_t *netmask, ip_addr_t *gw,
	unsigned char *mac_ethernet_address,
	UINTPTR mac_baseaddr)

netif_set_default

netif_set_default函数在lwIP中用于设置默认的网络接口。在一个系统中可能存在多个网络接口,但通常只有一个被视为默认接口。当lwIP需要发送数据包,但不知道应该通过哪个接口发送时,它会选择默认接口。

函数原型如下:

/**
 * @ingroup netif
 * Set a network interface as the default network interface
 * (used to output all packets for which no specific route is found)
 *
 * @param netif the default network interface
 */
void
netif_set_default(struct netif *netif)
{
  LWIP_ASSERT_CORE_LOCKED();

  if (netif == NULL) {
    /* remove default route */
    mib2_remove_route_ip4(1, netif);
  } else {
    /* install default route */
    mib2_add_route_ip4(1, netif);
  }
  netif_default = netif;
  LWIP_DEBUGF(NETIF_DEBUG, ("netif: setting default interface %c%c\n",
                            netif ? netif->name[0] : '\'', netif ? netif->name[1] : '\''));
}

其中,netif是你希望设置为默认的网络接口的指针。

当你调用这个函数时,传入的netif结构体会被设置为默认网络接口。这意味着,除非有特定的路由决策指示其他接口,否则所有的出站数据包都会通过这个接口发送。

例如,如果你有一个以太网接口和一个Wi-Fi接口,并且你希望所有的通信默认通过Wi-Fi接口进行,那么你会在初始化Wi-Fi接口后调用netif_set_default函数,并传入Wi-Fi接口的netif结构体指针。

这个函数对于确保正确的网络通信行为非常重要,特别是在存在多个网络接口的系统中。

platform_enable_interrupts

这个函数就很容易理解了,就是使能中断,函数原型如下:

void platform_enable_interrupts()
{
	/*
	 * Enable non-critical exceptions.
	 */
	Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
	XScuTimer_EnableInterrupt(&TimerInstance);
	XScuTimer_Start(&TimerInstance);
	return;
}

netif_set_up

netif_set_up函数在lwIP中用于激活一个网络接口。当你初始化一个网络接口并准备好开始接收和发送数据时,你需要调用这个函数来标记该接口为"up"状态。

函数原型如下:

void
netif_set_up(struct netif *netif)
{
  LWIP_ASSERT_CORE_LOCKED();

  LWIP_ERROR("netif_set_up: invalid netif", netif != NULL, return);

  if (!(netif->flags & NETIF_FLAG_UP)) {
    netif_set_flags(netif, NETIF_FLAG_UP);

    MIB2_COPY_SYSUPTIME_TO(&netif->ts);

    NETIF_STATUS_CALLBACK(netif);

#if LWIP_NETIF_EXT_STATUS_CALLBACK
    {
      netif_ext_callback_args_t args;
      args.status_changed.state = 1;
      netif_invoke_ext_callback(netif, LWIP_NSC_STATUS_CHANGED, &args);
    }
#endif

    netif_issue_reports(netif, NETIF_REPORT_TYPE_IPV4 | NETIF_REPORT_TYPE_IPV6);
#if LWIP_IPV6
    nd6_restart_netif(netif);
#endif /* LWIP_IPV6 */
  }
}

其中,netif是你希望激活的网络接口的指针。

当你调用netif_set_up函数时,它会执行以下操作:

  1. 设置netif结构体中的flags字段,标记该接口为"up"状态。
  2. 如果配置了lwIP的相关回调,例如NETIF_STATUS_CALLBACK,那么这些回调函数也会被触发,通知应用程序该接口的状态已经改变。

通常,在你完成网络接口的硬件初始化、分配了必要的资源,并确信接口已经准备好进行通信后,你会调用netif_set_up函数。这样,lwIP就知道它可以开始在该接口上接收和发送数据包了。

相反地,如果你需要将一个接口标记为"down"状态,例如在接口遇到错误或需要进行维护时,你可以调用netif_set_down函数。这会告诉lwIP停止在该接口上的通信,直到接口再次被设置为"up"状态。

assign_default_ip

从名字也可以看到出来,就是设置ip地址、Netmask和gate way

函数原型也非常直观,不做过多解释了

static void assign_default_ip(ip_addr_t *ip, ip_addr_t *mask, ip_addr_t *gw)
{
	int err;

	xil_printf("Configuring default IP %s \r\n", DEFAULT_IP_ADDRESS);

	err = inet_aton(DEFAULT_IP_ADDRESS, ip);
	if (!err)
		xil_printf("Invalid default IP address: %d\r\n", err);

	err = inet_aton(DEFAULT_IP_MASK, mask);
	if (!err)
		xil_printf("Invalid default IP MASK: %d\r\n", err);

	err = inet_aton(DEFAULT_GW_ADDRESS, gw);
	if (!err)
		xil_printf("Invalid default gateway address: %d\r\n", err);
}

start_application

start_application函数是一个启动网络应用的函数。在很多lwIP的示例应用中,这个函数被用来初始化和启动特定的网络应用,例如启动一个HTTP服务器、TCP客户端、UDP回声服务等。具体的功能和行为取决于应用的需求和设计。这个函数可能会初始化所需的网络资源,设置回调函数,并开始监听网络事件。

  1. 初始化变量:函数开始时,初始化了一些变量,如err用于错误处理,pcb代表TCP控制块,remote_addr用于存储远程服务器的IP地址,以及一个循环计数器i
  2. 设置远程服务器的IP地址
    • 如果启用了IPv6(LWIP_IPV6==1),则使用inet6_aton函数将TCP_SERVER_IPV6_ADDRESS字符串转换为IPv6地址格式并存储在remote_addr中。
    • 如果未启用IPv6,则使用inet_aton函数将TCP_SERVER_IP_ADDRESS字符串转换为IPv4地址格式。
  3. 检查IP地址的有效性:如果IP地址转换失败,函数会打印错误消息并返回。
  4. 创建TCP控制块(PCB):使用tcp_new_ip_type函数为客户端创建一个新的TCP控制块。
  5. 连接到远程服务器:使用tcp_connect函数尝试连接到远程服务器的指定IP地址和端口TCP_CONN_PORT。如果连接成功,tcp_client_connected回调函数将被注册,以便在连接建立后进行处理。
  6. 错误处理:如果在上述步骤中出现任何错误,函数会打印相应的错误消息并关闭TCP连接。
  7. 初始化发送缓冲区:为send_buf缓冲区填充数据,数据内容是0到9的数字字符。

总的来说,start_application函数的主要目的是初始化一个TCP客户端,尝试连接到指定的远程服务器,并准备发送数据。

函数原型如下:

void start_application(void)
{
	err_t err;
	struct tcp_pcb* pcb;
	ip_addr_t remote_addr;
	u32_t i;

#if LWIP_IPV6==1
	remote_addr.type= IPADDR_TYPE_V6;
	err = inet6_aton(TCP_SERVER_IPV6_ADDRESS, &remote_addr);
#else
	err = inet_aton(TCP_SERVER_IP_ADDRESS, &remote_addr);
#endif /* LWIP_IPV6 */

	if (!err) {
		xil_printf("Invalid Server IP address: %d\r\n", err);
		return;
	}

	/* Create Client PCB */
	pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);
	if (!pcb) {
		xil_printf("Error in PCB creation. out of memory\r\n");
		return;
	}

	err = tcp_connect(pcb, &remote_addr, TCP_CONN_PORT,
			tcp_client_connected);
	if (err) {
		xil_printf("Error on tcp_connect: %d\r\n", err);
		tcp_client_close(pcb);
		return;
	}
	client.client_id = 0;

	/* initialize data buffer being sent with same as used in iperf */
	for (i = 0; i < TCP_SEND_BUFSIZE; i++)
		send_buf[i] = (i % 10) + '0';

	return;
}

tcp_fasttmr和tcp_slowtmr

在lwip的TCP视线中,快速定时器(tcp_fasttmr)和慢速定时器(tcp_slowtmr)都是为了TCP连接的维护而存在的,但它们关注的方面和执行频率是不同的。

  1. 运行频率

    • 快速定时器:通常每250毫秒被调用一次(这是默认值,但可以配置)。
    • 慢速定时器:通常每500毫秒被调用一次(这也是默认值,但同样可以配置)。
  2. 关注的方面

    • 快速定时器 (tcp_fasttmr)

      主要关注:

      • 重传管理:如果一个数据段没有得到确认,它会被重新发送。快速定时器负责处理这些重传。
      • 延迟确认:TCP不会立刻确认每一个接收到的数据段,而是稍作延迟,以期待有数据可以与确认一同发送,从而减少网络的数据包数量。快速定时器可以触发这些延迟确认的发送。
    • 慢速定时器 (tcp_slowtmr)

      主要关注:

      • 连接的生命周期管理:例如,关闭那些已经结束但还没有完全关闭的连接。
      • 持续活动检测:例如,检查长时间没有活动的连接,并可能发送探测数据段来检查对方是否仍然活跃。
      • 超时管理:管理那些因为长时间没有响应而需要关闭的连接。
      • 拥塞控制:调整窗口大小和其他与流量控制相关的参数。

简而言之,快速定时器主要关注与数据传输直接相关的事务,如重传和确认,而慢速定时器则更多地关注连接的维护、超时和流控制。

tcp_write

tcp_write 函数用于将数据排入到一个TCP连接的发送队列。它是应用程序与 lwIP TCP层之间的一个关键接口,允许应用程序发送数据到其TCP连接。

以下是关于 tcp_write 函数的一些关键点:

  1. 非阻塞:与某些TCP/IP实现不同,tcp_write 是非阻塞的。这意味着,如果当前没有足够的可用缓冲区来容纳你想发送的数据,函数将不会阻塞,而是返回一个错误。
  2. 排队,不是直接发送:当你调用 tcp_write 时,你实际上是将数据放入发送队列,而不是立即发送数据。真正的数据传输将在后续的 lwIP 处理中进行,这可能涉及与其他TCP机制的交互,如拥塞控制。
  3. 参数:该函数通常接受以下参数:
    • pcb:代表TCP连接的控制块。
    • data:指向要发送数据的指针。
    • len:要发送的数据的长度。
    • flags:与数据发送相关的标志。例如,TCP_WRITE_FLAG_COPY 表示应从应用程序的数据缓冲区复制数据(而不是直接引用)。
  4. 确认机制:使用 tcp_write 发送的数据将在对方确认收到之后才从发送队列中移除。这意味着,即使你已经调用了 tcp_write,你也需要确保你的应用程序继续处理(例如,通过调用 tcp_output 或等待 lwIP 的主循环)来确保数据被实际发送和确认。
  5. 合适的调用时间:为了避免不必要的网络拥塞和效率低下,建议在连接建立后或在接收到数据或发送缓冲区有可用空间时(通过相关的TCP回调函数)再调用 tcp_write

tcp_writelwIP 的TCP API的一部分,与其他函数(如 tcp_connect, tcp_listen, tcp_close 等)一起,提供了完整的TCP功能。在使用它时,重要的是要理解其工作原理,以及与其他TCP操作的交互方式。

公众号:傅里叶的猫

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

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

相关文章

加解密原理(HCIA)

一、加密技术 1、加密的两个核心组件 2、加密技术作用&#xff1a; 二、加解密技术原理 1、对称加密 2、非对称加密 &#xff08;1&#xff09;思考问题&#xff1f; 1&#xff09;、有了非对称加密为什么还用对称加密&#xff1f; 2&#xff09;、如何传递秘钥呢&…

【微信小程序开发】学习小程序的网络请求和数据处理

前言 网络请求是微信小程序中获取数据和与服务器交互的重要方式。微信小程序提供了自己的API来处理网络请求&#xff0c;使得开发者可以轻松地在微信小程序中实现数据的获取和提交。本文将介绍微信小程序中的网络请求&#xff0c;包括使用wx.request发起GET和POST请求&#xf…

基于RK3568高性价比全国产EMS储能解决方案(二)设计方案

目录 版 本 修 订 记 录 1. 产品介绍 1.1. 什么是XM3568-EP 1.2. 产品特点 1.3. 外壳尺寸 1.4. 外壳外观 1.5. 规格参数 2. 设备使用介绍 2.1. 下载需要使用到的驱动和调试工具 2.2. 启动网关 2.3. DEBUG串口的使用方法 2.4. LED指示灯说明 3. Linux系…

【每日一题】274. H 指数-2023.10.29

题目&#xff1a; 274. H 指数 给你一个整数数组 citations &#xff0c;其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。 根据维基百科上 h 指数的定义&#xff1a;h 代表“高引用次数” &#xff0c;一名科研人员的 h 指数 是指他…

计算机视觉注意力机制小盘一波 (学习笔记)

将注意力的阶段大改分成了4个阶段 1.将深度神经网络与注意力机制相结合&#xff0c;代表性方法为RAM ⒉.明确预测判别性输入特征&#xff0c;代表性方法为STN 3.隐性且自适应地预测潜在的关键特征&#xff0c;代表方法为SENet 4.自注意力机制 通道注意力 在深度神经网络中…

ARPG----C++学习记录01日志和调试

多人射击有点难&#xff0c;发现这个更加基础&#xff0c;先学习这个 显示日志 可以在代码中插入这样一行来打印日志&#xff0c;蓝图里的printstring会在屏幕和日志里都显示。可以使用%f&#xff0c;d等来获取后边的输入值。对于打映字符串变量&#xff0c;传入需要* UE_LOG…

基于 matplotlib 实现的基本排序算法的动态可视化项目源码,通过 pyaudio 增加音效,冒泡、选择、插入、快速等排序

基本排序算法动态可视化 依托 matplotlib 实现的基本排序算法的动态可视化&#xff0c;并通过 pyaudio 增加音效。 安装 在使用之前请先检查本地是否存在以下库&#xff1a; matplotlibpyaudiofire requirements.txt 中包含了上述的库 使用 目前本项目仅提供了以下排序算…

最新基于机器学习模型单图换脸离线版软件包及使用方法,本地离线版本模型一键运行(免费下载)

最新基于机器学习模型单图换脸离线版软件包及使用方法,本地离线版本模型一键运行(免费下载)。 “单图换脸”离线一键运行版来了。Roop发布几十个小时后,马不停蹄地搞了Colab在线版。其实这东西都挺好的,又快又方便,几乎没有任何硬件要求,点一点就可以搞定了。但是它有…

案例分析真题-数据库

案例分析真题-数据库 2010年真题 【问题1】 【问题2】 【问题3】 2011 年真题 【问题1】 【问题2】 【问题3】 骚戴理解&#xff1a;这里主要是要学会分析出题目中哪里用到了反规范化技术&#xff0c;主打的就是一个会分析&#xff01; 2012年真题 【问题1】 【问题2】 【问题…

2022年上半年上午易错题(软件设计师考试)

1.以下关于冯诺依曼计算机的叙述中&#xff0c;不正确的是( )。 A.程序指令和数据都采用二进制表示 B.程序指令总是存储在主存中&#xff0c;而数据则存储在高速缓存中 C.程序的功能都由中央处理器(CPU)执行指令来实现 D.程序的执行过程由指令进行自动控制 程序指令和数据…

目标检测算法改进系列之嵌入Deformable ConvNets v2 (DCNv2)

Deformable ConvNets v2 简介&#xff1a;由于构造卷积神经网络所用的模块中几何结构是固定的&#xff0c;其几何变换建模的能力本质上是有限的。在DCN v1中引入了两种新的模块来提高卷积神经网络对变换的建模能力&#xff0c;即可变形卷积 (deformable convolution) 和可变形…

提高网站速度与用户体验:CDN加速的解决方案

对于网站运营者而言&#xff0c;维持高速和稳定的网站速度对于提升用户体验和提高搜索引擎排名至关重要。为了达到这一目标&#xff0c;内容交付网络&#xff08;CDN&#xff09;加速服务提供了一个强大的解决方案。然而&#xff0c;传统的CDN加速服务需要备案手续&#xff0c;…

『阿里云盘 AList Kodi』家庭影院搭建指南

文章目录 前言AList简介安装相关命令获取密码 阿里云盘Kodi安装 参考链接 前言 之前我们使用简易 Python 框架搭建部署了私人影院&#xff0c;但是部分小伙伴们表示对于个人使用来说比较复杂&#xff0c;这次将会带来更加适合个人使用&#xff0c;并且部署更加简单的 阿里云盘…

合并两个有序链表(C++)

题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#xff1…

案例分析大汇总

案例分析心得 2018-2022年的案例分析考试内容汇总&#xff08;近五年&#xff09; 架构设计题型 软件系统建模 数据库 Web 系统设计 2018年 胖/瘦客户端 C/S 架构非功能性需求 数据流图DFDE-R图Essential Use Cases(抽象用例)&#xff0c;Real Use Cases(基础用例)信息工…

Prometheus接入AlterManager配置钉钉告警(基于K8S环境部署)

文章目录 一、钉钉群创建报警机器人二、安装Webhook-dingtalk插件三、配置Webhook-dingtalk插件对接钉钉群四、配置AlterManager告警发送至Webhook-dingtalk五、Prometheus接入AlterManager配置六、部署PrometheusAlterManager(放到一个Pod中)七、测试告警 注意&#xff1a;请基…

Xubuntu16.04系统中create_ap开启5G网络的踩坑记录

Xubuntu16.04系统中安装create_ap创建无线AP&#xff1a; https://blog.csdn.net/qq_45445740/article/details/133972642?spm1001.2014.3001.5501 目录 1.create_ap.conf 配置文件解析2.关于信号强度和延时2.1 信号强度2.2 信号延时2.3 网络延时测试工具推荐——PingPlotter …

Linux | 程序替换

前言 本文主要记录小编学习程序替换中遇到的一些问题&#xff0c;并分享记录下来&#xff0c;希望可以给大家带来帮助&#xff1b; 一、初始程序替换 所谓程序替换&#xff0c;就是将本进程的代码和数据进行替换&#xff0c;运行新程序的代码&#xff1b;我们之前在讲解进程地址…

CDN技术:提升网络效能与用户满意度

网络的持续增长和数字内容的快速传播已经引发了对网络性能和用户体验的不断挑战。内容交付网络&#xff08;CDN&#xff09;技术应运而生&#xff0c;以应对这些挑战&#xff0c;实现内容高效分发&#xff0c;提升了整体网络质量。 CDN的核心原理在于通过在全球范围内部署多个服…

【C++初阶】类和对象——操作符重载const成员函数取地址重载日期类的实现

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C头疼记 目录 前言&#xff1a; 运算符重载 运算符重载 赋值运算符重载 前置和后置重载 const成员 取地址及const取地址操作符重载 使用函数操作符重载完成日期类的实现 前言&#xff1a; 上篇文…