RT-Thread 组件-网络组件-Lwip UDP说明①

这篇具有很好参考价值的文章主要介绍了RT-Thread 组件-网络组件-Lwip UDP说明①。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

RT-Thread 组件-网络组件-Lwip UDP说明

rtthread 保存ip地址,Linux,ARM MCU,MCU C51,linux,arm开发,网络,udp,c++,c语言,嵌入式硬件

1. Lwip UDP说明

1.1 协议简介

UDP (User Datagram Protocol):用户数据报协议,是一种简单、无连接、不可靠的传输协议。

无需建立连接、没有提供任何流量控制、拥塞控制机制,收到的报文也没有确认,因此 UDP 的传输速度快,但不能保证数据到达目的地。

与我们熟知的 TCP 协议一样,都属于 OSI 模型中的传输层协议。

1.2 UDP 特点

无连接性 UDP 可以提供无连接的数据传输服务,无需在通讯前建立连接,也无需在通讯结束后断开连接,节省了维护连接的开销。
不可靠性 受自身协议的限制,UDP 的传输是一种不靠传输方式,无法保证数据一定能完整有效的传输到目标。
以报文为边界 因为没有 TCP 协议的数据编号和接收确认机制,UDP 对于应用层交付的数据直接进行封装传输,不会对报文进行拆分合并,在多包数据传输时可能出现乱序的现象。
无流量和拥塞控制功能 UDP 协议没有流量控制和拥塞控制的机制,因此更适用于对数据连续性比完整性要求更高、对轻微的数据差错不敏感的场景,如语音、视频通话等。
支持广播、组播 不同于 TCP 协议只能实现一对一的单播通讯,UDP 协议支持单播、广播、组播通讯,实现一对一、一对多、多对多的数据传输。因此所有以广播、组播方式通信的协议都是在 UDP 协议上实现的,如我们常见的 DHCP、SNMP 协议。

1.3 报文格式

rtthread 保存ip地址,Linux,ARM MCU,MCU C51,linux,arm开发,网络,udp,c++,c语言,嵌入式硬件

本篇文章重点是 UDP 在 LwIP 中的实现,报文格式就不再展开介绍了,但还是可以直观地看出 UDP 首部只有 8 字节的长度 ( 伪首部只参与校验和的计算,不实际发送 ),贯彻了 UDP 的简洁易用的特点。

2. UDP 在 LWIP 上的实现

2.1. 数据结构

2.1.1 UDP 控制块
struct udp_pcb {
  IP_PCB; //通用IP控制块

  struct udp_pcb *next; //下一节点的指针,用于构成控制块链表

  u8_t flags; //控制块状态

  u16_t local_port, remote_port;  //本地端口号、远程端口号

  udp_recv_fn recv; //处理网络接收数据的回调

  void *recv_arg;   //用户自定义参数,接收回调入参
};

同时,lwip 在 udp.c 中创建了全局的 udp 控制块指针,作为管理所有 UDP 控制块的链表头。

