第一步:核心库函数梳理

1.1 什么是 lwIP?

lwIP = Lightweight IP(轻量级 TCP/IP 协议栈)。它是一个为嵌入式系统量身打造的开源 TCP/IP 协议栈,让你的单片机(如 STM32)也能联网通信。

打个比方:lwIP 就像给你的单片机装了一个"联网模块",让它可以像电脑一样通过网络发数据、收数据。

1.1.1 lwIP 协议栈分层架构

┌──────────────────────────────────────────────────────┐
│                   应用层 (Application)                 │
│    你的 lwip_demo.c / httpd / MQTT / SNTP             │
├──────────────────────────────────────────────────────┤
│     NETCONN API / Socket API  (lwip/api.h)            │
│     ↑ 封装层,阻塞式调用,面向应用开发者                 │
├──────────────────────────────────────────────────────┤
│     传输层 (Transport):TCP (tcp.c) / UDP (udp.c)      │
│     ↑ 可靠传输 + 流量控制 / 不可靠传输                  │
├──────────────────────────────────────────────────────┤
│     网络层 (Network):IP (ip.c) / ICMP / ARP           │
│     ↑ 寻址、路由、碎片重组                              │
├──────────────────────────────────────────────────────┤
│     网络接口层 (Link):netif / ethernetif / DMA驱动     │
│     ↑ 收发以太网帧,管理MAC                             │
├──────────────────────────────────────────────────────┤
│     硬件层 (Hardware):STM32 ETH MAC + PHY + RJ45      │
└──────────────────────────────────────────────────────┘

初学者关键理解点:

  • RAW API 直接操作传输层(TCP/UDP),不走 NETCONN/Socket 封装
  • NETCONN API 在 TCP/UDP 上面加了一层抽象,提供阻塞式调用
  • Socket API 在 NETCONN 上再封装一层,提供标准 BSD Socket 接口
  • 这三层 API 从上到下,最底层的实现都是同一个 TCP/IP 协议栈,只是封装程度不同

1.1.2 pbuf —— 数据包的核心载体

pbuf (Packet Buffer) 是 lwIP 中最重要的数据结构,所有网络数据都由它承载。

pbuf 分配类型 含义 内存来源 适用场景
PBUF_RAW 原始数据包 POOL/RAM low_level_input 接收以太网帧,不含头部预留
PBUF_POOL 从内存池快速分配 memp 固定大小池 DMA 接收缓冲、小数据快速收发
PBUF_RAM 从堆中动态分配 mem 堆(malloc) 大尺寸数据、需要连续内存时
PBUF_TRANSPORT 传输层发送用 POOL/RAM udp_send/tcp_write 时,会自动在 payload 前预留 UDP/TCP 头空间

pbuf 链的结构:

pbuf_1                     pbuf_2                     pbuf_3
┌──────────┐    next       ┌──────────┐    next       ┌──────────┐
│ payload ─┤──────────────→│ payload ─┤──────────────→│ payload ─┤→ NULL
│ len = N  │               │ len = M  │               │ len = K  │
│ tot_len  │               │ tot_len  │               │ tot_len  │
│ = N+M+K  │               │ = N+M+K  │               │ = N+M+K  │
└──────────┘               └──────────┘               └──────────┘

遍历方式:
for (q = p; q != NULL; q = q->next) {
    memcpy(dest + offset, q->payload, q->len);  // 逐段拷贝数据
}
  • payload = 指向实际数据的指针
  • len = 当前片段的长度
  • tot_len = 整条链的总长度(所有链表中每片都存同样的值)
  • next = 指向下一个 pbuf 片段

1.2 核心函数速查表

下表整理了本例程中用到的 lwIP 核心 API 函数。按模块分类,方便你以后查阅。

一、初始化与全局管理

函数名 所属模块 通俗解释
lwip_init() 初始化 “一键启动协议栈”——把 lwIP 内部的所有模块(内存、网络接口、协议等)全部初始化好。就像你开电脑时要先启动操作系统一样。
tcpip_init() 初始化 “带操作系统的启动”——在 FreeRTOS 下使用的初始化,内部创建一个 tcpip_thread 线程自动处理协议栈,比 lwip_init() 多了一层 OS 抽象。
lwip_pkt_handle() 数据接收 “取快递”——检查以太网 DMA 有没有收到新数据包,有的话通过 ethernetif_input() 送给 lwIP 协议栈处理。裸机下必须在主循环中定期调用。
sys_check_timeouts() 定时管理 “定时器心跳”——定期检查 lwIP 内部的各种定时器是否超时(比如 TCP 重传、ARP 老化等)。相当于你的闹钟提醒功能。
sys_now() 定时管理 “获取当前毫秒时间”——返回当前系统时钟的毫秒值,lwIP 用这个来计算各种超时。

二、内存管理

函数名 所属模块 通俗解释
pbuf_alloc() 数据包管理 “申请一个网络数据包”——在内存中分配一个 pbuf 结构体来存放网络数据。pbuf 就像是网络数据的一个"快递箱",可以装要发或刚收到的数据。
pbuf_free() 数据包管理 “释放快递箱”——用完数据包后释放内存,不然内存会越用越少(内存泄漏)。
pbuf_take() 数据包管理 “把数据装进快递箱”——将用户缓冲区中的数据复制到 pbuf 结构体中。
mem_malloc() 内存堆管理 “申请一块堆内存”——lwIP 自己实现的动态内存分配函数,类似于 C 语言的 malloc()
mem_free() 内存堆管理 “释放堆内存”——释放 mem_malloc 申请的内存。

