当前位置: 首页 > news >正文

LWIP的IP 协议栈

IP 协议栈

下面的分析把上文对 IPv4 概念的说明与 lwIP 中的具体实现代码对应起来,便于将理论与实现对照理解。分析覆盖:输入处理、路由与转发、输出与报文构建、分片/重组、校验和与各种配置点(hook、选项、统计等)。


目录(快速导航)

  • ip4_input:接收与分发流程
  • ip4_route / ip4_forward:路由与转发决策
  • ip4_output_if / ip4_output:报文构建与发送
  • 校验和(inet_chksum、CHECKSUM_GEN_IP_INLINE)
  • 分片(ip4_frag)与重组(ip4_reass)
  • 辅助逻辑:广播/组播、DHCP 链路层地址接收、HOOKs、统计计数器

1. 接收流程(ip4_input)

对应代码函数:ip4_input(struct pbuf *p, struct netif *inp)

主要步骤:

  • 基本验证:
    • 检查版本 IPH_V(iphdr) == 4,否则丢弃(并记录错误)。
    • 计算首部长度 iphdr_hlen = IPH_HL_BYTES(iphdr),总长度 iphdr_len = lwip_ntohs(IPH_LEN(iphdr)),并用 pbuf_realloc/检查 p->tot_len,防止长度不一致。
  • 校验和:
    • 在 CHECKSUM_CHECK_IP 启用且 netif 支持时,调用 inet_chksum(iphdr, iphdr_hlen) 检查 IP 首部。
  • 地址拷贝与 netif 匹配:
    • 将 src/dest 拷贝到 ip_data.current_iphdr_*,调用 ip4_input_accept() 判断本接口是否接受(检查网卡是否 up、是否和本机 IP 匹配、广播/回环等)。
    • 对组播,若启用 IGMP,会查 igmp_lookfor_group() 决定是否接受。
    • 若 netif==NULL,且 IP_ACCEPT_LINK_LAYER_ADDRESSING 条件下,若目标为 DHCP 客户端端口,也会接受(允许链路层寻址的 DHCP 报文)。
  • 源地址合法性检查:
    • 拒绝源地址为广播或组播的包(RFC 1122 要求)。
  • 非本机包处理:
    • 如果 netif == NULL(不是给本机),在启用 IP_FORWARD 时调用 ip4_forward(),否则丢弃。
  • 分片处理:
    • 检测 IP_MF 或偏移不为 0,即是分片,调用 ip4_reass()(若启用 IP_REASSEMBLY)进行重组;若不启用,直接丢弃。
  • IP 选项:
    • 若 IP_OPTIONS_ALLOWED == 0 且存在选项(除 IGMP router-alert 的特殊允许),则丢弃。
  • 分发到传输层:
    • pbuf_remove_header(p, iphdr_hlen) 将 payload 调到传输层头部。
    • 根据 IP_PROTO 调用 udp_input/tcp_input/icmp_input/igmp_input 或 raw_input/返回 ICMP 不可达。
  • 清理与统计:
    • 使用 IP_STATS/MIB2 增加相应统计。

要点:

  • lwIP 在 ip 层只检查 IP 首部检验和(传输层有自己的校验),并在每个路由器处重新计算首部校验和(因为 TTL 会被减 1)。
  • pbuf 是 lwIP 的缓冲结构,ip4_input 会对 pbuf 做裁剪(pbuf_realloc)和头部移动(pbuf_remove_header)。
    在这里插入图片描述
    ip数据报格式。

在这里插入图片描述


2. 路由与转发(ip4_route / ip4_forward)

  • ip4_route(const ip4_addr_t *dest)
    • 线性遍历 netif 列表,检查 netif 是否 up、link up 且有地址;用 ip4_addr_netcmp 与 netmask 比较判断是否同网段。
    • 对多播有 ip4_default_multicast_netif 的支持(LWIP_MULTICAST_TX_OPTIONS)。
    • 若没有找到合适的 netif,会尝试 netif_default(并记录 rterr)。
  • ip4_forward(struct pbuf *p, struct ip_hdr *iphdr, struct netif *inp)
    • 首先用 ip4_canforward() 检查地址是否允许转发(不转发广播、多播、实验地址、回环等)。
    • 禁止转发链路本地地址(RFC3927)。
    • 查找下一跳 netif = ip4_route_src(src, dest)(可能有源地址路由 hook)。
    • 防止在输入 netif 上再次发出(可由 IP_FORWARD_ALLOW_TX_ON_RX_NETIF 控制)。
    • TTL–,若变为 0 则丢弃并发送 ICMP Time Exceeded(不对 ICMP 报文回复 ICMP)。
    • 增量更新首部校验和(不必完全重算)。
    • 处理分片:若 p->tot_len > netif->mtu:
      • 若允许分片(DF 未置位)且 IP_FRAG 启用,则调用 ip4_frag()。
      • 否则发送 ICMP Destination Unreachable (Fragmentation Needed)。
    • 最后调用 netif->output(netif, p, dest) 将数据放到链路层。