struct udp_pcb *udp_pcbs;
2.1.2 UDP 首部
PACK_STRUCT_BEGIN
struct udp_hdr {
  PACK_STRUCT_FIELD(u16_t src);   //源端口
  PACK_STRUCT_FIELD(u16_t dest);  //目的端口
  PACK_STRUCT_FIELD(u16_t len);   //此次发送的数据报的长度
  PACK_STRUCT_FIELD(u16_t chksum);//校验和
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END

报文格式中提到了 UDP 伪首部,但数据结构中没有出现,那计算伪首部的功能是在哪里实现的呢?

找到计算 UDP 首部中的校验和计算函数:

/**
 * 计算首部校验和
 * @param p 待计算数据的pbuf指针
 * @param proto 协议类型
 * @param proto_len ip数据部分的长度
 * @param src 源ip地址  (这里的IP是网络字节序)
 * @param dst 目标ip地址
 * @return 创建的UDP控制块结构体指针,创建失败返回NULL
 */
u16_t ip_chksum_pseudo(struct pbuf *p, u8_t proto, u16_t proto_len,const ip_addr_t *src, const ip_addr_t *dest)

例如在 udp_sendto_if_src() 中,数据发送之前需要计算出首部校验和,可以看到源 IP、目的 IP 等参数是现算现传的,没有再使用额外的数据结构来维护伪首部。

if (IP_IS_V6(dst_ip) || (pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
  u16_t udpchksum = ip_chksum_pseudo(q, IP_PROTO_UDP, q->tot_len,src_ip, dst_ip);

  /*0表示“无校验和,因此计算为0时需要改为0xffff*/
  if (udpchksum == 0x0000) {
    udpchksum = 0xffff;
  }
  udphdr->chksum = udpchksum;
}

2.2 接口函数

2.2.1. 创建/删除 UDP 控制块
/**
 * 创建UDP控制块
 * @return 创建的UDP控制块结构体指针,创建失败返回NULL
 */
struct udp_pcb* udp_new(void);
复制错误复制成功
udp_new() 为创建的 UDP 控制块申请内存空间、初始化控制块,返回创建的控制块指针供后续操作。

/**
 * 创建UDP控制块
 * @param type 控制块的IP类型
 * @return 创建的UDP控制块结构体指针
 */
struct udp_pcb * udp_new_ip_type(u8_t type);

与 udp_new() 相似,都是创建 UDP 控制块,区别是 udp_new_ip_type() 可以指定创建的 UDP 控制块为 IPV4 / IPV6 / IPV4+IPV6 类型,而 udp_new() 默认创建 IPV4 的 UDP 控制块。

以上两个函数都很简单,就不把函数体展开讨论了,这里需要注意的是两个创建函数都只创建了控制块的内存空间,进行了简单的初始化,并未将控制块挂载到udp_pcbs 链表中。

/**
 * 删除UDP控制块
 * @param pcb UDP控制块指针
 */
void udp_remove(struct udp_pcb *pcb);
{
  struct udp_pcb *pcb2;

  LWIP_ASSERT_CORE_LOCKED();

  LWIP_ERROR("udp_remove: invalid pcb", pcb != NULL, return);

  mib2_udp_unbind(pcb);
  /* 判断待删除的控制块在链表开头 */
  if (udp_pcbs == pcb) {
    /* 从将第二个控制块作为链表头 */
    udp_pcbs = udp_pcbs->next;

  } else {
      /* 遍历udp 控制块链表 */
    for (pcb2 = udp_pcbs; pcb2 != NULL; pcb2 = pcb2->next) {
      /* 在链表中找到了该控制块 */
      if (pcb2->next != NULL && pcb2->next == pcb) {
        /* 将该控制块在链表中删除 */
        pcb2->next = pcb->next;
        break;
      }
    }
  }
  /* 释放该控制块的内存空间 */
  memp_free(MEMP_UDP_PCB, pcb);
}

删除 UDP 控制块,并将该控制块从 UDP 控制块链表中删除,最后释放控制块的内存空间。

通过 udp_remove() 以及后面的 udp_connect() 可以看到 lwip 对 udp 控制块链表的管理方式:单向链表,每次添加新节点插到链表开头,尾节点的 next 为 NULL。简单方便,但个人认为控制块链表头作为全局变量存放,使用时也没有加锁或者关中断保护,在 RT-Thread 这类抢占式的操作系统中,是存在临界区问题的,应用开发时应避免频繁的对控制块链表有操作。

2.2.2. 绑定
/**
 * 将UDP控制块绑定到一个本地IP和端口号上
 * @param pcb UDP控制块指针
 * @param ipaddr 要绑定的本地IP
 * @param port 要绑定的本地端口号,输入0时会绑定一个随机端口
 * @return 错误码
 */
err_t udp_bind(struct udp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port);

udp_bind() 除了将控制块与指定的 IP 和端口号绑定,还会检查 UDP 控制块是否挂载到了上文提到的全局 UDP 控制块链表中、待绑定的 IP - 端口号是否与链表中的其他控制块重复,未挂载、未重复的话会执行挂载:

{
  /* code... */

  rebind = 0;
  /* 遍历udp控制块链表 */
  for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
    /* 如果当前控制块已在控制块链表中 */
    if (pcb == ipcb) {
     /* 已挂载标志位置位 */
      rebind = 1;
      break;
    }
  }

  /* code... */

  /* 未挂载? */
  if (rebind == 0) {
    /* 将当前控制块插入到链表头 */
    pcb->next = udp_pcbs;
    udp_pcbs = pcb;
  }
  /* code... */
}

绑定本地端口不是 UDP 通讯的必要步骤,因为如果没有绑定本地端口,调用 sendto() 时会分配一个随机端口。 该接口一般是设备作 UDPS 时使用,在此场景下,存在 UDPC 先向 UDPS 发送数据的情况,因此需要预先知道 UDPS 的端口号,即 UDPS 需要绑定某个端口而不能是随机端口。

2.2.3. 连接/断连
/**
 * 将UDP与指定IP、端口“建立连接”
 * @param pcb UDP控制块指针
 * @param ipaddr 要连接的目的IP
 * @param port 要连接的目的端口
 * @return 错误码
 */
err_t udp_connect(struct udp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port);

UDP 是无连接的,因此 udp_connect() 并不会真的像 TCP 的 connect() 一样去执行建立连接的网络交互,而只是在内部把目标 IP 和端口号与 UDP 控制块绑定。ip 和端口号绑定成功后,函数内部会将该 PCB 的 flag 置位为已连接:

