简介
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
新建控制块
使用tcp_new()函数建立一个TCP控制块。
绑定控制块
对于服务器来说,新建一个控制快后,需要在控制块上绑定本地IP和端口,以方便客户端的连接。
控制块侦听
使用tcp_listen函数,对于服务器来说,需要显性调用tcp_listen函数以使控制块进入监听状态,等待客户端的连接请求。
建立连接
在tcp_listen函数进入服务器监听状态后,需要马上使用tcp_accept函数来注册一个接收处理函数,因为一旦有客户端连接请求被成功建立后,服务器就会调用这个处理函数。
接受并处理数据
一旦连接成功,accept回调函数会调用tcp_recv函数注册一个接收完成的处理函数。对于服务器来说,接收到了客户端的数据或操作要求,就会调用这一回调函数进行处理。这其实是一个复杂的过程:接收到数据后,首先通知更新接受窗口(使用tcp_recved函数),处理并发送数据(使用tcp_write函数),数据发送成功则清除已发送的数据(使用tcp_sent函数),最后关闭连接(使用函数tcp_close)。
Q&A
-
A:很多源码是在tcp_server_sent发送函数中调tcp_recvd,如果用户不是用来echo(立即发回相同的报文),而是真正要处理事务的时候,这种用法是错误的:此时,接收窗口大小由发送窗口决定,如果没有发送新数据,接收窗口就会越来越小,直到最后无法接收数据。正确的用法是:在收到数据后,应用层调用tcp_recved函数恢复原来的窗口大小;而在发送函数中,不用管窗口的事!
Q:调用lwip iperf接口,无法关闭server?
A:lwip iperf只是实现当client断开连接删除掉其相关pcb结构体,而没有断开tcp连接没有释放监听端口,仿照lwiperf_tcp_close函数实现一个新的针对server关闭的接口即可