要点:

  • 转发功能可选(IP_FORWARD 宏)。嵌入式通常按需启用。
  • 增量校验和更新是性能优化(仅因为 TTL 字段改变)。

3. 发送与报文构建(ip4_output_if / ip4_output)

对应函数:ip4_output_if_src / ip4_output_if / ip4_output

主要流程:

  • ip4_output():高层入口,先通过 ip4_route_src 找到合适 netif,然后调用 ip4_output_if()。
  • ip4_output_if():判断是否需要生成 IP 首部(dest != LWIP_IP_HDRINCL)。
    • 若需要生成:
      • 可能插入 IP 选项(IP_OPTIONS_SEND),并把 pbuf 前面扩展出选项区与 IP_HLEN(pbuf_add_header)。
      • 填写 IP 字段:版本、头长、TOS、长度(p->tot_len)、标识(ip_id++)、偏移(0)、TTL、协议、源/目的地址。
      • 计算并填写首部校验和(支持 inline 计算或 inet_chksum,受 CHECKSUM_GEN_IP_INLINE、CHECKSUM_GEN_IP、NETIF_CHECKSUM_GEN_IP 控制)。
    • 若 dest == LWIP_IP_HDRINCL:p 已包含 IP 头,直接使用 p->payload。
  • 回环与多播处理:
    • 若目标是本机地址或 loopback,则调用 netif_loop_output。
    • 若 IP_FRAG 启用且 p->tot_len > netif->mtu,则调用 ip4_frag() 分片发送。
  • 最后调用 netif->output(netif, p, dest)。

要点:

  • ip_id 是静态全局 u16_t,用于 IP 标识字段(RFC791 建议各实现可自有策略)。
  • LWIP 支持用户提前构造 IP 头(LWIP_IP_HDRINCL),或由栈自动构建。
  • netif->output 负责把 IP 包封装到链路层(通常最终会调用 etharp_output 或底层驱动)。

4. 校验和实现细节

  • IP 首部校验和:
    • 接收时:CHECKSUM_CHECK_IP 宏控制是否在 netif 启用硬件/软件校验时检查;使用 inet_chksum(iphdr, iphdr_hlen)。
    • 发送时:CHECKSUM_GEN_IP 或 CHECKSUM_GEN_IP_INLINE 控制是否内联计算校验和或调用 inet_chksum。
    • LWIP_CHECKSUM_CTRL_PER_NETIF 允许 per-netif 配置是否生成/检查校验和。
  • 传输层校验和(TCP/UDP)由各层自己计算/检查。

要点:

  • 每次路由器处理需要修改 TTL,会相应更新首部校验和(代码用增量方式更新,避免重算)。

5. 分片(ip4_frag)与重组(ip4_reass)

分片发送:ip4_frag(struct pbuf *p, struct netif *netif, const ip4_addr_t *dest)

  • 只在发送端且 netif->mtu 小于 p->tot_len 时触发(并受 IP_FRAG 宏控制)。
  • 要点:
    • 不支持 IP options(IPH_HL_BYTES 必须等于 IP_HLEN)。
    • 以 8 字节为单位分片(nfb = (mtu - IP_HLEN)/8)。
    • 使用 pbuf_alloc / pbuf_alloced_custom 等机制,创建每个片的 pbuf 链(有的配置使用 PBUF_REF 避免复制)。
    • 每个片设置偏移量(IP_OFFMASK + IP_MF),设置长度、首部校验和,并调用 netif->output 发送。
    • 若内存不足返回 ERR_MEM。

