diff --git a/rt-thread-version/rt-thread-standard/_sidebar.md b/rt-thread-version/rt-thread-standard/_sidebar.md index f1f07cbfce750372ab0c96872134c2cc9dd5efa5..f1596596342e586f26817924664beee9d51c3099 100644 --- a/rt-thread-version/rt-thread-standard/_sidebar.md +++ b/rt-thread-version/rt-thread-standard/_sidebar.md @@ -3,7 +3,9 @@ - **RT-Thread 标准版本** - 简介 + - [RT-Thread 简介](/rt-thread-version/rt-thread-standard/README.md) + - 快速上手 - [Keil模拟器STM32F103](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/stm32f103-simulator.md) - [RT-Thread潘多拉STM32L475](/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/quick-start.md) @@ -15,6 +17,7 @@ - [野火I.MX RT1052](/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/quick-start.md) - [正点原子号令者I.MX RT1052](/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/quick-start.md) - [其他开发板...](/rt-thread-version/rt-thread-standard/tutorial/quick-start/more.md) + - 内核 - [内核基础](/rt-thread-version/rt-thread-standard/programming-manual/basic/basic.md) - [线程管理](/rt-thread-version/rt-thread-standard/programming-manual/thread/thread.md) @@ -24,6 +27,7 @@ - [内存管理](/rt-thread-version/rt-thread-standard/programming-manual/memory/memory.md) - [中断管理](/rt-thread-version/rt-thread-standard/programming-manual/interrupt/interrupt.md) - [内核移植](/rt-thread-version/rt-thread-standard/programming-manual/porting/porting.md) + - 设备和驱动 - [I/O设备模型](/rt-thread-version/rt-thread-standard/programming-manual/device/device.md) - [UART设备](/rt-thread-version/rt-thread-standard/programming-manual/device/uart/uart.md) @@ -43,17 +47,22 @@ - [CRYPTO 设备](/rt-thread-version/rt-thread-standard/programming-manual/device/crypto/crypto.md) - [AUDIO设备](/rt-thread-version/rt-thread-standard/programming-manual/device/audio/audio.md) - [Pulse Encoder设备](/rt-thread-version/rt-thread-standard/programming-manual/device/pulse_encoder/pulse_encoder.md) + - 组件 + - 网络组件 + - [net 组件总概](/rt-thread-version/rt-thread-standard/programming-manual/net/net_introduce.md) + - [AT命令](/rt-thread-version/rt-thread-standard/programming-manual/at/at.md) + - [Lwip协议栈](/rt-thread-version/rt-thread-standard/programming-manual/lwip/lwip.md) + - [netdev网卡](/rt-thread-version/rt-thread-standard/programming-manual/netdev/netdev.md) + - [SAL套接字抽象层](/rt-thread-version/rt-thread-standard/programming-manual/sal/sal.md) - [FinSH控制台](/rt-thread-version/rt-thread-standard/programming-manual/finsh/finsh.md) - [虚拟文件系统](/rt-thread-version/rt-thread-standard/programming-manual/filesystem/filesystem.md) - - [netdev网卡](/rt-thread-version/rt-thread-standard/programming-manual/netdev/netdev.md) - - [SAL套接字抽象层](/rt-thread-version/rt-thread-standard/programming-manual/sal/sal.md) - - [AT命令](/rt-thread-version/rt-thread-standard/programming-manual/at/at.md) - [ulog日志](/rt-thread-version/rt-thread-standard/programming-manual/ulog/ulog.md) - [utest测试框架](/rt-thread-version/rt-thread-standard/programming-manual/utest/utest.md) - [动态模块](/rt-thread-version/rt-thread-standard/programming-manual/dlmodule/dlmodule.md) - [POSIX接口](/rt-thread-version/rt-thread-standard/programming-manual/posix/posix.md) - [电源管理](/rt-thread-version/rt-thread-standard/programming-manual/pm/pm.md) + - 软件包 - 物联网 - [网络工具集 (NetUtils) 应用](/rt-thread-version/rt-thread-standard/application-note/packages/netutils/an0018-system-netutils.md) @@ -70,6 +79,7 @@ - [MicroPython 固件开发指南](/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/firmware-develop.md) - [MicroPython C 模块扩展](/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/external_c_modules.md) - [更多软件包...](/rt-thread-version/rt-thread-standard/packages-manual/more.md) + - 应用开发 - 开发环境搭建 - [在windows平台使用QEMU运行RT-Thread](/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/an0006-qemu-windows.md) @@ -140,9 +150,12 @@ - [RT-Thread连接ROS控制小车](/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/ros-camera-car.md) - [RT-Thread连接RPLidar激光雷达](/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/rplidar-connect.md) - [RT-Thread搭配ROS实现目标检测小车](/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/object-detection.md) + - 代码贡献 - [传感器驱动开发指南](/rt-thread-version/rt-thread-standard/development-guide/sensor/sensor_driver_development.md) - [软件包开发指南](/rt-thread-version/rt-thread-standard/development-guide/package/package.md) - [向RT-Thread贡献代码](/rt-thread-version/rt-thread-standard/development-guide/github/github.md) + - [RT-Thread 标准版的版本选择](/rt-thread-version/rt-thread-standard/application-note/setup/rt-thread-version/an0030-rtthread-version.md) + - [API参考手册](https://www.rt-thread.org/document/api/) diff --git a/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/eth_frame.png b/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/eth_frame.png new file mode 100644 index 0000000000000000000000000000000000000000..4163be68cedf8b47f052428a7cfb985c1aaff9d5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/eth_frame.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/eth_frame_information.png b/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/eth_frame_information.png new file mode 100644 index 0000000000000000000000000000000000000000..f5036f79bafd1790945c7caa614bca548c50caa0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/eth_frame_information.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/ip_frame_information.png b/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/ip_frame_information.png new file mode 100644 index 0000000000000000000000000000000000000000..d0672793a145a96bcba8d4c3897705ed41d15be8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/ip_frame_information.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/lwip_menuconfig.png b/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/lwip_menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..052f2aa2ba660abe6c21e68f340bd3f0196917e9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/lwip_menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/net-osi.png b/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/net-osi.png new file mode 100644 index 0000000000000000000000000000000000000000..a2659a07fe2138b2f454748fc9d7b23c25cde069 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/net-osi.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/tcpip.png b/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/tcpip.png new file mode 100644 index 0000000000000000000000000000000000000000..59318b1ac5f5f8d4a78d4981fa884efe7933fbea Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/lwip/docs/tcpip.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/lwip/lwip.md b/rt-thread-version/rt-thread-standard/programming-manual/lwip/lwip.md new file mode 100644 index 0000000000000000000000000000000000000000..c7eef1b4875e94e5cf44437e4da10de37a035d49 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/lwip/lwip.md @@ -0,0 +1,229 @@ +# RT-Thread 的 lwip 协议栈 + +lwip 是一个轻量级的 TCP/IP 协议栈,在 RT-Thread 中提供三个版本供开发者使用:lwip1.4.1,lwip2.0.2,lwip2.1.2 。而且配置界面相当复杂,有很多参数需要我们配置。```menuconfig```配置界面如下: + +![](./docs/lwip_menuconfig.png) + +这内容太多了,想要一口气全部了解,简直是不可思议的事情。所以,还是在文章的一开始阐明这篇文章的目的: + +1. IP 报文的基本知识,IP,ICMP,IGMP,ARP 报文的概念 +2. TCP 报文与 UDP 报文概念 +3. RT-Thread 中 lwip 的内存分配策略 +4. 解决常见配置项的作用讲解,及配置方式 + +一点一点的,一小步一小步得帮助大家理解 lwip 在 RT-Thread 的 net 组件中的地位,以及,问题如何定位(至少发问题贴的时候,让大佬能看懂想问啥),和一些配置项的意义,希望大家在阅读完本篇文章后,能树立起对 lwip 的兴趣和信心,继续在网络世界中遨游。 + +### 1. TCP / IP 的网络分层模型 + +讲解之前,需要再次申明 lwip 的地位: [**A Lightweight TCP/IP stack**](http://savannah.nongnu.org/projects/lwip/),一个轻量级的 TCP/IP 协议栈。lwip 所实现的,是 TCP/IP 协议栈的一部分内容。不过已经实现的内容已经满足嵌入式系统的的需求,甚至在性能方面完全超出使用需求,而且是较完整的。 + +![](./docs/net-osi.png) + +![](./docs/tcpip.png) + +本篇文章的主要内容:IP,ICMP,ARP,TCP,UDP 都集中在网络层与传输层中。按照分层协议的习惯,层与层之间的数据按照习惯应该是不可见的,也就是说TCP在填充数据时是不知道 IP 层是怎么处理 TCP 的数据的。但是在 lwip 中,这个限制并不是那么严格,因为 pbuf 结构的存在;IP 报文,乃至 ETH 链路上的数据实际上相同的一块或者多块区域;一旦发送的数据确定,pbuf 中的数据通常是不会频繁得在网络接口层 / 网络层 / 传输层 这三个的解析过程中拷贝数据。具体的内容在**lwip 的内存管理办法**中说明。 + +IP,ICMP,ARP,TCP,UDP 的报文格式,想必大家就算没有听说过,也该了解他们都是有自己的格式的;对于这些报文格式的单个解析,网络上已经有大量大佬有相当充实的介绍了,在这里就不单个拿出来继续描述,大家可以使用该[链接](https://www.baidu.com/),使用 ```xxx报文格式``` 的关键词进行搜索,或者能找到《TCP/IP详解 卷1:协议》,有兴趣情况下进行研读也是蛮好的。 + +在下面贴两张比较重要的图,通过这两张图,相信大家会有 IP 报文的问题,ETH 报文都是什么样子,他们的内容都大概有什么有点了解。 + +![](./docs/eth_frame.png) + +**注:图片来源于《TCP/IP详解 卷1:协议》,链路层,以太网与IEEEE 802封装 小节** + +* ARP 请求可以看到,它是和 IP 报文平行的,ARP 报文并不从属于 IP 报文;ARP 也是在路由器,交换机,在局域网络中经常会看到 ARP 的报文。Wireshark 能清晰得捕捉到网络中的 ARP 报文。如果 ARP 过程不顺利,那么任何数据都不能从 ETH 中发出。ARP 沟通了 IP 地址与 MAC 地址;如果在局域网中有一台网络设备使用 ARP 报文使坏,很有可能该局域网络瘫痪。 + +![](./docs/ip_frame_information.png) + +**注:图片来源于《TCP/IP详解 卷1:协议》,TCP/IP的分成 小节** + +![](./docs/eth_frame_information.png) + +**注:图片来源于《TCP/IP详解 卷1:协议》,概述,封装 小节** + +* 通过上面两张图片可以认识到 IP 报文的主要内容有 TCP、UDP、ICMP 的内容。 +* IP 报文的首部有段空间是协议域;1 表示为 ICMP 协议,6 表示为 TCP 协议,17 表示为 UDP 协议。 +* 可以看到 MTU = 1500 的意义是 IP 报文的总长为 1500 字节,对于 ETH 帧来说应该是 1518 字节,此时 MSS 的大小为 1460,也就是 “应用数据” 的长度;所以在 RT-Thread 的底层驱动中,底层的长度一般是大于 1518 字节的。 +* PING 命令发送的内容,实际上是 ICMP 报文;既不是 TCP 也不是 UDP,在 Socket 申请时属于 ```NETCONN_RAW```。在 AT 设备中,通常是没有这个选项的;AT 设备中 PING 功能是对外封装的具体 AT 功能,而不能由 socket 申请对应的 ICMP 结构报文。 + + + +### 2. lwip 的基础内存情况 + +在 RT-Thread 的 lwip 协议栈中,对内存堆的分配方法做了调整;在 lwip 原生的内存堆算法 ```src/core/mem.c```并没有参与工程的编译,取而代之的是,在```src/arch/sys_arch.c```重新定义的内存堆的相关申请函数。pbuf ,内存堆,内存池这三个概念,在 lwip 中很常见;在 menuconfig 的 lwip 配置界面,对一些资源的配置,很多都是一些内存空间的大小配置。在带大家浏览一些配置前,先描述一下 pbuf ,内存堆,内存池的概念。 + +* pbuf ,pbuf 的结构是 lwip 特殊的结构;首先,pbuf 是一个特殊的数据存储结构; + +```c +/** Main packet buffer struct */ +struct pbuf { + /** next pbuf in singly linked pbuf chain */ + struct pbuf *next; + + /** pointer to the actual data in the buffer */ + void *payload; + + /** + * total length of this buffer and all next buffers in chain + * belonging to the same packet. + * + * For non-queue packet chains this is the invariant: + * p->tot_len == p->len + (p->next? p->next->tot_len: 0) + */ + u16_t tot_len; + + /** length of this buffer */ + u16_t len; + + /** pbuf_type as u8_t instead of enum to save space */ + u8_t /*pbuf_type*/ type; + + /** misc flags */ + u8_t flags; + + /** + * the reference count always equals the number of pointers + * that refer to this pbuf. This can be pointers from an application, + * the stack itself, or pbuf->next pointers from a chain. + */ + u16_t ref; +}; +``` + +pbuf 有一个 *next 指针,这代表 pbuf 可以使用该指针实现类似数据链条的效果;在 pbuf 存储的数据,有可能是一个整块的空间,也有可能是多个空间片链接在一起。除此以外,pbuf 在网络层 / 传输层 这两个层级之间都是可见的;这意味着 pbuf 在申请后不需要因为层与层的数据解析方式不同而频繁拷贝数据,可以节省系统资源。各个层级在进行内存申请时,都可以调用 pbuf_alloc 函数实现内存申请。 + +**通过上一段描述,我们至少会有两个疑问?**一,各个层级不同申请 pbuf 时候,需要开辟多少空间呢?比如 ICMP 报文的申请,总和 UDP 报文的申请不同吧,为什么 pbuf 可以保证不需要频繁拷贝数据。二,pbuf 是从哪里申请的,内存池和内存堆,从那个地方来申请呢?如果对于 pbuf 有更多的疑问,可以阅读 ```src/core/pbuf.c```中的源码来进行了解;或者该[链接](https://www.baidu.com/),使用 ```lwip协议栈pbuf分析``` 的关键词进行搜索;也可以通过《嵌入式网络哪些事》来学习 lwip 的基础知识。 + +1. 对于第一个疑问,不同层级申请 pbuf 时,需要开辟的空间大小是不同的;正如上图的[以太网帧的模型](###1. TCP / IP 的网络分层模型)所描述的,不同层级在申请 pbuf 时,会预留的空间是不一致的。 + +```c +typedef enum { + PBUF_TRANSPORT, + PBUF_IP, + PBUF_LINK, + PBUF_RAW_TX, + PBUF_RAW +} pbuf_layer; + +/* determine header offset */ + switch (layer) { + case PBUF_TRANSPORT: + /* add room for transport (often TCP) layer header */ + offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN; + break; + case PBUF_IP: + /* add room for IP layer header */ + offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN; + break; + case PBUF_LINK: + /* add room for link layer header */ + offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN; + break; + case PBUF_RAW_TX: + /* add room for encapsulating link layer headers (e.g. 802.11) */ + offset = PBUF_LINK_ENCAPSULATION_HLEN; + break; + case PBUF_RAW: + /* no offset (e.g. RX buffers or chain successors) */ + offset = 0; + break; + default: + LWIP_ASSERT("pbuf_alloc: bad pbuf layer", 0); + return NULL; + } +``` + +不同层级在申请 pbuf 时,因为入参 layer 的不同,会给 pbuf 设置一个 offset 。这个 offset 可以在其他层级进行处理时直接向对应的空间区域填充数据,而不需要进行拷贝操作。所以,pbuf 结构才被称为可以在不同层级之间可见的结构,因为自始至终都是同一个 pbuf 区域。 + +2. 对于第二个提问,pbuf 的空间是由谁提供。实际上,pbuf 的空间的提供,既有内存堆,也有内存池。这个申请的空间的不同,是由申请 pbuf 的入参决定的。 + +```c +typedef enum { + /** pbuf data is stored in RAM, used for TX mostly, struct pbuf and its payload + are allocated in one piece of contiguous memory (so the first payload byte + can be calculated from struct pbuf). + pbuf_alloc() allocates PBUF_RAM pbufs as unchained pbufs (although that might + change in future versions). + This should be used for all OUTGOING packets (TX).*/ + PBUF_RAM, + /** pbuf data is stored in ROM, i.e. struct pbuf and its payload are located in + totally different memory areas. Since it points to ROM, payload does not + have to be copied when queued for transmission. */ + PBUF_ROM, + /** pbuf comes from the pbuf pool. Much like PBUF_ROM but payload might change + so it has to be duplicated when queued before transmitting, depending on + who has a 'ref' to it. */ + PBUF_REF, + /** pbuf payload refers to RAM. This one comes from a pool and should be used + for RX. Payload can be chained (scatter-gather RX) but like PBUF_RAM, struct + pbuf and its payload are allocated in one piece of contiguous memory (so + the first payload byte can be calculated from struct pbuf). + Don't use this for TX, if the pool becomes empty e.g. because of TCP queuing, + you are unable to receive TCP acks! */ + PBUF_POOL +} pbuf_type; +``` + +如果在查看 RT-Thread 的源码,比如 drv_eth.c 会发现,是 ```p = pbuf_alloc(PBUF_RAW, len, PBUF_RAM)```,即```PBUF_RAM```;而在另一些驱动中,例如 ```componets/drivers/wlan/wlan_lwip.c```的驱动中,是优先 ```pbuf_alloc(PBUF_RAW, len, PBUF_POOL)```方式。这里面没有类似于 PBUF_POOL 比 PBUF_RAW 更快,更好的说法;使用情况会根据系统资源的情况来自行调整。 + +---- + +**lwip 的内存堆与内存池都是为 pbuf 服务的吗?**这肯定不止,那内存堆与内存池的空间还用在什么部分呢?这就牵扯到接下来要描述的 menuconfig 配置的。诸如netconns, raw connect, udp socket, tcp socket, tcp segment 的这些参数的数量的意义: + +| 配置选项 | 配置大小 | 意义 | +| ------------- | -------- | ------- | +| the number of struct netconns | 8 | lwip 中可以同时申请的 socket 数量,由 MEMP_NETCONN 限制 | +| the number of raw connection | 4 | IP 层面的连接结构体数量,例如 ICMP/ IGMP | +| the number of UDP socket | 4 | UDP 的可以连接数量 | +| the number of TCP socket | 4 | TCP 可用的数量,在创建一个 TCP 连接时创建,一但转为 Listen 状态则删除 | +| the number of TCP segment | 40 | TCP 数据段,牵扯到分片,或者TCP缓存的数据维护,尽量发送大块数据等作用的维护 | +| the number of listening TCP connections[defaults,8] | 8 | 用作服务器时,设置为处于 Listen 状态的 TCP 控制块,不与 tcp socket 共享,是独立的 | + +对于上面的参数,基本都是由 MEMP_POOL ,也就是内存池实现的;而且,讲道理也应该由内存池来实现。内存池的优势就是分配迅速,没有碎片化问题,而且大小固定;对于这些长度固定的 PCB 控制块来说,是很完美的配合。 + +对于内存池的空间分配的代码,在```src/include/lwip/memp_std.h```与```src/core/memp.c```中实现;相信第一次在看这个文件时候,都是懵逼的。对于 ```##```拼接符,以及```.h```文件的作用空间都会是一个很好的学习平台。在这两个文件中,可以看到 PBUF_POOL 到底是有多少空间,以便于我们在使用 socket 编程时,能顺心得按照自己工程的需求来裁剪 lwip 的内存大小。 + +--- + +在 lwip 的配置项界面有这个配置项 + +| 配置选项 | 配置大小 | 意义 | +| ------------- | -------- | ------- | +| the number of pbuf | 16 | 一个pubf 的大小为 (1460[MSS] + 40[TCP HEAD] + 18[ETH]),16 个就是 20 KB | + +在驱动编写时,一直使用```pbuf_alloc(PBUF_RAW, len, PBUF_RAM)```的方式,很明显不会使用到这个空间。 + +---- + +* 不能傻傻分不清 PBUF_RAW,PBUF_RAM;两个虽然很像,但是前者表示空间的作用(TCP,UDP,IP报文);或者表示空间归属(来自内存堆[RAM]还是内存池[POOL])。 + +### 3. RT-Thread 的中 lwip 一般配置项 + +在上面的内容中,讲述了一些 lwip 内部的内存分配问题;下面要描述得是一些比较简单的配置项,这些配置项基本和概念有关。 + +| 配置选项 | 配置大小 | 意义 | +| ------------- | -------- | ------- | +| the size of send buffer | 8196 | TCP 发送缓存空间 | +| the size of TCP send window | 8196 | TCP 窗口大小 | +| the priority level value of ethernet thread | 10 | tcpip 线程优先级 | +| the number of mail in the lwIP thread mailbox | 8 | tcpip 邮箱大小,用于各种 tcpip 邮件的响应 | +| the stack size of lwIP thread | 1024 | tcpip 线程栈大小,如果启用 PPP ,需要调大的就是这个线程栈 | +| the priority level value of ethernet thread | 12 | RT-Thread 实现的底层的发送和接收线程优先级 | +| the stack size of ethernet thread | 1024 | RT-Thread 实现的底层的发送和接收线程的线程栈大小 | +| Enable IP reassembly and frag | 0 | 是否允许 IP 报文分片和重组 | +| Enable netif status callback | 1 | 启用或者禁用网卡的回调 | +| Enable netif link status callback | 1 | 链路连接或者断开的回调 | +| SO_REUSEADDR / SO_RCVTIMEO / SO_SNDTIMEO/SO_RCVBUF / SO_LINGER | 0 /1 | 一些特殊的 TCP/IP 协议处理方法 | +| Enable netif loopback | 0 | 使能 netif 的回环功能;可以自发自收,不需要硬件支持 | +| Enable lwIP statistics | 0 | 启用信息统计功能 | +| Enable hardware checksum | 0 | 启用硬件校验,是校验和算法 | +| Enable lwIP Debugging Options | 0 | 启用 lwip 的 debug 功能,功能丰富,建议出问题时开打需要的选项 | + +从名称看不出来功能的一些配置项,在这里做出一些情况的解释;并且可以追溯到 lwip 协议栈的功能选项。对于 RT-Thread 而言,lwip 的配置项最终会在 ```src/include/lwip/opt.h```文件中反应出来,是由 ```src/include/lwip/opt.h```,```src/lwipopts.h```,```rtconfig.h``` 三个文件共同作用的结果。对于上面的大小配置,实际上如果乱改的话,连编译都是过不去的。因为 ```src/core/init.c``` 中会在编译时就检查相关宏配置是否合法,在调整时还是应该了解一些基本的 TCP/IP 知识,再次推荐《TCP/IP详解 卷1:协议》,不需要完全看完,而且还有电子版的书。 + +### 4. 写在最后 + +lwip 协议栈是实现了 TCP/IP 协议栈一部分功能的,可以用在嵌入式中的网络协议栈;有些函数并不能完全兼容,比如 SO_REUSEADDR 参数,在大型的网络协议栈中,很有可能是 ```setsockopt```的一个入参,而对于 lwip 来说,是一个一旦编译好就固定的策略,不是一个灵活的配置项。在进行对应的 APP 功能移植时,应当慎重处理编译产生的异常。通过 sal 的方式使用 lwip,at socket,wiznet 来进行编程,一定要注意编译产生一些异常事件和函数异常事件。lwip 是一个可以运行在嵌入式中的网络协议栈,它的高效,精简的编程方式和策略都很值得我们学习;希望在 lwip 的学习道路上,能和 RT-Thread 一起分享自己的经验和心得,贡献自己的理解和感悟,共同成长进步。 + + + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/net/docs/Kconfig_file.png b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/Kconfig_file.png new file mode 100644 index 0000000000000000000000000000000000000000..b09bc5a14815ae09371e08969dd339a939ef9744 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/Kconfig_file.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/net/docs/at_framework.jpg b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/at_framework.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f5c868dc6c26a7715b9946817c68ca24d9be68cd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/at_framework.jpg differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/net/docs/at_menuconfig.png b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/at_menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..aad48215e95c44fb2757b7ac4786f5bf07209400 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/at_menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/net/docs/ifconfig_cmd.png b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/ifconfig_cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..93c51879cedd74ac7544f44073966c6d51c08e93 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/ifconfig_cmd.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/net/docs/net-osi.png b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/net-osi.png new file mode 100644 index 0000000000000000000000000000000000000000..a2659a07fe2138b2f454748fc9d7b23c25cde069 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/net-osi.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/net/docs/network_frame.jpg b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/network_frame.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5980ac6d929c4aa3387a02c6305c876a328de658 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/network_frame.jpg differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/net/docs/ping_cmd.png b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/ping_cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..8363066bec20c05fb965382c460a00311460e134 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/net/docs/ping_cmd.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/net/net_introduce.md b/rt-thread-version/rt-thread-standard/programming-manual/net/net_introduce.md new file mode 100644 index 0000000000000000000000000000000000000000..55ba28fce1aa0aa14ec95e51e8a79f85ac0be9c0 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/net/net_introduce.md @@ -0,0 +1,203 @@ +# RT-Thread 提供的网络世界入口 - net 组件 + +作为一款在 RTOS 领域对网络支持很丰富的 RT-Thead,对设备联网功能的支持的工具就是接下来要讨论的 net 组件。它位于 ```./rt-thread/components/net``` 路径下;作为一款基础组件, env 与 Studio 的工程配置界面的配置项都依赖该目录下的 Kconfig 文件。我们对网络功能的选择,或者其配置都属于该 Kconfig 的管理范畴。 + +![](./docs/Kconfig_file.png) + +从图片中的信息也可以了解到,AT,Lwip,Netdev,Sal 这些我们相对熟悉的功能也位于该目录中。这四个基础组件,可以驱动的网络设备的种类多到各个方面。从有线网络的各种 ETH 的方式,带 PHY 的 enc28j60,纯接口的 RJ45,还有自带网络协议栈的 W5500都可以直接经过配置,通过我们的 net 组件实现设备联网的需求;除了有线网络的接入方式,无线网络的接入方式则更多,2G模块,3G模块,4G模块,Cat-1模块,Cat-4模块,NB-IOT模块,乃至 5G 模块这些依赖基站运营商的入网方式,例如 SIM800,EC20,AIR720,L610,N58,M5311 等,这些不同厂家,不同工作频率的模组均可以通过 NET 组件入网;WIFI 这种无需运营商直接提供的网络的入网方式,例如 ESP8266,W60x,rw007 等。 + +通过上面的简单介绍,大家应该可以基本了解 Net 组件的作用,即为使用 RT-Thread 的设备提供上网能力。上面的联网设备,品类繁多,方式各异,但其实追踪到依赖的基础 net 能力其实也没有多复杂。实际上大部分的功能依赖的基础能力,只有两组而已,即 AT ,Lwip;而到用户,也就开发者层面,大部分就只用到 Sal 这一个概念。 + +![](./docs/network_frame.jpg) + +相信大家看过这份[文档](https://www.rt-thread.org/document/site/programming-manual/sal/sal/ ),主要描述了 Sal 提供的编程接口,也就是大家常用的 socket 接口;通过 Sal 接口大家已经可以实现 MQTT,NTP,TFTP,TCP Client,TCP Server,webclient,UDP Client,UDP Server,webnet 等这些大家经常会使用的工具。像浏览器一样访问网站,webclient;模拟一个网站,webnet;连接各种云平台,Mqtt;获取准确授时,NTP ;通过网络传输文件 ,TFTP ;诸如此类功能, Sal 提供了轻松访问网络的一把钥匙。你可以理解这把钥匙是向上的,但是这篇文章现在要讲的,是向下的,看看 Sal 这样组件,又是谁在支撑着它。 + +Net 组件的主要内容有 4 个方面,分别是 sal ,AT,lwip 与 netdev,我们先从 AT 和 Lwip 讲起,在不同的方式中介绍 sal 的用途;然后讲 netdev 的作用。 + + + +### 1. 总概 + +对于一个给定的模组,他的联网方式有时并不是单一的;以常见的 Cat-4 (4G) 模块为例,一方面它可以使用 AT 命令这种经典的方式来实现网络连接;另一方面又可以通过 PPP 拨号,使用 lwip 的方式来实现网络连接。或者在以 W60x 为例,一方面可以使用 AT 的方式来实现联网功能;另一方面又可以使用裸 IP 包的方式来实现网络连接。**在选取上网模块时,应当考虑片上资源的情况,以及网络质量的问题,选择比较适合的联网方式。**条条大陆通罗马,短途长途,步行开车还是有些小区别的。 + +### 2. AT + +![](./docs/at_framework.jpg) + +> AT 命令一开始并没有那么丰富,一开始是用作拨号的功能,也就是电话业务方面的内容。后来随着时间发展,AT 命令的范围也一步步进行了扩展,而逐渐支持了网络的一些功能,例如 TCP ,UDP 的功能;而到现在,有些模块厂商甚至直接使用 AT 命令扩展的方式,实现了诸如 MQTT,HTTP,HTTPS 之类的功能。这些使用扩展 AT 命令方式实现的上层应用对模块依赖度高,在 RT Thread 的 IOT Package 的栏目中,可以留意到这样的软件包,例如:[bc28_mqtt](https://github.com/luhuadong/rtt-bc28-mqtt)。RT-Thread 的 AT socket 功能,则是更接近底层,实现 TCP UDP 这样的接口,通过 SAL 来实现 MQTT 这种上层应用。 + +RT-Thread 的 AT 组件的设计目的就是使设备能发送和解析 AT 命令。为了到达这个目的,契合 AT 命令的常见逻辑,提供了AT client 功能和 AT Server 功能。 + +* 对于 AT Client 来说,是由 MCU 去向 Modem 发送 AT 命令,由 Modem 响应命令,并给出回复。这种方式我们常用的,也是我们通过支持 AT 的模组上网的常用方式。 +* 对于 AT Server 来说,是由 MCU 作为类似 Modem 的功能,由其他 MCU 向这个 MCU 发送 AT 命令,由这个 MCU 响应 AT 命令并做出回复。 + +具体的 AT 的使用,可以参考 [AT 文档](https://www.rt-thread.org/document/site/programming-manual/at/at/);更加详细,底层的实操的内容在专门的文档中有描述;在查阅文档时,可以同时浏览代码,通过文章的说明,我们应该能了解到 AT 组件的使用方法。AT 组件的适用范围也进而清晰起来: + +* AT 命令用作模块控制 +* AT 命令用作数据的解析 + +对 AT Client 功能做大规模应用的软件包为 [at_device ](https://github.com/RT-Thread-packages/at_device),作为一款使用频次很高的软件包,可以看到他充分利用了 AT 组件的能力,一方面针对模块控制的能力设置了 at_device_xxx.c 来提供模块的基础控制操作;一方面也提供了 at_socket_xxx.c 来提供数据解析能力。对于整体的 net 组件而言,at_socket_xxx.c 实际上沟通了 AT 组件与 Sal,使得上层应用能依赖 at_socket_xxx.c 的函数,实现对应的网络功能 MQTT,HTTP.... + +```c +static const struct sal_socket_ops at_socket_ops = +{ + at_socket, + at_closesocket, + at_bind, + NULL, + at_connect, + NULL, + at_sendto, + at_recvfrom, + at_getsockopt, + at_setsockopt, + at_shutdown, + NULL, + NULL, + NULL, +#ifdef SAL_USING_POSIX + at_poll, +#endif /* SAL_USING_POSIX */ +}; + +static const struct sal_netdb_ops at_netdb_ops = +{ + at_gethostbyname, + NULL, + at_getaddrinfo, + at_freeaddrinfo, +}; +``` + +从 AT 组件的 ```at/at_socket/at_socket.h``` 中可以看到 AT 组件实现的 sal 的**一部分函数**,从这些函数命令也能看出来 at_socket 是不支持 server 模式的。 + +对于 at_device 软件包来说,at_socket_xxx.c 的内容,也主要是对 at_socket.h 内容的实现,例如: + +```c +/* AT socket operations function */ +struct at_socket_ops +{ + int (*at_connect)(struct at_socket *socket, char *ip, int32_t port, enum at_socket_type type, rt_bool_t is_client); + int (*at_closesocket)(struct at_socket *socket); + int (*at_send)(struct at_socket *socket, const char *buff, size_t bfsz, enum at_socket_type type); + int (*at_domain_resolve)(const char *name, char ip[16]); + void (*at_set_event_cb)(at_socket_evt_t event, at_evt_cb_t cb); + int (*at_socket)(struct at_device *device, enum at_socket_type type); +}; +``` + +如果你能浏览对应的 at_device 中 at_socket_xxx.c 的代码,你可以发现几乎都是上述函数的实现,都是根据不同的模块的特性来处理对应逻辑。 + +----- + +在论坛中也能看到 at_device 的[影响](https://club.rt-thread.org/ask/search.html?q=AT): + +> 1. [at_device使用bc28报错](https://club.rt-thread.org/ask/question/430690.html) +> 2. [4.0.3版本串口接收buff设置大于128时,使用at_device有问题](https://club.rt-thread.org/ask/question/429740.html) +> 3. [air720 at-device回复命令冲突](https://club.rt-thread.org/ask/question/429519.html) +> 4. [AT组件接收不到数据](https://club.rt-thread.org/ask/question/430679.html) +> 5. ..... + +除了这些使用 at_device 能搜索到的问题,还有一些因为使用了 at_device 而产生的问题;这些问题的出现,对于在使用 AT 组件时的使用体验影响是很大的。 + +在遇到问题时,希望能小心得验证问题,尝试解决问题;在问题难以解决时,详细描述问题复现的场景,按照格式在论坛中提问。问题描述清晰,复现手段准确,更能吸引小伙伴们的参与,共同解决问题。 + +![](./docs/at_menuconfig.png) + +* AT RAW 能准确显示 AT 交互中的 modem 返回的数据,再数据异常需要调试时,建议打开。 + +市面上的各种模块,at_device 已经囊括了最常使用的一些部分,不过还有更多的模块等待着小伙伴们一起帮忙完善。也许是因为在小伙伴提交代码时并没有那么多的业务量或者功能,在一次次的功能增加迭代中,导致了 at_device 有时不能做到随取随用而产生了一些问题,也希望使用的小伙伴能发现问题,做出修改并给予 at_device 软件包自己的 PR,一起推动 at_device 更加易用。 + +### 3. Lwip + +![](./docs/net-osi.png) + +大家基本都是看过这个 TCP/IP 模型的,在嵌入式的网络协议栈入口中,lwip 就是很重要的一个入口; lwip 是开源的一个协议栈,已经经过很多厂家和开发者使用后,得到验证和肯定的嵌入式 TCP/IP 网络协议栈,lwip 的全称为 [**A Lightweight TCP/IP stack**](http://savannah.nongnu.org/projects/lwip/),一个轻量级的 TCP/IP 协议栈。 + +所以可以知道:lwip 并不是嵌入式上唯一的 TCP/IP 协议栈,但 lwip 是经过验证的,能胜任工作的 TCP/IP 协议栈。在 RT-Thread 中,提供了三个 lwip 的版本供用户使用: lwip1.4.1,lwip2.0.2,lwip2.1.2 ;通常大家在使用有线连接的方式使用网络时,一部分是使用的 lwip 协议栈,而另一部分则是使用了 w5500 这种方式来实现。 + +难道 lwip 只有在有线网络中才能使用吗?其实,也不尽然。对于使用裸 IP 包方式实现的网络数据的交互,而不是简单使用 AT 命令的话;rw007 这种 WIFI 设备,Air720 这种 Cat-4 设备,都是可以使用 Lwip 来使用网络功能的。 + +| | AT 方式 | lwip 方式 | +| :----: | :----------------------------------------------------------: | :----------------------------------------------------------: | +| rw007 | [AT 命令实现数据交互](https://github.com/RT-Thread-packages/at_device)(与模块版本有关) | [裸 IP 包形式实现数据交互](https://github.com/RT-Thread-packages/rw007) | +| Air720 | [AT 命令实现数据交互](https://github.com/RT-Thread-packages/at_device) | [PPP 包形式实现数据交互](https://github.com/RT-Thread-packages/ppp_device) | + +通过表格可以发现 net 组件的意图:即通过数据交互的方式来实现网络数据传输。对于 AT 方式来说,网络数据在 AT 命令中,需要从 AT 数据中解析出想要使用的网络数据;这种方式不需要 Lwip 庞大的协议栈,但很明显需要比较强大的解析能力,而且频繁转换解析对网络效率也是有影响的。对于 lwip 方式来说,网络数据直接以 IP/PPP 包的方式传输,这是网络世界通用的格式;无论是我们的交换机,路由器,它们都能识别 IP 报文,更加轻便而且无需进行复杂的相互转换,但这种方法需要有 lwip 协议栈的参与,需要根据项目对 lwip 做一些细微得调整。 + +在 RT-Thread 中,我们对 lwip 做了一些小修改,也使用 Kconfig 的方式提供了一些简单的优化配置项供用户进行简单的配置。在上面提到,我们对 lwip 有一些修改,主要的修改内容包括 netif 与 mem 方面;在 netif 中,我们在上层抽象了 netdev 的结构,对于 lwip 而言是对 netif 的继承和常用的一些接口函数的函数,而对于不是使用 lwip 实现的网络通信功能来说,netdev 可以使开发者更轻松的使用;在 mem 方面,我们使用 RT-Thread 自身的内存分配算法取代了 lwip 的内存堆分配算法,**不包括内存池部分**。查看 lwipx.x.x 下的j脚本文件 ```Sconscript ```文件,可以留意到```.\src\core\mem.c```文件是没有参与编译的;也就是说在使用 ```pbuf_alloc(PBUF_RAW, len, PBUF_RAM)```语句来申请 pbuf 空间时,我们实际上使用得是 RT-Thread 通用的内存堆算法。**当然无论是 lwip 还是 RT-Thread ,内存堆与内存池的申请确实是各有优劣的,还是需要针对使用场景才能确定孰优孰劣。**在本篇文章中,主要讲解 lwip 在 net 中的作用,具体的 lwip 配置方法和内容讲解会在另一篇文章中仔细描述。 + +### 4. W5500 + +我们经常使用的 net 组件中的两项已经介绍了基本情况;除了这两种方式,w5500 作为一个拥有 TCP/IP 协议栈的一个独立模块,也是一种通用的方式。在 RT-Thread 中使用 w5500 的方式也很简单。只需在工程中使用 [wiznet 软件包](https://github.com/RT-Thread-packages/wiznet)就可以使用标准的 BSD socket 接口来实现网络编程了。 + +![](./docs/network_frame.jpg) + +通过网络框架图可以看到:wiznet 是独立与 AT socket 与 lwip 的,但还是需要 netdev 与 SAL 的参与才能轻松的使用。 + +从sal,netdev,w5500 的对接过程来看,在 BSD socket 的接口方面,只是实现了常用的一些接口;这些接口已经囊括了 IOT 设备中会使用的常用能力。 + +```c +static const struct sal_socket_ops wiz_socket_ops = +{ + wiz_socket, + wiz_closesocket, + wiz_bind, + wiz_listen, + wiz_connect, + wiz_accept, + wiz_sendto, + wiz_recvfrom, + wiz_getsockopt, + wiz_setsockopt, + wiz_shutdown, + NULL, + NULL, + NULL, +#ifdef SAL_USING_POSIX + wiz_poll, +#endif /* SAL_USING_POSIX */ +}; + +static const struct sal_netdb_ops wiz_netdb_ops = +{ + wiz_gethostbyname, + NULL, + wiz_getaddrinfo, + wiz_freeaddrinfo, +}; +``` + +在 w5500 的使用中,也有一部分小伙伴会遇到问题。比如,一些 liunx 上的网络应用在迁移到 w5500 的平台上时会出现一些参数的不兼容问题。比如设置某些 socket 的属性,设置一个 TCP 的属性等等;有些设置在 linux 平台上可能是可以使用的,有些则可能会产生编译或者执行过程的逻辑异常,不符合函数预期。 + +为了在嵌入式平台上实现网络功能,AT命令,lwip 协议栈,wiznet 这些方法都是实现了 TCP/IP 协议中的一大部分功能;并没有完全实现整个协议栈的内容。可能有些函数特性在嵌入式的平台上是不太符合预期的。这时候就需要发现问题的小伙伴及时在论坛中提出问题,更多的小伙伴参与讨论,才能使我们的 net 组件功能更丰富。 + +### 5. Netdev + +在 lwip 的介绍中,我们提到 netdev 是对 lwip 中 netif 的继承。因为 netdev 从 netif 中取出了一些字节用于关键信息的填充。我们在 cmd 控制台上,经常会用到 ```ifconfig```命令,然后得到下面的回复: + +![](./docs/ifconfig_cmd.png) + +在控制台中可以显示每个注册为 netdev 的信息,IP地址,网关地址,子网掩码,以及 DNS 服务器地址;还有 MTU 长度,MAC 地址,以及该 netdev 的属性(是否启用,链路是否正常,网络是否正常,DHCP 是否打开,ARP 功能是否开启,广播是否支持,IGMP组播能力是否支持)。这些内容中经常使用到的一般有三个属性: + +| 重要属性 | 意义 | +| --------------------------- | ------------ | +| UP / DOWN | 网卡是否启用 | +| LINK_UP / LINK_DOWN | 链路是否正常 | +| INTERNET_UP / INTERNET_DWON | 网络是否正常 | + +* LINK_UP 的意义为链路是否正常,在 netdev 启用后,LINK_UP 是作为 PING 命令是否可用的关键。如果 为 LINK_DOWN ,PING 命令将禁用。毕竟,网线都没有连接,PING 命令不可以用也是合情合理的喽。**LINK_DOWN 状态下禁用 PING 命令很合理,但是如果 netdev 的状态被异常置为 LINK_DOWN 就很苦恼了。如果被代码错误执行到设置为 LINK_DOWN,PING 命令将不可用。**netdev 的使用,可以参考[文档介绍](https://www.rt-thread.org/document/site/programming-manual/netdev/netdev/)。 +* INTERNET_UP 的意义为网络是否正常。实际上,RT-Thread 的代码逻辑中,把这个位作为标志位,但很少利用这个标志位做逻辑判断。这样做的原因,是因为判断 INTERNET_UP 的方式为访问 ```link.rt-thread.org```,如果能有回复,就把标志位置为 INTERNET_UP,否则置为 INTERNET_DOWN。这个逻辑在一般情况下算是正常,但是在一些网络拓扑中,很有可能被网络标记位不可达(被墙了,PS :也有可能是 RT-Thread 的网络主机重启了),所以哪怕实际上是可以连接网络的,却显示为 INTERNET_DOWN。 + +除此以外,netdev 还有 PING 功能的抽象,就是上面提到的 LINK_DOWN 状态不可用的那个。说它是抽象,是因为 netdev 中并没有直接实现全部的 ping 逻辑,而是依赖底层的提供提供的 PING 能力。 + +![](./docs/ping_cmd.png) + +也许会有小伙伴想问,netdev 没有提供 ping 功能是以为代码不够高大上,能力不足吗?都是连接到一个 IP 地址上面,使用 socket 的接口不可以吗?为什么一定要抽象出一个 ping 接口,让底层去实现这个接口,而不是 netdev 层直接实现呢?其实这并不是这个原因,因为 ping 功能发送的数据,和通过 socket 发出的数据,格式并不是完全相同的。虽然统称为 IP 数据报文,但实际上 netdev 这个层级是感受不到这个数据的。具体的内容可以在下一篇 lwip 的讲解中来一起解释。 + + + + + + +