Fork me on GitHub
0%

LwIP协议栈

简介

Lwip官网:https://savannah.nongnu.org/projects/lwip/

LWIP(light weight ip)轻型IP协议,有无操作系统的支持都可以运行。LwIP实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用,它只需十几KB的RAM和40K左右的ROM就可以运行,这使LwIP协议栈适合在低端的嵌入式系统中使用。

LWIP支持的协议种类:

  • DNS,域名解析;
  • SNMP,简单网络管理协议;
  • DHCP,动态主机配置协议;
  • UDP 协议,用户数据报协议;
  • TCP 协议,支持 TCP 拥塞控制, RTT 估计,快速恢复与重传等;
  • IP 协议,包括 IPv4 和 IPv6,支持 IP 分片与重装,支持多网络接口下数据转发;
  • AUTOIP, IP 地址自动配置;
  • ICMP 协议,用于网络调试与维护;
  • IGMP 协议,用于网络组管理,可以实现多播数据的接收;
  • ARP 协议,以太网地址解析协议;
  • PPP,点对点协议,支持 PPPoE

LwIP架构

LwIP 符合 TCP/IP 模型架构,规定了数据的格式、传输、路由和接收,以实现端到端的通信。 此模型包括四个抽象层,用于根据涉及的网络范围,对所有相关协议排序。这几 层从低到高依次为:

  • 链路层包含了局域网的单网段 (链路)通信技术

  • 网际层 (IP)将独立的网络连接起来,建立互联

  • 传输层处理主机端口到主机端口的通信

  • 应用层在实现多个应用进程相互通信的同时,完成应用所需的服务 (例如:数据处理)

编程模式

RAW API

内核回调型API,当初始化应用时,用户需要为不同内核事件注册所需的回调函数 (例如 TCP_Sent、 TCP_error…)。当相应事件发生时, LwIP 会自发地调用相关的回调函数。没有操作系统支持中,只能使用RAW API开发

API 说明
TCP连接建立 tcp_new 建立一个新的 TCP PCB (协议控制块)。
tcp_bind 将 TCP PCB 绑定到本地 IP 地址和端口。
tcp_listen 启动 TCP PCB 上的监听进程。
tcp_accept 注册回调函数,连接成功建立后调用。
tcp_connect 发送连接建立请求。
发送TCP数据 tcp_write 将发送数据写入 TCP 缓冲区中。
tcp_sent 注册回调函数,数据发送成功后调用。
tcp_output 发送 TCP 缓冲区中的数据
接收TCP数据 tcp_recv 注册回调函数, TCP 接收到数据后调用
应用轮询 tcp_poll 注册回调函数,TCP慢定时器调用(500ms一次)。
关闭并终止连接 tcp_close 主动关闭。
tcp_err 注册回调函数,出错时调用。
tcp_abort 中止连接,向远程主机发送 RST。
udp_new 创建新的 UDP PCB。
udp_remove 移除 UDP PCB 并释放相关资源。
udp_bind 将 UDP PCB 与本地 IP 地址和端口绑定。
udp_connect 建立 UDP PCB 远程 IP 地址和端口。
udp_disconnect 移除 UDP PCB 远程 IP 和端口。
udp_send 发送 UDP 数据
udp_recv 注册回调函数,当收到新数据报时即对其调用。

Netconn API

Netconn API 为高层有序 API,其执行模型基于典型的阻塞式打开 - 读 - 写 - 关闭机制。 若要正常工作,此 API 必须处于多线程工作模式,该模式需为 LwIP TCP/IP 栈实现专用线程, 并 / 或为应用实现多个线程。

基于操作系统的IPC机制(即信号量和邮箱机制)实现的,将LWIP内核代码和网络应用程序分离成了独立的线程,因此LWIP内核线程就只负责数据包的TCP/IP封装和拆封,而不用进行数据的应用层处理,大大提高了系统对网络数据包的处理效率

API 说明
netconn_new 创建一个新连接。
netconn_delete 删除一个已有连接。
netconn_bind 将连接绑定到本地 IP 地址和端口。
netconn_connect 连接远程 IP 地址和端口。
netconn_send 通过 UDP 发送数据。
netconn_recv 接收数据。
netconn_listen 置 TCP netconn 处于监听模式。
netconn_accept 接受正在监听状态的 TCP 连接上的传入连接。
netconn_write 通过 TCP 数据 (将数据写入 TCP 缓冲区)。
netconn_close 主动关闭 TCP netconn。