三、网络接口(网卡)

函数名 所属模块 通俗解释
netif_add() 网络接口 “注册一张网卡”——告诉 lwIP:"我现在有一张网卡要加入网络,这是它的初始化函数和输入函数。"相当于给电脑插入网卡并安装驱动。
netif_set_default() 网络接口 “设为默认网卡”——如果有多张网卡,指定这一张为默认路由出口。就像你电脑有 WiFi 和有线网卡,系统会选一个默认的。
netif_set_up() 网络接口 “打开网卡”——把网卡设置为"已启用"状态,告诉 lwIP 这张网卡可以用了。
netif_set_down() 网络接口 “关闭网卡”——把网卡设置为"已禁用"状态。
netif_set_link_callback() 网络接口 “注册网线插拔通知”——当网线被拔掉或插上时,lwIP 会调用你注册的这个函数。
netif_is_link_up() 网络接口 “检查网线是否插好”——判断物理链路是否正常的函数。

四、地址设置

函数名 所属模块 通俗解释
ip4_addr() IP 地址 “组装IP地址”——把 4 个数字(如 192,168,1,30)组装成 lwIP 认识的 IP 地址格式。
ip_addr_set_zero_ip4() IP 地址 “清空IP地址”——把 IP 地址设为 0.0.0.0(未分配状态),等待 DHCP 分配。
dhcp_start() DHCP “派地去要IP地址”——发送 DHCP 请求,让路由器自动分配一个 IP 地址。
dhcp_stop() DHCP “停止要IP地址”——停止 DHCP 请求。
dhcp_supplied_address() DHCP “检查IP地址要到没有”——判断 DHCP 是否成功分配到 IP 地址。

五、UDP 通信(RAW API)

函数名 所属模块 通俗解释
udp_new() UDP “创建一个UDP插座”——创建一个新的 UDP 控制块(类似于一个"快递收发点")。
udp_remove() UDP “拆除UDP插座”——删除 UDP 控制块,释放资源。
udp_bind() UDP “绑定端口号”——把 UDP 控制块绑定到本地某个端口上,这样别人发到这个端口的数据你才能收到。就像你的快递箱上贴了房间号。
udp_connect() UDP “指定对方的地址”——设置远程主机的 IP 地址和端口号,告诉 lwIP 你要跟谁通信。
udp_disconnect() UDP “断开UDP连接”——清除远程地址设置。
udp_send() UDP “发送UDP数据”——通过 UDP 把数据发送出去。
udp_recv() UDP “注册UDP收件回调”——注册一个回调函数,当收到 UDP 数据时,lwIP 会自动调用这个函数。相当于告诉快递员:“有包裹送到就喊我!”

六、TCP 通信(RAW API)

函数名 所属模块 通俗解释
tcp_new() TCP “创建一个TCP插座”——创建一个新的 TCP 控制块。
tcp_bind() TCP “绑定端口号”——绑定本地端口。
tcp_connect() TCP “连接远程服务器”——向远程服务器发起 TCP 连接请求(三次握手)。
tcp_abort() TCP “强行断开连接”——直接终止 TCP 连接,不等待挥手完成。
tcp_write() TCP “把数据写进发送缓冲区”——将要发送的数据复制到 lwIP 内部的 TCP 发送缓冲区中。注意:这只是"准备发送",还不是"真正发出"。
tcp_output() TCP “真正发出数据”——把 TCP 发送缓冲区中准备好的数据真正通过网络发出去。
tcp_recved() TCP “告诉lwIP:数据我收好了”——当应用层读取了接收到的数据后,通知 lwIP 可以释放缓冲区接收更多数据。如果不调用这个函数,lwIP 会以为你还没看完数据,就不再收新的。
tcp_sndbuf() TCP “查看发送缓冲区还剩多少”——返回当前 TCP 发送缓冲区的剩余空间大小。
tcp_arg() TCP “给回调函数传参数”——注册一个自定义参数,在后续的回调函数中可以取出来用。
tcp_recv() TCP “注册TCP收件回调”——当收到 TCP 数据时会调用这个函数。
tcp_sent() TCP “注册发送确认回调”——当对方确认收到我们的数据后会调用这个函数。
tcp_err() TCP “注册错误回调”——当 TCP 连接发生错误时会调用这个函数。
tcp_poll() TCP “注册周期轮询回调”——lwIP 会定期(每隔 N 秒)调用这个函数,让你有机会做一些周期性检查。
tcp_listen(pcb) TCP “进入监听状态”——TCP服务器专用,将绑定的 PCB 转为 LISTEN 状态,等待客户端连接。
tcp_accept(pcb, fn) TCP “注册新连接回调”——TCP服务器专用,当有新客户端连接时 lwIP 会自动调用此函数,并传入一个新的 PCB。
tcp_close(pcb) TCP “优雅关闭连接”——发起 TCP 四次挥手,正常关闭连接。对比 tcp_abort() 是暴力断开。

什么是 RAW API?
RAW API 是 lwIP 最底层的编程接口,直接与 TCP/UDP 协议交互,没有操作系统也可以使用。特点是:

  • 没有阻塞等待(non-blocking)
  • 全部通过回调函数来通知你
  • 代码量少、效率高
  • 适合裸机(无 RTOS)或对性能要求高的场景

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