分片重组:ip4_reass(struct pbuf *p)

  • 仅在 IP_REASSEMBLY 启用时存在。
  • 使用 struct ip_reassdata 链表保存正在重组的报文,每个报文链由多个 pbuf(头部被替换为 ip_reass_helper 结构)串联。
  • 关键点:
    • ip_reass_enqueue_new_datagram:为新报文分配 ip_reassdata。
    • ip_reass_chain_frag_into_datagram_and_validate:插入并检测重叠、缺片、排序;计算是否已全部接收。
    • ip_reass_pbufcount 控制系统内排队 pbuf 总量(避免资源耗尽)。
    • 超时机制 ip_reass_tmr(每 IP_TMR_INTERVAL 调用一次,过期则清理并可能发送 ICMP Time Exceeded)。
    • 成功重组时把 pbuf 链拼接、恢复 IP 头并返回完整 pbuf。

要点:

  • 重组在接收端进行(端系统处理),路由器不重组。

  • 若发生重叠或资源不足,会丢弃分片/报文并统计相应错误。

  • 支持策略:IP_REASS_FREE_OLDEST 在队满时释放最旧报文以给新报文空间。
    在这里插入图片描述

  • 何时发生分片

    • 当要发送的 IP 数据报(包括 IP 首部)比下一跳链路的 MTU 大时,源主机或转发主机必须将数据报分片以适应 MTU。
    • 如果 IP 头部的 DF(Don’t Fragment)位被置位,则不能分片,若超过 MTU 则应产生 ICMP Destination Unreachable(Fragmentation Needed)。
  • 分片的单位与偏移量

    • IPv4 分片偏移量(Fragment Offset)单位是 8 字节(64 位)。因此,除首/尾片外每个分片的数据长度必须是8的整数倍(除非是最后一个分片)。
    • lwIP 中计算每片数据块数: nfb = (netif->mtu - IP_HLEN) / 8;实际每片数据字节数 = nfb * 8。
  • 发送端(ip4_frag)的关键步骤(对应源码要点)

    • 不支持带 IP option 的数据报(在 lwIP ip4_frag 中要求 IPH_HL_BYTES == IP_HLEN)。
    • 计算要分的片数并循环:
      • 为每片准备 pbuf:在不同配置下使用 PBUF_RAM(复制数据)或用 PBUF_REF / pbuf_alloced_custom 来引用原始 pbuf 的数据以避免拷贝(节省内存/CPU)。
      • 设置每片的 IP 首部字段:
        • 将原始首部复制到每个片的首部位置(除某些字段外保持一致)。
        • 设置长度(片的总长度 = IP_HLEN + 片数据长度)。
        • 计算并设置分片偏移量(以 8 字节为单位)和 MF(更多片)位:除最后一片外 MF=1,最后一片 MF=0。
        • 计算 IP 首部校验和(受 CHECKSUM_GEN_IP / CHECKSUM_GEN_IP_INLINE / NETIF_CHECKSUM_GEN_IP 控制)。
      • 调用 netif->output(netif, rambuf, dest) 逐片发送,然后释放 rambuf(注意硬件可能异步使用)。
    • 返回 ERR_OK 或在内存不足时返回 ERR_MEM。
  • 接收端重组(ip4_reass)的关键步骤(对应源码要点)

    • 入口:当接收到分片(IPH_OFFSET 中含有 MF 或 offset != 0)时,ip4_input 调用 ip4_reass。
    • 结构与队列:
      • 用 struct ip_reassdata 链表保存待重组的报文(每个 ip_reassdata 保留原始第一个片的 IP 头副本、timer、pbuf 链等)。
      • 为每个已接片在 pbuf 的 IP 首部处放置一个小的 helper 结构(ip_reass_helper),记录该片在整体报文中的 start/end 偏移以及指向下一个片的指针。
    • 入队与插入验证(ip_reass_chain_frag_into_datagram_and_validate):
      • 将新片按 start 偏移排序插入链表(在正确位置插入或追加)。
      • 检查重叠(可选,受 IP_REASS_CHECK_OVERLAP 控制):若重叠或重复片可选择丢弃。
      • 检查“洞”(missing fragments):若链中有间隙则标记为未完成(valid = 0)。
      • 若见到最后一片(MF==0),并且链上没有洞,则认为重组已完成。
    • 资源与防护
      • 使用 ip_reass_pbufcount 跟踪已占用 pbuf 数量,并限制最大排队 pbuf(IP_REASS_MAX_PBUFS)。
      • 当达到上限时,可通过 IP_REASS_FREE_OLDEST 策略释放最旧报文以腾出空间(实现中有此可选行为)。
      • 超时机制:ip_reass_tmr 每 IP_TMR_INTERVAL(一般 1s)递减重组项 timer,超时后释放并(如果已接到第一个片)发送 ICMP Time Exceeded。
    • 成功重组
      • 将所有片的 pbuf 链按顺序拼接:对于除第一片外每个片调用 pbuf_remove_header(r, IP_HLEN) 隐藏其 IP 首部,然后用 pbuf_cat 把它们追加到第一个 pbuf。
      • 恢复第一个 pbuf 的原始 IP 首部(从 ipr->iphdr 拷贝),设置总长度、将偏移设为 0,校验和重算(若需要)。
      • 从重组队列中移除并释放 ip_reassdata,更新 ip_reass_pbufcount,返回完整的 pbuf 链给上层处理。
    • 失败情形
      • 如果出现重叠(并被配置为拒绝)、内存不足、或超时,丢弃分片并相应更新统计;在某些情形会发送 ICMP(例如超时发送 Time Exceeded,或 DF 导致分片失败则转发端会触发 ICMP)。
  • 路由器与终端的区别

    • 路由器不应(且在 lwIP 默认实现中不)对通过它的分片进行重组:重组工作放在目的主机(端系统)上。路由器只转发分片(除非路由器自身就是目的主机)。
    • 因此分片可以在路径上的不同链路被再次分片(分片的再分片),最终端系统负责按偏移装配回原始数据报。
  • 安全与性能注意事项

    • 分片可被滥用用于逃避检测或触发 DoS(大量未完成的重组会耗尽内存)。建议:
      • 限制 IP_REASS_MAX_PBUFS(或直接禁用 IP_REASSEMBLY,当能保证上层不依赖分片时)。
      • 启用 IP_REASS_FREE_OLDEST 或短重组超时以减少资源占用窗口。
      • 严格处理重叠片(拒绝或按策略合并)。
    • 分片/重组会增加延迟并消耗内存与 CPU,能避免时尽量通过 Path MTU Discovery(PMTUD)或尽量发送小包减少分片需求。
  • 示例

    • 原始 IP 数据段:3980 字节(不含 20 字节 IP 头),通过 MTU=1500 的链路。
    • 每片能承载的数据 = 1500 - 20 = 1480 字节(且为8字节倍数)。
    • 分片方案:
      • 分片 1:数据长度 1480,偏移 0,MF = 1,总长 1500(含 20 字节头)。
      • 分片 2:数据长度 1480,偏移 1480/8 = 185,MF = 1,总长 1500。
      • 分片 3(最后片):数据长度 = 3980 - 1480 - 1480 = 1020,偏移 = 185 + 185 = 370,MF = 0,总长 = 1020 + 20 = 1040。
    • 目的主机收到三片后根据偏移(0, 185, 370)拼接成完整 3980 字节数据并交给传输层。
  • 实践建议(对 lwIP 配置)

    • 如果内存充足且需要接收任意分片:启用 IP_REASSEMBLY,合理增大 IP_REASS_MAX_PBUFS 并启用 IP_REASS_FREE_OLDEST(以减轻洪泛)。
    • 对于路由器/网关:通常关闭重组(路由器不必重组)并启用转发(IP_FORWARD),确保 DF/MTU/ICMP Path MTU 策略正确。
    • 若链路 MTU 可变或紧张:优先使用 PMTUD 或在上层限制 MSS/MTU,减少 IP 分片发生。

