#lwIP 的 Raw API 使用指南
1. 简介
lwIP(Lightweight IP)是一个为嵌入式系统设计的开源轻量级 TCP/IP 协议栈。它旨在提供尽可能小的内存占用和高效的性能,适用于资源受限的设备,如物联网设备、路由器和工业控制系统。lwIP 支持多种协议,包括 IPv4、IPv6、TCP、UDP、ICMP 等,并提供多种 API 以适应不同的应用需求。
2. lwIP 提供的 API 接口
lwIP 提供三种应用程序接口(API)供程序使用,以与 TCP/IP 代码进行通信:
- 低级别 “核心” / “回调” 或 “Raw” API。
- 较高级别的 “顺序” API。
- 类似 BSD 的 Socket API。
2.1 顺序 API
顺序 API 为普通的、顺序执行的程序提供了一种使用 lwIP 栈的方式。它与 BSD socket API 非常相似。执行模型基于阻塞的打开-读取-写入-关闭范式。由于 TCP/IP 栈本质上是基于事件的,TCP/IP 代码和应用程序必须运行在不同的执行上下文(线程)中。
2.2 Socket API
Socket API 是一个兼容性 API,适用于现有的应用程序,目前构建在顺序 API 之上。它旨在为在其他平台(例如 Unix / Windows 等)上运行的 Socket API 应用程序提供所需的所有功能。然而,由于该 API 规范的限制,可能存在不兼容性,可能需要对现有程序进行小幅修改。
2.3 Raw API
Raw API 允许应用程序更好地与 TCP/IP 代码集成。通过在 TCP/IP 代码内部调用回调函数来实现事件驱动的程序执行。TCP/IP 代码和应用程序都在同一线程中运行。与顺序 API 相比,Raw API 在代码执行时间方面更快,内存占用也更少。但其缺点是编程开发较为复杂,使用 Raw API 编写的应用程序更难理解。然而,对于希望在代码大小和内存使用上保持小型的应用程序来说,这是首选方法。
3. 线程模型
lwIP 最初针对单线程环境设计。在添加多线程支持时,没有选择使核心线程安全,而是采用了另一种方法:运行 lwIP 核心的主线程(也称为 “tcpip_thread”)。Raw API 只能在这个主线程中使用! 使用顺序 API 或 Socket API 的应用线程通过消息传递与主线程通信。
因此,仅有限的函数列表可以从其他线程或中断服务程序(ISR)中调用!只有以下 API 头文件中的函数是线程安全的:
api.h
netbuf.h
netdb.h
netifapi.h
sockets.h
sys.h
此外,内存(分配和释放)函数可以在多个线程中调用(但不能在 ISR 中),前提是 NO_SYS=0
,因为它们受 SYS_LIGHTWEIGHT_PROT
和/或信号量的保护。
从版本 1.3.0 开始,如果设置了 SYS_LIGHTWEIGHT_PROT=1
和 LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT=1
,pbuf_free()
也可以从另一个线程或 ISR 中调用(仅在 PBUF_RAM
使用时,mem_free
可以从 ISR 中调用;否则,堆仅通过信号量保护)。
4. Raw API 详解
Raw TCP/IP 接口允许应用程序更紧密地与 TCP/IP 代码集成。通过在 TCP/IP 代码内部调用回调函数实现事件驱动的程序执行。TCP/IP 代码和应用程序都在同一线程中运行。
4.1 回调机制
程序执行由回调驱动。每个回调都是一个普通的 C 函数,由 TCP/IP 代码内部调用。每个回调函数会传递当前 TCP 或 UDP 连接状态作为参数。此外,为了能够保持程序特定的状态,回调函数会传递一个由程序指定的、独立于 TCP/IP 状态的参数。
设置应用连接状态
void tcp_arg(struct tcp_pcb *pcb, void *arg);
- 功能:设置传递给所有其他回调函数的程序特定状态。
- 参数:
pcb
:当前的 TCP 连接控制块。arg
:将传递给回调函数的参数。
4.2 TCP 连接设置
TCP 连接的设置函数类似于顺序 API 和 BSD Socket API。使用 tcp_new()
函数创建一个新的 TCP 连接标识符(即协议控制块 PCB)。然后,可以将其设置为监听新传入连接或显式连接到另一个主机。
创建新的 TCP PCB
struct tcp_pcb *tcp_new(void);
- 功能:创建一个新的连接标识符(PCB)。
- 返回值:如果没有可用内存,返回
NULL
,否则返回新的 PCB 指针。
绑定 PCB 到本地地址和端口
err_t tcp_bind(struct tcp_pcb *pcb, struct ip_addr *ipaddr, u16_t port);
- 功能:将 PCB 绑定到本地 IP 地址和端口号。IP 地址可以设置为
IP_ADDR_ANY
,以绑定到所有本地 IP 地址。 - 返回值:
ERR_USE
:如果另一个连接已绑定到相同端口。ERR_OK
:绑定成功。
开始监听传入连接
struct tcp_pcb *tcp_listen(struct tcp_pcb *pcb);
- 功能:将 PCB 设置为监听传入连接。当有传入连接被接受时,将调用通过
tcp_accept()
设置的回调函数。 - 返回值:返回一个新的连接标识符,用于监听连接。如果没有足够的内存,返回
NULL
。 - 注意:会释放传入的 PCB 所占用的内存,并分配一个新的更小的 PCB 用于监听连接。
设置接受回调函数
void tcp_accept(struct tcp_pcb *pcb, err_t (* accept)(void *arg, struct tcp_pcb *newpcb, err_t err));
- 功能:设置当有新的连接到达时调用的回调函数。
- 参数:
pcb
:监听的 PCB。accept
:回调函数,接收新连接时调用。
连接到远程主机
err_t tcp_connect(struct tcp_pcb *pcb, struct ip_addr *ipaddr, u16_t port, err_t (* connected)(void *arg, struct tcp_pcb *tpcb, err_t err));
- 功能:设置 PCB 以连接到远程主机,并发送初始的 SYN 段以打开连接。
- 返回值:
ERR_OK
:如果 SYN 段成功入队。ERR_MEM
:如果内存不足以入队 SYN 段。
- 注意:
tcp_connect()
函数立即返回,不会等待连接建立。连接建立后,将调用传递的connected
回调函数。如果连接无法建立,将调用通过tcp_err()
设置的错误回调函数。
4.3 发送 TCP 数据
TCP 数据的发送通过调用 tcp_write()
将数据入队。当数据成功发送到远程主机后,应用程序将通过指定的回调函数接收到通知。
发送数据
err_t tcp_write(struct tcp_pcb *pcb, void *dataptr, u16_t len, u8_t copy);
- 功能:将
dataptr
指向的数据入队,长度为len
字节。copy
参数为 0 或 1,指示是否为数据分配新内存并复制。copy = 1
:分配新内存并复制数据。copy = 0
:不分配新内存,直接引用数据指针。
- 返回值:
ERR_MEM
:如果数据长度超过当前发送缓冲区大小或发送队列超出lwipopts.h
中定义的上限。ERR_OK
:数据成功入队。
- 注意:
- 正确使用方法是调用
tcp_write()
时,最多传递tcp_sndbuf()
字节的数据。 - 如果返回
ERR_MEM
,应用程序应等待部分已入队数据被远程主机确认后再重试。
- 正确使用方法是调用
设置已发送回调函数
void tcp_sent(struct tcp_pcb *pcb, err_t (* sent)(void *arg, struct tcp_pcb *tpcb, u16_t len));
- 功能:设置当数据被远程主机确认时调用的回调函数。
- 参数:
pcb
:连接的 PCB。sent
:回调函数,接收确认的字节数。
4.4 接收 TCP 数据
TCP 数据的接收是基于回调的。当新数据到达时,将调用应用程序指定的回调函数。当应用程序处理完数据后,必须调用 tcp_recved()
函数,以便 TCP 可以增加接收窗口。
设置接收回调函数
void tcp_recv(struct tcp_pcb *pcb, err_t (* recv)(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err));
- 功能:设置当有新数据到达时调用的回调函数。如果远程主机关闭连接,将传递一个
NULL
的 pbuf。 - 参数:
pcb
:连接的 PCB。recv
:回调函数,接收新数据时调用。
- 注意:
- 如果没有错误且回调函数返回
ERR_OK
,则必须释放 pbuf。 - 否则,不能释放 pbuf,让 lwIP 核心代码保存它。
- 如果没有错误且回调函数返回
通知 lwIP 已接收数据
void tcp_recved(struct tcp_pcb *pcb, u16_t len);
- 功能:当应用程序接收到数据时调用,通知 lwIP 可以增加接收窗口。
- 参数:
pcb
:连接的 PCB。len
:接收数据的长度。
4.5 应用程序轮询
当连接处于空闲状态(即没有数据传输或接收)时,lwIP 会通过调用指定的回调函数来轮询应用程序。这可以用作看门狗定时器,以终止长时间空闲的连接,或者作为等待内存变得可用的方法。例如,如果 tcp_write()
返回内存不足,应用程序可以使用轮询功能在连接空闲一段时间后再次尝试调用 tcp_write()
。
设置轮询回调函数
void tcp_poll(struct tcp_pcb *pcb, err_t (* poll)(void *arg, struct tcp_pcb *tpcb), u8_t interval);
- 功能:设置轮询间隔和回调函数,当连接空闲时调用回调函数。
- 参数:
pcb
:连接的 PCB。poll
:回调函数,每隔interval
个 TCP 粗粒度定时器周期调用一次。interval
:轮询间隔,以 TCP 粗粒度定时器周期数表示,通常每秒两次。比如,interval = 10
表示每 5 秒轮询一次。
4.6 关闭和中止连接
关闭连接
err_t tcp_close(struct tcp_pcb *pcb);
- 功能:关闭连接。
- 返回值:
ERR_OK
:关闭成功。ERR_MEM
:内存不足,无法关闭连接。
- 注意:
- 如果关闭成功,函数返回
ERR_OK
,并且 PCB 将被自动释放。 - 如果内存不足,应用程序应等待并通过确认回调或轮询功能重试。
- 如果关闭成功,函数返回
中止连接
void tcp_abort(struct tcp_pcb *pcb);
- 功能:通过发送 RST(重置)段来中止连接,并释放 PCB。
- 注意:
- 永远不会失败。
- 在 TCP 回调中调用时,确保返回
ERR_ABRT
,否则可能导致访问已释放的内存或内存泄漏。
4.7 设置错误回调函数
void tcp_err(struct tcp_pcb *pcb, void (* err)(void *arg, err_t err));
- 功能:设置当连接因错误而中止时调用的回调函数。
- 参数:
pcb
:连接的 PCB。err
:回调函数,不会接收 PCB 作为参数,因为 PCB 可能已经被释放。
5. UDP 接口
UDP 接口类似于 TCP,但由于 UDP 的低复杂性,接口显著更简单。
5.1 创建 UDP PCB
struct udp_pcb *udp_new(void);
- 功能:创建一个新的 UDP PCB,用于 UDP 通信。
- 返回值:新的 UDP PCB 指针,若内存不足则返回
NULL
。
5.2 移除 UDP PCB
void udp_remove(struct udp_pcb *pcb);
- 功能:移除并释放 UDP PCB。
5.3 绑定 UDP PCB 到本地地址和端口
err_t udp_bind(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port);
- 功能:将 PCB 绑定到本地地址和端口。IP 地址可以设置为
IP_ADDR_ANY
,表示绑定到所有本地 IP 地址。 - 返回值:当前总是返回
ERR_OK
。
5.4 连接 UDP PCB 到远程地址和端口
err_t udp_connect(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port);
- 功能:设置 PCB 的远程端。这不会产生任何网络流量,只是设置 PCB 的远程地址。
5.5 断开 UDP 连接
err_t udp_disconnect(struct udp_pcb *pcb);
- 功能:移除 PCB 的远程端。不会产生任何网络流量,只是移除 PCB 的远程地址。
5.6 发送 UDP 数据
err_t udp_send(struct udp_pcb *pcb, struct pbuf *p);
- 功能:发送 pbuf
p
。不会释放 pbuf。
5.7 设置接收回调函数
void udp_recv(struct udp_pcb *pcb, void (* recv)(void *arg, struct udp_pcb *upcb, struct pbuf *p, struct ip_addr *addr, u16_t port), void *recv_arg);
- 功能:设置当收到 UDP 数据报时调用的回调函数。
- 参数:
pcb
:连接的 PCB。recv
:回调函数,当收到数据报时调用。recv_arg
:传递给回调函数的参数。
6. 系统初始化
系统初始化的完整和通用序列取决于构建配置(lwipopts.h
)及运行时环境的其他初始化(例如定时器)。以下是基于单一以太网接口、UDP 和 TCP 传输层、IPv4 和 DHCP 客户端的 Raw API 使用流程。
6.1 初始化步骤
按照以下顺序调用这些函数:
-
初始化统计信息
stats_init();
- 清除用于收集运行时统计信息的结构。
-
初始化系统
sys_init();
- 如果在
lwipopts.h
中设置了NO_SYS=1
,此函数用于简化配置更改。
- 如果在
-
初始化内存管理
mem_init();
- 初始化由
MEM_SIZE
定义的动态内存堆。
- 初始化由
-
初始化内存池
memp_init();
- 初始化由
MEMP_NUM_x
定义的内存池。
- 初始化由
-
初始化 pbuf 内存池
pbuf_init();
- 初始化由
PBUF_POOL_SIZE
定义的 pbuf 内存池。
- 初始化由
-
初始化 ARP
etharp_init();
- 初始化 ARP 表和队列。
- 注意:必须每隔
ARP_TMR_INTERVAL
(5 秒)调用etharp_tmr()
。
-
初始化 IP 层
ip_init();
- 目前没有太大作用,但应在后续可能的更改时调用。
-
初始化 UDP
udp_init();
- 清空 UDP PCB 列表。
-
初始化 TCP
tcp_init();
- 清空 TCP PCB 列表并清除一些内部 TCP 定时器。
- 注意:必须在此初始化后,定期调用
tcp_fasttmr()
和tcp_slowtmr()
。
-
添加网络接口
netif_add(struct netif *netif, struct ip_addr *ipaddr, struct ip_addr *netmask, struct ip_addr *gw, void *state, err_t (* init)(struct netif *netif), err_t (* input)(struct pbuf *p, struct netif *netif));
- 将你的网络接口添加到
netif_list
。 - 分配一个
struct netif
并传递指向该结构的指针作为第一个参数。 - 如果使用 DHCP,传递已清零的
ip_addr
结构;否则,填充为合理的数值。 init
函数指针必须指向以太网网络接口的初始化函数。示例如下:
err_t netif_if_init(struct netif *netif) {u8_t i;for(i = 0; i < ETHARP_HWADDR_LEN; i++) netif->hwaddr[i] = some_eth_addr[i];init_my_eth_device();return ERR_OK; }
- 对于以太网驱动,输入函数指针必须指向 lwIP 的
ethernet_input()
函数(在netif/etharp.h
中声明)。 - 对于其他驱动,必须使用
ip_input()
函数(在lwip/ip.h
中声明)。
- 将你的网络接口添加到
-
设置默认网络接口
netif_set_default(struct netif *netif);
- 注册默认网络接口。
-
启用网络接口
netif_set_up(struct netif *netif);
- 当网络接口完全配置后,必须调用此函数。
-
启动 DHCP 客户端
dhcp_start(struct netif *netif);
- 为此接口创建一个新的 DHCP 客户端。
- 注意:必须之后定期调用
dhcp_fine_tmr()
和dhcp_coarse_tmr()
。
6.2 示例:初始化 lwIP 网络接口
#include "lwip/netif.h"
#include "lwip/tcpip.h"
#include "lwip/dhcp.h"/* 定义网络接口结构体 */
struct netif netif0;/* 初始化网络接口 */
void lwip_network_init(void) {ip4_addr_t ipaddr, netmask, gw;/* 设置初始IP地址为0.0.0.0 */IP4_ADDR(&ipaddr, 0, 0, 0, 0);IP4_ADDR(&netmask, 0, 0, 0, 0);IP4_ADDR(&gw, 0, 0, 0, 0);/* 初始化TCP/IP堆栈 */tcpip_init(NULL, NULL);/* 添加网络接口 */netif_add(&netif0, &ipaddr, &netmask, &gw, NULL, low_level_init, tcpip_input);/* 设置为默认网络接口 */netif_set_default(&netif0);/* 启动DHCP客户端 */dhcp_start(&netif0);
}
7. lwIP API 使用
lwIP 提供了多种 API 以适应不同的应用层需求。以下主要介绍 Socket API 和 Raw API 的使用方法。
7.1 Socket API
Socket API 类似于 BSD Socket,适用于网络应用开发,如 HTTP 服务器、TCP 客户端等。
创建 TCP 服务器
#include "lwip/sockets.h"void vTCPServer(void *pvParameters) {int listen_fd, conn_fd;struct sockaddr_in server_addr, client_addr;socklen_t client_len = sizeof(client_addr);char buffer[1024];int n;/* 创建套接字 */listen_fd = lwip_socket(AF_INET, SOCK_STREAM, 0);if (listen_fd < 0) {/* 处理错误 */}/* 配置服务器地址 */server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);server_addr.sin_addr.s_addr = htonl(INADDR_ANY);/* 绑定套接字 */if (lwip_bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {/* 处理错误 */}/* 监听连接 */if (lwip_listen(listen_fd, 5) < 0) {/* 处理错误 */}/* 接受客户端连接 */while (1) {conn_fd = lwip_accept(listen_fd, (struct sockaddr *)&client_addr, &client_len);if (conn_fd < 0) {/* 处理错误 */continue;}/* 处理客户端请求 */while ((n = lwip_recv(conn_fd, buffer, sizeof(buffer), 0)) > 0) {lwip_send(conn_fd, buffer, n, 0);}lwip_close(conn_fd);}
}
创建 TCP 客户端
#include "lwip/sockets.h"void vTCPClient(void *pvParameters) {int sock_fd;struct sockaddr_in server_addr;char *message = "Hello, Server!";char buffer[1024];int n;/* 创建套接字 */sock_fd = lwip_socket(AF_INET, SOCK_STREAM, 0);if (sock_fd < 0) {/* 处理错误 */}/* 配置服务器地址 */server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);server_addr.sin_addr.s_addr = inet_addr("192.168.1.100");/* 连接服务器 */if (lwip_connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {/* 处理错误 */}/* 发送消息 */lwip_send(sock_fd, message, strlen(message), 0);/* 接收响应 */if ((n = lwip_recv(sock_fd, buffer, sizeof(buffer), 0)) > 0) {/* 处理接收到的数据 */}lwip_close(sock_fd);
}
7.2 Raw API
Raw API 提供更底层的接口,适用于对性能要求高或需要自定义协议处理的应用。
创建一个简单的 TCP 客户端
#include "lwip/tcp.h"struct tcp_pcb *client_pcb;
struct pbuf *p_tx;static err_t tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err) {if (err == ERR_OK) {/* 连接成功,发送数据 */const char *data = "Hello, Raw TCP!";tcp_write(tpcb, data, strlen(data), TCP_WRITE_FLAG_COPY);tcp_output(tpcb);/* 关闭连接 */tcp_close(tpcb);}return ERR_OK;
}void vRawTCPClient(void *pvParameters) {/* 创建 TCP PCB */client_pcb = tcp_new();if (client_pcb != NULL) {/* 连接到服务器 */ip_addr_t server_ip;IP4_ADDR(&server_ip, 192, 168, 1, 100);err_t err = tcp_connect(client_pcb, &server_ip, 8080, tcp_client_connected);if (err != ERR_OK) {/* 处理连接错误 */}}
}
8. 常见配置选项
lwIP 的功能通过 lwipopts.h
文件中的宏定义进行配置。以下是一些常用的配置选项:
内存管理
#define MEM_SIZE (16 * 1024)
#define MEMP_NUM_PBUF 128
#define MEMP_NUM_TCP_PCB 32
#define MEMP_NUM_TCP_SEG 256
#define MEMP_NUM_UDP_PCB 16
#define MEMP_NUM_REASSDATA 10
协议选项
#define LWIP_TCP 1
#define LWIP_UDP 1
#define LWIP_IPV4 1
#define LWIP_IPV6 0
调试与跟踪
#define LWIP_DEBUG 0
#define LWIP_STATS 1
任务配置(与 FreeRTOS 集成时)
#define TCPIP_THREAD_NAME "TCP/IP"
#define TCPIP_THREAD_STACKSIZE 512
#define TCPIP_MBOX_SIZE 32
#define DEFAULT_TCP_RECVMBOX_SIZE 32
#define DEFAULT_UDP_RECVMBOX_SIZE 32
9. 使用示例
以下是一个完整的示例,展示如何在 FreeRTOS 环境中集成 lwIP,并创建一个简单的 TCP 服务器。
9.1 初始化 lwIP 网络接口
#include "lwip/netif.h"
#include "lwip/tcpip.h"
#include "lwip/dhcp.h"/* 定义网络接口结构体 */
struct netif netif0;/* 初始化网络接口 */
void lwip_network_init(void) {ip4_addr_t ipaddr, netmask, gw;/* 设置初始IP地址为0.0.0.0 */IP4_ADDR(&ipaddr, 0, 0, 0, 0);IP4_ADDR(&netmask, 0, 0, 0, 0);IP4_ADDR(&gw, 0, 0, 0, 0);/* 初始化TCP/IP堆栈 */tcpip_init(NULL, NULL);/* 添加网络接口 */netif_add(&netif0, &ipaddr, &netmask, &gw, NULL, low_level_init, tcpip_input);/* 设置为默认网络接口 */netif_set_default(&netif0);/* 启动DHCP客户端 */dhcp_start(&netif0);
}
9.2 创建 TCP 服务器任务
#include "lwip/sockets.h"
#include "FreeRTOS.h"
#include "task.h"void vTCPServerTask(void *pvParameters) {int listen_fd, conn_fd;struct sockaddr_in server_addr, client_addr;socklen_t client_len = sizeof(client_addr);char buffer[1024];int n;/* 创建套接字 */listen_fd = lwip_socket(AF_INET, SOCK_STREAM, 0);if (listen_fd < 0) {/* 处理错误 */vTaskDelete(NULL);}/* 配置服务器地址 */server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);server_addr.sin_addr.s_addr = htonl(INADDR_ANY);/* 绑定套接字 */if (lwip_bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {/* 处理错误 */lwip_close(listen_fd);vTaskDelete(NULL);}/* 监听连接 */if (lwip_listen(listen_fd, 5) < 0) {/* 处理错误 */lwip_close(listen_fd);vTaskDelete(NULL);}/* 接受并处理客户端连接 */while (1) {conn_fd = lwip_accept(listen_fd, (struct sockaddr *)&client_addr, &client_len);if (conn_fd < 0) {/* 处理错误 */continue;}/* 处理客户端请求 */while ((n = lwip_recv(conn_fd, buffer, sizeof(buffer), 0)) > 0) {/* 回显接收到的数据 */lwip_send(conn_fd, buffer, n, 0);}/* 关闭连接 */lwip_close(conn_fd);}
}
9.3 主函数
#include "lwip_network_interface.h"
#include "tcp_server_task.c"
#include "FreeRTOS.h"
#include "task.h"int main(void) {/* 系统初始化代码,如硬件初始化 *//* 初始化网络接口 */lwip_network_init();/* 创建 TCP 服务器任务 */xTaskCreate(vTCPServerTask, "TCPServer", 1024, NULL, tskIDLE_PRIORITY + 1, NULL);/* 启动调度器 */vTaskStartScheduler();/* 程序不应到达这里 */for (;;) {}
}
10. 优化建议
10.1 优化校验和
首先要优化的是 lwip_standard_checksum()
函数,该函数位于 src/core/inet.c
。可以通过以下方式覆盖标准校验和函数,以提高性能:
#define LWIP_CHKSUM <your_checksum_routine>
可以参考 inet.c
中的 C 示例,或者编写一个汇编函数符合 RFC1071 规范。
10.2 优化字节序转换
如果使用小端架构,可以通过提供汇编或内联的 htons()
和 htonl()
函数进行优化:
#define LWIP_PLATFORM_BYTESWAP 1
#define LWIP_PLATFORM_HTONS(x) <your_htons>
#define LWIP_PLATFORM_HTONL(x) <your_htonl>
10.3 调整网络接口驱动
检查网络接口驱动是否以高于最大线速的速度读取数据。如果硬件没有被及时服务,可能会导致缓冲区溢出。举例来说,如果使用 cs8900
驱动,应尽可能频繁调用 cs8900if_service(ethif)
。在使用 RTOS 时,可以让 cs8900
中断唤醒一个高优先级的任务,通过二值信号量或事件标志服务驱动。
10.4 关闭统计信息
对于生产发布版本,建议将 LWIP_STATS
设置为 0。注意,简单地设置较高的内存选项值并不会显著影响速度性能。
10.5 零拷贝 MAC
为了实现发送数据的零拷贝,传递给 Raw API 的数据必须保持不变,直到发送完成。具体要求如下:
- 对于
PBUF_RAM
或PBUF_POOL
的 pbuf,应用程序在数据被入队后不应修改数据,除非其引用计数为 1。 - 对于
PBUF_ROM
或PBUF_REF
,数据也必须保持不变,但栈/驱动程序会复制PBUF_REF
的数据进行入队,而PBUF_ROM
的 pbuf 则直接入队(因期望 ROM 数据永不更改)。 - 使用
tcp_write()
时,如果copy
标志为 0,传递的数据指针对应的数据不能被修改。
11. 关键注意事项
- 内存管理:确保
lwipopts.h
中的内存配置适合你的应用需求,避免内存不足或浪费。 - 多线程安全:在多任务环境下,lwIP 的 API 调用应确保线程安全,通常通过配置
NO_SYS
和启用任务锁来实现。 - 网络接口驱动:正确实现网络接口驱动是确保 lwIP 正常工作的关键,需根据具体硬件平台进行定制。
- 调试与监控:启用调试和跟踪功能,有助于开发过程中问题的排查与性能优化。
12. 总结
lwIP 是一个功能强大且灵活的轻量级 TCP/IP 协议栈,适用于各种嵌入式系统。通过正确的配置和集成,可以实现高效的网络通信功能。在 FreeRTOS 等 RTOS 环境中使用 lwIP,可以充分利用 RTOS 的任务调度能力,提高系统的实时性和响应速度。掌握 lwIP 的基本使用方法和关键配置选项,是开发网络嵌入式应用的基础。
参考资料
- lwIP 官方文档
- FreeRTOS 集成 lwIP 指南
- lwIP GitHub 仓库
致谢
感谢 Adam Dunkels 为嵌入式网络通信所做的贡献,lwIP 为开发者提供了一个高效、可靠的网络协议栈解决方案。
相关文章:
#lwIP 的 Raw API 使用指南
1. 简介 lwIP(Lightweight IP)是一个为嵌入式系统设计的开源轻量级 TCP/IP 协议栈。它旨在提供尽可能小的内存占用和高效的性能,适用于资源受限的设备,如物联网设备、路由器和工业控制系统。lwIP 支持多种协议,包括 I…...
Elasticsearch开启认证及kibana密码登陆
Elasticsearch不允许root用户运行,使用root用户为其创建一个用户es,为用户es配置密码,并切换到es用户。 adduser elastic passwd elastic su elasticElasticsearch(简称ES)是一个基于Lucene的搜索服务器。它提供了一个分布式、多用户能力的全文搜索引擎,基于RESTful web…...
【论文阅读】Large Language Models for Equivalent Mutant Detection: How Far Are We?
阅读笔记:Large Language Models for Equivalent Mutant Detection: How Far Are We? 1. 来源出处 本文发表于《ISSTA’24, September 16–20, 2024, Vienna, Austria》会议,由Zhao Tian, Honglin Shu, Dong Wang, Xuejie Cao, Yasutaka Kamei和Junji…...
vue2 面试题带答案,万字总结
1. 什么是 vue Vue 是一套用于构建用户界面的渐进式框架。Vue.js 的主要特点:渐进式框架、声明式渲染、组件化、响应式数据绑定等; 2、MVC 和 MVVM 区别 MVC 是模型(model)-视图(view)-控制器(controller),控制器负责…...
git的常用用法(最简精华版)
一、工作区域(工作区,暂存区,本地仓库) 1、工作区 当前正在使用的文件 2、暂存区 已使用add命令提交的工作区的文件,会保存到暂存区 3、本地仓库 已使用commit命令提交的暂存区的文件,会保存到本地仓库。…...
哥德巴赫猜想渐行渐远
我现在的工作,表明经典分析可能出了问题,如此则连Vinogradov的三素数定理都不成立了,更别说基于L-函数方程的陈氏定理“12”了。事实上即使L-函数方程成立,由于我指出Siegel定理不成立,陈景润和张益唐的工作就不成立。…...
Spring Boot应用开发实战:构建高效、可维护的Web应用
Spring Boot应用开发实战:构建高效、可维护的Web应用 在当今快速迭代的软件开发环境中,Spring Boot凭借其“约定优于配置”的理念,迅速成为Java开发者构建微服务及Web应用的首选框架。它不仅简化了Spring应用的初始搭建以及开发过程,还通过自动配置、嵌入式服务器等特性,…...
keep-alive多级页面缓存实现
文章目录 keep-alive多级页面缓存实现只适用于页面是否缓存状态不变的情况对于上面的问题提供一种解决方案 keep-alive多级页面缓存实现 只适用于页面是否缓存状态不变的情况 网上有一种很普遍的教程,不使用keep-alive的include属性,而是通过在路由表中…...
ks 小程序sig3
前言 搞了app版的快手之后 (被风控麻了) 于是试下vx小程序版的 抓包调试 小程序抓包问题 网上很多教程, github也有开源的工具代码 自行搜索 因为我们需要调试代码,所以就用了下开源的工具 (可以用chrome的F12功能&a…...
图论之构造完全图
题目 2398: 信息学奥赛一本通T1489-构造完全图 时间限制: 2s 内存限制: 192MB 提交: 16 解决: 9 题目描述 对于完全图 G,若有且仅有一棵最小生成树为 T,则称完全图 G 是树 T 扩展出的。 给你一棵树 T,找出 T 能扩展出的边权和最小的完全图 G…...
RDD触发算子:一些常用的触发算子(count、foreach、saveAsTextFile、first)
文章目录 1、count算子功能语法 2、foreach算子功能语法 3、saveAsTextFile算子功能语法 4、first算子功能语法举例 1、count算子 功能 统计RDD集合中元素的个数,返回一个int值 语法 def count(self) -> int2、foreach算子 功能 对RDD中每个元素调用一次参数中…...
搭建RAGFlow
RAGFlow 是一款基于深度文档理解构建的开源 RAG(Retrieval-Augmented Generation)引擎。RAGFlow 可以为各种规模的企业及个人提供一套精简的 RAG 工作流程,结合大语言模型(LLM)针对用户各类不同的复杂格式数据提供可靠…...
css中的box-sizing,记录
border-box:最终高度为height,默认包含padding border等属性 content-box:box-sizing默认值,最终大小为heightpaddingborder 等...
使用useCallback引发对闭包的理解
一、先简单介绍一下闭包: 闭包是 JavaScript 中的重要概念,它指的是一个函数可以“记住”并访问其词法作用域,即使在这个函数的外部被执行。简单来说,闭包是由函数及其相关的环境组合而成的。 闭包的特性 函数内部可以访问外部变量: 闭包…...
gvim添加至右键、永久修改配置、放大缩小快捷键、ctrl + c ctrl +v 直接复制粘贴、右键和还原以前版本(V)冲突
一、将 vim 添加至右键 进入安装目录找到 vim91\install.exe 管理员权限执行 Install will do for you:1 Install .bat files to use Vim at the command line:2 Overwrite C:\Windows\vim.bat3 Overwrite C:\Windows\gvim.bat4 Overwrite C:\Windows\evim.bat…...
腾讯云-COS
COS 对象存储 是一种可扩展的云端数据存储服务。它适用于存储任意类型的文件,并且可以针对这些文件进行访问控制。 CORS 跨域资源共享 是一种机制,它使用额外的HTTP头来告诉浏览器允许一个域上的Web应用请求另一个域上的资源。当需要从一个域名下的网页向…...
蓝桥杯每日真题 - 第16天
题目:(卡牌) 题目描述(13届 C&C B组C题) 解题思路: 题目分析: 有 n 种卡牌,每种卡牌的现有数量为 a[i],所需的最大数量为 b[i],还有 m 张空白卡牌。 每…...
基因组之全局互作热图可视化
引言 PlotHiC 是一个专为 Hi-C 数据可视化分析而设计的 Python 包。Hi-C 技术是一种能够检测染色体三维结构的实验方法,它能揭示 DNA 在细胞核内的三维组织结构。为了更好地展示和解释这些复杂的数据,PlotHiC[1] 可以帮助用户方便地绘制Hi-C 数据的热图。…...
基于Lora通讯加STM32空气质量检测WIFI通讯
目录 目录 前言 一、本设计主要实现哪些很“开门”功能? 二、电路设计原理图 1.电路图采用Altium Designer进行设计: 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着环境污染问题的日益严重,空气质量的监测与管理已经…...
STM32 极速入门第一天基础拓展 驱动i2c屏幕 ( 使用PlatformIO开发STM32单片机 )
输入输出模式解析 输出模式 在输出模式下,通常不需要设置上下拉电阻. 输出电平由 LL_GPIO_SetOutputPin 和 LL_GPIO_ResetOutputPin 函数直 接控制。 输入模式 在输入模式下,设置上下拉电阻是非常重要的. 输入引脚悬空时可能会导致不确定的电平…...
【WPF】Prism学习(五)
Prism Commands 1.错误处理(Error Handling) Prism 9 为所有的命令(包含AsyncDelegateCommand)提供了更好的错误处理。 避免用try/catch包装每一个方法根据不同遇到的异常类型来提供特定的逻辑处理可以在多个命令之间共享错误处…...
RabbitMQ的基本概念和入门
RabbitMQ 的基本概念和入门 RabbitMQ 是一款流行的开源消息队列中间件,实现了高级消息队列协议(AMQP)。它使用Erlang语言编写,具备高可用性、可扩展性和易用性等特点,广泛应用于各种分布式系统中。本文将详细介绍Rabb…...
Shell脚本6 -- 条件判断if
声明: 本文的学习内容来源于B站up主“泷羽sec”视频【shell编程(4)脚本与用户交互以及if条件判断】的公开分享,所有内容仅限于网络安全技术的交流学习,不涉及任何侵犯版权或其他侵权意图。如有任何侵权问题,…...
经验笔记:从生成 SSH 密钥到成功连接测试(以Gitee为例)
从生成 SSH 密钥到成功连接测试的经验笔记(以Gitee为例) 1. 生成 SSH 密钥对 选择合适的加密算法 ED25519: 密钥长度:私钥 256 位(32 字节),公钥 256 位(32 字节)&#…...
Object.defineProperty和响应式
Object.defineProperty()是一个监听对象属性变化的方法。一般情况下我们是不会直接使用的,或者说我们遇到的场景还没有这么高级。 最有名的例子就是Vue2的响应式实现,就是通过这个方法来实现的。 用起来不难,就是个API,只是用的…...
前端web
题目:制作带有下拉悬停菜单的导航栏 效果图 一、先制作菜单栏 <body> <div id"menu"> <div id"container"> <div class"item">游戏1 <div cla…...
DDNet 服务器配置教程 Linux 环境
DDNet 服务器配置教程 Linux 环境 配置之前可以参考一下官方网址给出的内容 官方网址:DDNet官方 环境说明 OS: Debian 11 安装 可以直接从官网下载,也可以使用这个链接: Linux_DDNet 下载链接 上文中给的链接会因为更新而出现版本落后的情况&#x…...
Vue 2 —监视器实现动态切换表单属性值
目录 一、需求背景 二、监视器语法 三、实例展示 1、HTML部分 2、JS部分 四、使用场景总结 1. 表单验证 2. 动态更新 UI 3. 数据同步 4. 计算属性的替代方案 计算属性的优势 : 简洁性: 监视器的优势 : 灵活性: 多属性依赖: 副…...
Qt_day10_程序打包(完结)
目录 1. 设置图标 2. Debug和Release版本 3. 动态链接库 4. 打包 5. 联系项目要求 Qt开发的程序最终都是要给用户使用的,用户的电脑上不可能装一个Qt的开发环境导入项目使用。因此项目项目开发完成后需要打包——制作成安装包,用户直接下载并安装即可使用…...
golang通用后台管理系统09(系统操作日志记录)
1.日志工具类 package log/**** 日志记录 wangwei 2024-11-18 15:30*/ import ("log""os""path/filepath""time" )// 获取以当前日期命名的日志文件路径 func getLogFilePath() string {currentDate : time.Now().Format("2006-…...
网站集约整合建设交流/seo快速排名软件方案
1. 下载electron文件: git clone https://github.com/electron/electron-quick-start cd electron-quick-start npm install npm start2. 更改vue配置 将下载的electron文件中的main.js复制到vue项目根目录下并改名为electron.js 更改路径:在项目confi…...
网站建设费用多少钱/seo营销排名
工作中我们可能会需要将EXCEL中做好的表格导入到WORD里面来,今天我们要讲的不是简单的复制,而是要在WORD中导入一个具有完整功能的EXCEL表格。第一步:我们需要打开一个WORD文档和一个EXCEL表格;第二步:选择EXCEL表格中需要导入到W…...
wordpress apache 404/网络销售管理条例
以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。 在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) 表示将…...
wordpress 评论时间/网络服务商怎么咨询
上代码: :http-request"(file)>imgUploadLogin(file, 上传的参数)"这是upload组件的上传文件成功时的钩子。 文件上传一、 methods:{imgUploadLogin(file, name) {//name就是上传的参数let content file.file;let data new FormData();data.appen…...
武汉建设局网站/企业官网
/*** 该示例展示了创建不同样式的 GeoODLine*/ var app new THING.App(); app.background [0, 0, 0]THING.Utils.dynamicLoad(https://www.thingjs.com/uearth/uearth.min.js, function () {// 创建一个地图var map app.create({type: Map,attribution: Google,style: {nigh…...
wordpress 讲解/广州seo公司排行
1.问题现场 目的:去掉字符串中的特殊符号,比如\t \r \n。 结果:同样的正则处理代码,得到的结果不同。 maxos: # -*- encoding:utf-8 -*- import torch, re s [可怜][可怜][可怜][可怜]那天下像张开的弓࿰…...