Socket API

了标准 BSD 套接字 API。它是有序 API,在内部构建于 Netconn API 之上,对网络连接进行了高级的抽象,使得用户可以像操作文件一样操作网络连接

API 说明
socket 创建一个新套接字。
bind 将套接字绑定到 IP 地址和端口。
listen 监听套接字连接。
connect 将套接字连接到远程主机 IP 地址和端口。
accept 在套接字上接受新连接。
read 从套接字读取数据。
write 向套接字写入数据。
close 关闭套接字 (删除套接字)。

LwIP配置

lwipopts.h 文件允许用户充分配置栈及其所有模块。用户不需要定义 所有 LwIP 选项:如果未定义某选项,则使用 opt.h 文件中定义的默认值。因此, lwipopts.h 提供了覆盖许多 lwIP 行为的方法。

Lwip 内存选项 含义
MEM_SIZE LwIP 堆内存大小:用于所有 LwIP 动态内存分配
MEMP_NUM_PBUF MEM_REF 和 MEM_ROM pbuf 总数
MEMP_NUM_UDP_PCB UDP PCB 结构体的总数
MEMP_NUM_TCP_PCB TCP PCB 结构体的总数
MEMP_NUM_TCP_PCB_LISTEN 处于监听状态的 TCP PCB 总数
MEMP_NUM_TCP_SEG 最多同时在 TCP 缓冲队列中的报文段数量
PBUF_POOL_SIZE PBUF_POOL 类型的 pbuf 总数
PBUF_POOL_BUFSIZE PBUF_POOL 类型 pbuf 的大小
TCP_MSS 最大 TCP 报文段 (MTU - IP 报头大小 - TCP 报头大小)
TCP_SND_BUF 对于一个连接, TCP 的发送缓冲空间
TCP_SND_QUEUELEN TCP 发送队列中 pbuf 的最大数
TCP_WND TCP 接收窗大小

LwIP内存管理

LwIP有两种内存管理方式:内存池、内存堆

参考链接:https://blog.csdn.net/jiangjunjie_2005/article/details/26051399#t9

实现TCP服务器

echoserver实现:https://github.com/yangLieee/codebackup/tree/master/iperf

  1. 新建控制块

    使用tcp_new()函数建立一个TCP控制块。

  2. 绑定控制块

    对于服务器来说,新建一个控制快后,需要在控制块上绑定本地IP和端口,以方便客户端的连接。

  3. 控制块侦听

    使用tcp_listen函数,对于服务器来说,需要显性调用tcp_listen函数以使控制块进入监听状态,等待客户端的连接请求。

  4. 建立连接

    在tcp_listen函数进入服务器监听状态后,需要马上使用tcp_accept函数来注册一个接收处理函数,因为一旦有客户端连接请求被成功建立后,服务器就会调用这个处理函数。

  5. 接受并处理数据

    一旦连接成功,accept回调函数会调用tcp_recv函数注册一个接收完成的处理函数。对于服务器来说,接收到了客户端的数据或操作要求,就会调用这一回调函数进行处理。这其实是一个复杂的过程:接收到数据后,首先通知更新接受窗口(使用tcp_recved函数),处理并发送数据(使用tcp_write函数),数据发送成功则清除已发送的数据(使用tcp_sent函数),最后关闭连接(使用函数tcp_close)。

Q&A

  1. Q:tcp_recvd正确调用方法

    A:很多源码是在tcp_server_sent发送函数中调tcp_recvd,如果用户不是用来echo(立即发回相同的报文),而是真正要处理事务的时候,这种用法是错误的:此时,接收窗口大小由发送窗口决定,如果没有发送新数据,接收窗口就会越来越小,直到最后无法接收数据。正确的用法是:在收到数据后,应用层调用tcp_recved函数恢复原来的窗口大小;而在发送函数中,不用管窗口的事!

  2. Q:调用lwip iperf接口,无法关闭server?

    A:lwip iperf只是实现当client断开连接删除掉其相关pcb结构体,而没有断开tcp连接没有释放监听端口,仿照lwiperf_tcp_close函数实现一个新的针对server关闭的接口即可

参考资料

  1. LwIP应用开发指南
  2. 网络基础之协议栈
  3. 使用 LwIP TCP/IP 栈在 STM32Cube 上开发应用
  4. LwIP使用经验
  5. TCP回调函数是何时调用的
  6. 协议栈什么情况下发送 RST 标志