(上述流程直接映射至 lwIP 源码中的 ip4_frag、ip4_reass、ip_reass_tmr、ip_reass_chain_frag_into_datagram_and_validate、ip4_input 等函数)


6. 组播、广播与 DHCP(链路层寻址)

  • 代码中对组播做了专门处理:
    • ip4_input 在 dest 为 multicast 时会对 IGMP 做额外检查(igmp_lookfor_group)。
    • ip4_route 支持 ip4_default_multicast_netif(LWIP_MULTICAST_TX_OPTIONS)。
  • 将链路层寻址包(例如 DHCP Server->Client reply)在 netif down 时仍能接收:
    • IP_ACCEPT_LINK_LAYER_ADDRESSING 宏及 IP_ACCEPT_LINK_LAYER_ADDRESSED_PORT 的扩展允许接受目的 IP 不匹配的 UDP 包(通常用于 DHCP)。
    • 当 netif==NULL 并检测到 UDP 且目标端口为 DHCP 客户端端口时,强制接受。

7. Hook 与可配置项(可扩展点)

代码中暴露多个 hook / macro 以便定制:

  • LWIP_HOOK_IP4_INPUT:在 ip4_input 开始时可「吃掉」包(返回非 0 则视为已处理)。
  • LWIP_HOOK_IP4_ROUTE_SRC / LWIP_HOOK_IP4_ROUTE:可自定义路由选择(包括基于源地址的路由)。
  • LWIP_HOOK_IP4_CANFORWARD:自定义能否转发判断。
  • LWIP_NETIF_LOOPBACK / NETIF_FLAG_BROADCAST / NETIF_CHECKSUM_* 等编译时选项影响行为。