 pcb->flags |= UDP_FLAGS_CONNECTED;

udp_connect() 会检查控制块是否绑定了本地 ip 端口,如果未绑定,会执行一次 udp_bind(),绑定到随机端口。
udp_connect() 还会检查该控制块是否挂载到了 udp 控制块链表中,未挂载的话执行挂载。
udp_connect() 同样也不是 UDP 通讯的必要步骤,绑定的优点在于绑定后可以直接调用 udp_send(),直接向绑定的目标IP和端口发送数据,无需像调用udp_sendto() 接口一样每次指定目标 IP 和端口,同时提高了执行效率,recv() 时防止受到其他 IP 数据。
该接口一般是设备作 UDPC 时使用,绑定了目标 IP 后直接调用 udp_send() 发送,UDPS 这类需要频繁向不同目标 IP、端口发送数据的应用显然不适合使用该接口。

/**
 * “断开”UDP控制块已经建立的连接
 * @param pcb UDP控制块指针
 */
void udp_disconnect(struct udp_pcb *pcb);

与 udp_connect() 同理,udp_disconnect() 也不会真的执行断开连接的交互,只是将控制块中绑定的远程 IP、端口号清零,并将 flag 的连接标志复位。也没有将控制块从链表中删除的操作。

  pcb->remote_port = 0;
  pcb->netif_idx = NETIF_NO_INDEX;
  udp_clear_flags(pcb, UDP_FLAGS_CONNECTED);
2.2.4. 发送
err_t udp_send(struct udp_pcb *pcb, struct pbuf *p);

err_t udp_sendto(struct udp_pcb *pcb, struct pbuf *p,
                const ip_addr_t *dst_ip, u16_t dst_port);

err_t udp_sendto_if(struct udp_pcb *pcb, struct pbuf *p,
                    const ip_addr_t *dst_ip, u16_t dst_port,
                    struct netif *netif);

err_t udp_sendto_if_src(struct udp_pcb *pcb, struct pbuf *p,
                        const ip_addr_t *dst_ip, u16_t dst_port,
                        struct netif *netif, const ip_addr_t *src_ip);

四个函数是一层一层调用的,udp_send() -> udp_sendto() -> udp_sendto_if() -> udp_sendto_if_src(),从 udp_send() 函数开始,只需要传入 UDP 控制块和pbuf 指针,在每层的调用过程中根据控制块中的信息将目的 ip、端口号、netif、源 IP 信息逐步补全,最后通过 ip_output_if_src() 函数将数据传输到 IP 层继续处理。
实际开发中常用的两个接口是 send() 和 sendto(),如上文介绍,执行过 connect() 的 UDP 可以直接调用send()发送到固定 IP 端口,代码设计上更加简洁高效。而调用 sendto() 可以每次向不同目标 IP 端口发送,使用上更加灵活。

2.2.5. 接收
/**
 * 为控制块注册接收回调
 * @param pcb UDP控制块指针
 * @param recv 处理网络数据的接收回调
 * @param recv_arg 触发时传入回调的用户自定义参数
 */
void udp_recv(struct udp_pcb *pcb, udp_recv_fn recv,void *recv_arg);

udp 层提供的方法是通过注册接收回调的方式实现接收网络数据。 回调类型:

/**
 * udp接收回调
 * @param arg 回调注册时设置的用户自定义参数
 * @param pcb UDP控制块
 * @param p pbuf指针(payload在这里)
 * @param addr 数据来源IP
 * @param port 数据来源端口号
 */
typedef void (*udp_recv_fn)(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port);

注册的回调在 udp_input() 中被执行,而 udp_input() 由 IP 层的 ip4_input() / ip6_input() 触发。网络端收到数据后,IP 层会判断数据协议是否为 UDP 协议,若是则将数据、发送方的信息、用户自定义数据传入 udp_input(),最终到达用户设置的回调中供使用。

3. 参考文献:

LwIP-2.1.0源码
《深入理解计算机网络》文章来源地址https://www.toymoban.com/news/detail-834707.html


维护人:

  • 华为奋斗者精神, 邮箱:1992152446@qq.com

到了这里,关于RT-Thread 组件-网络组件-Lwip UDP说明①的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32H723 + DP83848 + LWIP + RT-Thread(FreeRTOS) + STM32CubeMX + Keil MDK 超详细

    MCU:STM32H723ZGT ETH PHY :DP83848 RT-Thread:RT-Thread nano 3.1.5 Software Pack:STM32CubeH7 Firmware Package V1.10.0 / 11-February-2022 STM32H723配置以太网+Freertos注意事项 STM32H723+Lwip+ETH+CUBE 完整配置(排了巨多坑!) Cube配置STM32H743+DP83848以太网工程 STM32H743+CubeMX-梳理MPU的设置 首先使用 CubeMX配置相关外