8. 统计与调试

  • 大量 IP_STATS_*、MIB2_STATS_INC 用于统计收发、错误、转发、碎片、重组等信息,便于监控与调试。
  • IP_DEBUG / IP_REASS_DEBUG 等宏用于运行时打印调试信息(lwIP 的 LWIP_DEBUGF)。

9. 关联要点汇总

  • IP 地址与路由选择:理论上 IP 地址指定主机身份;实现上由 ip4_route() 根据 netif 和 netmask 找到发送接口。
  • TTL 防止循环:在 ip4_forward 中 TTL 每跳减 1,TTL 归 0 时发 ICMP 超时(icmp_time_exceeded)。
  • 分片单位与重组:
    • 理论:分片以 8 字节为单位;实现:nfb = (mtu - IP_HLEN) / 8,偏移以 8 字节单位设置。
    • 重组仅在接收端完成:ip4_reass 使用链表与 helper 结构保存片段。
  • NAT:用户文中描述 NAT 工作方式(修改源 IP:port 并维护转换表)属于应用/路由器层,lwIP core 没有内置完整 NAT 实现,这种功能通常由路由器软件/OS 层实现(lwIP 可以作为设备上的 IP 层基础)。
  • 校验和策略:IP 首部在每跳必须验证,发包时根据配置选择内联或函数计算;传输层还会做更广泛的校验(TCP/UDP 校验和)。
    在这里插入图片描述

10. 常见调整建议

  • 若设备作路由器:打开 IP_FORWARD、IP_FRAG、IP_REASSEMBLY;注意内存(IP_REASS_MAX_PBUFS)与安全(不要盲目接受链路层包)。
  • 若网卡有硬件校验:启用 LWIP_CHECKSUM_CTRL_PER_NETIF 并为 netif 配置 NETIF_CHECKSUM_*,可节约 CPU。
  • 对内存敏感:禁用 IP_REASSEMBLY 或启用 IP_REASS_FREE_OLDEST,慎重设置 IP_REASS_MAX_PBUFS。
  • 若需要高性能:启用 CHECKSUM_GEN_IP_INLINE,避免重复拷贝(使用 PBUF_REF / custom pbufs)。

11. 参考源码位置

  • 输入/输出主入口:ip4_input、ip4_output、ip4_output_if、ip4_output_if_src
  • 转发/路由:ip4_route、ip4_forward、ip4_canforward
  • 分片/重组:ip4_frag、ip4_reass、ip_reass_tmr、ip_reass_chain_frag_into_datagram_and_validate
  • 辅助:ip4_addr_isbroadcast_u32、ip4addr_aton、ip4addr_ntoa_r 等地址工具

12. 小结

将理论与lwIP实现对照可以看出:IP 协议栈在嵌入式实现中需要在功能完备与资源受限之间做折中。lwIP 提供了丰富的编译时配置点(碎片、重组、校验和、路由 hook 等),可以根据设备角色(终端/路由器/网关)调整。理解 ip4_input/ip4_output/ip4_frag/ip4_reass 这几个核心函数,能帮助定位大多数网络问题(丢包、碎片、路由错误、校验失败、DHCP/组播交互等)。

在这里插入图片描述
核心思路:验证 → 匹配本机/多播/广播 → 是否需转发 → 是否为分片 → 重组或直接分发到上层协议(UDP/TCP/ICMP/IGMP)或丢弃/回复 ICMP。

  • 关键处理步骤(按图自下而上)

    1. ethernet_input() 把以太网帧交给 ip4_input()(若是 IPv4)。
      关联源码:ethernet_input、netif 层。
    2. ip4_input() 首部验证(版本、首部长度、校验和、总长度)和源/目的地址基本检查。
      失败:丢弃并更新统计/可能发送 ICMP。
    3. 判断是否 IPv4(图中 “IPv4?”)。不是则由上层其它处理或丢弃。
    4. 检查目的地址是否为本机(ip4_addr_isloopback / 本机 IP / 多播 / 广播)。
      • 是本机:继续处理(进入分片重组或上层协议)。
      • 否且 netif==NULL:不是给本机——进入转发逻辑或丢弃(取决于 IP_FORWARD)。
    5. netif==NULL 分支:对于非广播/多播目标且启用转发,调用 ip4_forward() 转发;对广播通常不转发(直接丢弃或本地处理视实现)。
    6. 分片检测:若 IP 标志或偏移表明为分片(MF 或 offset!=0),调用 ip4_reass() 做重组。
      • 重组成功:交给上层协议处理。
      • 重组失败或超时:释放并可能发送 ICMP(Time Exceeded 等)。
    7. 分发到上层:根据 IP 协议字段发到 udp_input、tcp_input、icmp_input、igmp_input 或 raw_input;未知协议通常丢弃或统计错误。
  • 特别注意点

    • 分片在目的端重组:路由器不重组(转发即可),重组逻辑在 ip4_reass。
    • 多播与 IGMP:多播包可能需要额外的 IGMP 检查(igmp_lookfor_group)。
    • 广播处理:通常不转发到其它网段;交给本机所有接口的广播机制或丢弃。
    • netif==NULL 表示该包在本机没有匹配的接口(即目标不是本机地址),需要考虑是否转发。
    • 未知上层协议会被丢弃(或由 raw/pcap/probe 钩子捕获)。
  • 常见后续动作(实现/调试相关)

    • 出错会更新 IP_STATS/MIB2 并在启用的情况下打印调试信息。
    • 转发时若需分片且 DF=1,会触发 ICMP Destination Unreachable (Fragmentation Needed)。
    • 重组资源受限会导致丢弃,应检查 IP_REASS_MAX_PBUFS、重组超时策略等。
  • 源码映射(快速索引)

    • 入口/验证/分发:ip4_input
    • 重组:ip4_reass、ip_reass_tmr
    • 转发/路由:ip4_forward、ip4_route、ip4_canforward
    • 发送到上层:udp_input / tcp_input / icmp_input / igmp_input
http://www.lryc.cn/news/625958.html

相关文章:

  • springboot--使用QQ邮箱
  • 网络聚合链路与软件网桥配置指南
  • 源代码安装部署lamp
  • 云端赋能,智慧运维:分布式光伏电站一体化监控平台研究
  • “R语言+遥感”的水环境综合评价方法实践技术应用
  • 微服务-07.微服务拆分-微服务项目结构说明
  • 云电脑 vs 传统PC:全面对比3A游戏与AI训练的成本与性能
  • 基于STM32+NBIOT设计的宿舍安防控制系统_264
  • Java NIO (New I/O) 深度解析
  • 深入理解Prompt构建与工程技巧:API高效实践指南
  • webpack》》Plugin 原理
  • Spring Ai Prompts
  • webrtc弱网-GoogCcNetworkController类源码分析与算法原理
  • Jenkins服务器SSH公钥配置步骤
  • 哈希:两数之和
  • 磁盘镜像格式RAW、QCOW2、VHD、VMDK的核心区别
  • Android -登录注册实践技术总结
  • Android SystemServer 中 Service 的创建和启动方式
  • 代码随想录Day56:图论(冗余连接、冗余连接II)
  • CLIK-Diffusion:用于牙齿矫正的临床知识感知扩散模型|文献速递-深度学习人工智能医疗图像
  • 心路历程-启动流程的概念
  • 如何让你的知识分享更有说服力?
  • RNN如何将文本压缩为256维向量
  • AC内容审计技术
  • 单一职责原则(SRP)深度解析
  • django生成迁移文件,执行生成到数据库
  • CNN-LSTM-Attention、CNN-LSTM、LSTM三模型多变量时序光伏功率预测
  • 开源 GIS 服务器搭建:GeoServer 在 Linux 系统上的部署教程
  • Scikit-learn通关秘籍:从鸢尾花分类到房价预测
  • Vim笔记:缩进