    2024年02月13日
    浏览(45)
  • RT-Thread STM32 GoKit V2.1 开发板BSP说明

    本文档为刘恒为 GoKit V2.1 开发板提供的 BSP (板级支持包) 说明。 主要内容如下: 开发板资源介绍 BSP 快速上手 进阶使用方法 通过阅读快速上手章节开发者可以快速地上手该 BSP,将 RT-Thread 运行在开发板上。在进阶使用指南章节,将会介绍更多高级功能,帮助开发者利用 RT-

    2024年02月04日
    浏览(38)
  • RT-Thread STM32L433 Ali Starterkit BSP说明

    本文档为STM32L433 Ali Starterkit Kit 提供的 BSP (板级支持包) 说明。 主要内容如下: 开发板资源介绍 BSP 快速上手 进阶使用方法 通过阅读快速上手章节开发者可以快速地上手该 BSP,将 RT-Thread 运行在开发板上。在进阶使用指南章节,将会介绍更多高级功能,帮助开发者利用 RT-T

    2024年02月05日
    浏览(36)
  • 保姆级AT32F437 配置RT-Thread 以太网(UDP/TCP Server)

    好记性不如烂笔头,既然不够聪明,就乖乖的做笔记,温故而知新。 本文档用于本人对知识点的梳理和记录。 雅特力 AT32F437 配置RT-Thread 以太网(UDP/TCP Server) 目录         一、前言         二、ENV配置         三、结语   一、前言 ENV版本:v1.3.5 rt-thread版本:V5+

    2024年02月12日
    浏览(36)
  • 雅特力 AT32F437 配置RT-Thread 以太网(UDP/TCP Server)

    好记性不如烂笔头,既然不够聪明,就乖乖的做笔记,温故而知新。 本文档用于本人对知识点的梳理和记录。 雅特力 AT32F437 配置RT-Thread 以太网(UDP/TCP Server) 目录         一、前言         二、ENV配置         三、结语   一、前言 ENV版本:v1.3.5 rt-thread版本:V5+

    2024年02月12日
    浏览(41)
  • RT-Thread STM32F429 armfly-v6开发板BSP说明

    本文档为 RT-Thread 开发团队为 STM32F429 armfly-v6 开发板提供的 BSP (板级支持包) 说明。 主要内容如下: 开发板资源介绍 BSP 快速上手 进阶使用方法 通过阅读快速上手章节开发者可以快速地上手该 BSP,将 RT-Thread 运行在开发板上。在进阶使用指南章节,将会介绍更多高级功能,帮

    2024年01月18日
    浏览(52)
  • RT-Thread NUCLEO-STM32L452RE开发板BSP说明

    本文档为 NUCLEO-L452RE 开发板提供的 BSP (板级支持包) 说明。 主要内容如下: 开发板资源介绍 BSP 快速上手 进阶使用方法 通过阅读快速上手章节开发者可以快速地上手该 BSP,将 RT-Thread 运行在开发板上。在进阶使用指南章节,将会介绍更多高级功能,帮助开发者利用 RT-Thread

    2024年01月22日
    浏览(49)
  • RT-Thread NUCLEO-STM32H563ZI开发板BSP说明

    本文档为 yuanzihao为 NUCLEO-STM32H563ZIT6 开发板提供的 BSP (板级支持包) 说明。 主要内容如下: 开发板资源介绍 BSP 快速上手 注意事项及参考资料 通过阅读快速上手章节开发者可以快速地上手该 BSP,将 RT-Thread 运行在开发板上。 NUCLEO-STM32H563ZIT6是 ST 推出的一款基于 ARM Cortex-M33 内

    2024年02月03日
    浏览(45)
  • RT-Thread STM32L475 IoT Discovery kit BSP说明①

    本文档为 RT-Thread 开发团队为 STM32L475 IoT Discovery kit开发板提供的 BSP (板级支持包) 说明。 主要内容如下: 开发板资源介绍 BSP 快速上手 进阶使用方法 通过阅读快速上手章节开发者可以快速地上手该 BSP,将 RT-Thread 运行在开发板上。在进阶使用指南章节,将会介绍更多高级功

    2024年02月02日
    浏览(76)
  • RT-Thread STM32G070RB-Nucleo开发板的BSP说明

    本文档为 STM32G070RB-Nucleo 开发板的 BSP (板级支持包) 说明。 主要内容如下: 开发板资源介绍 BSP 快速上手 进阶使用方法 通过阅读快速上手章节开发者可以快速地上手该 BSP,将 RT-Thread 运行在开发板上。在进阶使用指南章节,将会介绍更多高级功能,帮助开发者利用 RT-Thread 驱

    2024年02月03日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包