lwip源码分析 之 UDP协议

LWIP 专栏收录该内容
19 篇文章 3 订阅

一,udp.h

udp协议呢比tcp简单,其数据结构也简单了许多。lwip的udp代码结构简单如下:比较复杂的是udp_input()函数。
在这里插入图片描述
(图侵删)

下面来看看udp.h这个udp协议接口文件

struct udp_pcb {
  IP_PCB; //ip地址等

  struct udp_pcb *next;

  u8_t flags;

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

#if LWIP_MULTICAST_TX_OPTIONS
  /** outgoing network interface for multicast packets */
  ip_addr_t multicast_ip;
  /** TTL for outgoing multicast packets */
  u8_t mcast_ttl;
#endif /* LWIP_MULTICAST_TX_OPTIONS */

#if LWIP_UDPLITE
  /** used for UDP_LITE only */
  u16_t chksum_len_rx, chksum_len_tx;
#endif /* LWIP_UDPLITE */

  udp_recv_fn recv; //接收回调函数

  void *recv_arg; //接收回调函数参数
};

udp_pcb结构体非常简单哈,主要包含了ip地址,端口,还有上层应用的回调函数。
flag的取值有以下:udp是可以补进行校验和计算的

#define UDP_FLAGS_NOCHKSUM       0x01U  //不进行校验和计算
#define UDP_FLAGS_UDPLITE        0x02U
#define UDP_FLAGS_CONNECTED      0x04U  //控制块已经和对方建立连接
#define UDP_FLAGS_MULTICAST_LOOP 0x08U  //循环广播

其次是应用回调函数,它是个函数指针,用于udp接收到正常的数据时,调用函数,让应用层对数据进行处理,应用层处理完数据应该对数据缓存pbuf进行释放。

/** Function prototype for udp pcb receive callback functions
 * addr and port are in same byte order as in the pcb
 * The callback is responsible for freeing the pbuf
 * if it's not used any more.
 *
 * ATTENTION: Be aware that 'addr' might point into the pbuf 'p' so freeing this pbuf
 *            can make 'addr' invalid, too.
 *
 * @param arg user supplied argument (udp_pcb.recv_arg)
 * @param pcb the udp_pcb which received data
 * @param p the packet buffer that was received
 * @param addr the remote IP address from which the packet was received
 * @param port the remote port from which the packet was received
 */
typedef void (*udp_recv_fn)(void *arg, struct udp_pcb *pcb, struct pbuf *p,
    const ip_addr_t *addr, u16_t port); //上层应用的数据接收回调函数

udp.h中有两个比较重要的函数,也就是数据的输入输出函数,接下来看看他们的代码,其他函数不复杂。

err_t            udp_send       (struct udp_pcb *pcb, struct pbuf *p);

void             udp_input      (struct pbuf *p, struct netif *inp);

二,udp数据输出

代码提供了多个udp输出的函数,这些函数各自实现一部分功能,最后都要调用udp_sendto_if_src()函数把数据交给ip层。
例如:udp_send()将pbuf发送到udp_pcb中记录的目的地址。其实它调用了udp_sendto(),而udp_sendto()中由目的ip和源ip地址,先确定使用的网络接口netif,然后调用udp_sendto_if();在udp_sendto_if()中确定了真正的src_ip(源ip地址)并调用udp_sendto_if_src();
所以最终boss是函数udp_sendto_if_src();,该函数就是填充了udp的首部,并传递给ip层,其中关于校验和计算的宏会比较复杂

//将pbuf填充并交给ip层
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)
{
  struct udp_hdr *udphdr; //发送的udp首部
  err_t err;
  struct pbuf *q; /* q will be sent down the stack */
  u8_t ip_proto;    //?
  u8_t ttl;

  if ((pcb == NULL) || (dst_ip == NULL) || !IP_ADDR_PCB_VERSION_MATCH(pcb, src_ip) ||
      !IP_ADDR_PCB_VERSION_MATCH(pcb, dst_ip)) {
    return ERR_VAL;
  }

  //检查端口是否绑定
  if (pcb->local_port == 0) {
    LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_send: not yet bound to a port, binding now\n"));
    err = udp_bind(pcb, &pcb->local_ip, pcb->local_port); //未绑定的就帮他绑定
    if (err != ERR_OK) {
      LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: forced port bind failed\n"));
      return err;
    }
  }

  //将pbuf的payload指针前移8个字节,用于填充udp首部
  if (pbuf_header(p, UDP_HLEN)) {   //0:移动成功;
    //若移动失败,pbuf前面内存不够,需要新增内存

    q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);  //申请一个pbuf存放udp首部

    if (q == NULL) {
      LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("udp_send: could not allocate header\n"));
      return ERR_MEM;
    }
    //当pbuf中有数据时,将udp首部q连接到pbuf之前
    if (p->tot_len != 0) {
      /* chain header q in front of given pbuf p (only if p contains data) */
      pbuf_chain(q, p); //q放到p之前
    }

    LWIP_DEBUGF(UDP_DEBUG,
                ("udp_send: added header pbuf %p before given pbuf %p\n", (void *)q, (void *)p));
  } else {	//移动8字节成功
    /* adding space for header within p succeeded */
    /* first pbuf q equals given pbuf */
    q = p; 
    LWIP_DEBUGF(UDP_DEBUG, ("udp_send: added header in given pbuf %p\n", (void *)p));
  }
  //q现在是即将发送的pbuf了
  LWIP_ASSERT("check that first pbuf can hold struct udp_hdr",
              (q->len >= sizeof(struct udp_hdr)));

  udphdr = (struct udp_hdr *)q->payload;  //取得udp首部
  udphdr->src = lwip_htons(pcb->local_port);  //填充udp首部的地址
  udphdr->dest = lwip_htons(dst_port);
  /* in UDP, 0 checksum means 'no checksum' */
  udphdr->chksum = 0x0000;  //填充校验和

#if (LWIP_IPV4 && LWIP_MULTICAST_TX_OPTIONS) || (LWIP_IPV6 && LWIP_IPV6_MLD)
  //判断是否开启循环广播且目标地址是广播地址
  if (((pcb->flags & UDP_FLAGS_MULTICAST_LOOP) != 0) && ip_addr_ismulticast(dst_ip)) {
    q->flags |= PBUF_FLAG_MCASTLOOP;
  }
#endif 

  LWIP_DEBUGF(UDP_DEBUG, ("udp_send: sending datagram of length %"U16_F"\n", q->tot_len));

  {      
    LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP packet length %"U16_F"\n", q->tot_len));
    udphdr->len = lwip_htons(q->tot_len); //填充udp首部中的len字段 表示udp的数据长度
   
#if CHECKSUM_GEN_UDP
    //检查网络接口校验和计算是否使能
    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_UDP) {
      //对于ipv6,校验和是必须的
      if (IP_IS_V6(dst_ip) || (pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
        u16_t udpchksum;
        {
          udpchksum = ip_chksum_pseudo(q, IP_PROTO_UDP, q->tot_len,
            src_ip, dst_ip);  //计算校验和
        }

        /* chksum zero must become 0xffff, as zero means 'no checksum' */
        if (udpchksum == 0x0000) {
          udpchksum = 0xffff;
        }
        udphdr->chksum = udpchksum;	//填写新的校验和
      }
    }
#endif /* CHECKSUM_GEN_UDP */
    ip_proto = IP_PROTO_UDP;
  }

  /* Determine TTL to use */
#if LWIP_MULTICAST_TX_OPTIONS
  ttl = (ip_addr_ismulticast(dst_ip) ? udp_get_multicast_ttl(pcb) : pcb->ttl);
#else /* LWIP_MULTICAST_TX_OPTIONS */
  ttl = pcb->ttl;	
#endif /* LWIP_MULTICAST_TX_OPTIONS */

  LWIP_DEBUGF(UDP_DEBUG, ("udp_send: UDP checksum 0x%04"X16_F"\n", udphdr->chksum));
  LWIP_DEBUGF(UDP_DEBUG, ("udp_send: ip_output_if (,,,,0x%02"X16_F",)\n", (u16_t)ip_proto));
  //这些宏未开启
  NETIF_SET_HWADDRHINT(netif, &(pcb->addr_hint));

  //传递pbuf给ip层
  err = ip_output_if_src(q, src_ip, dst_ip, ttl, pcb->tos, ip_proto, netif);
  NETIF_SET_HWADDRHINT(netif, NULL);

  /* @todo: must this be increased even if error occurred? */
  MIB2_STATS_INC(mib2.udpoutdatagrams);

  /* did we chain a separate header pbuf earlier? */
  //若刚刚使用了q申请udp首部,则释放q
  if (q != p) {
    /* free the header pbuf */
    pbuf_free(q);
    q = NULL;
    /* p is still referenced by the caller, and will live on */
  }

  UDP_STATS_INC(udp.xmit);
  return err;
}

三,udp数据输入

ip层调用该函数,该函数先判断udp包是否是广播,然后查找匹配的udp,其中又分为以下情况:

1,ip地址不匹配时,直接删除
2,ip地址匹配但端口不匹配,则发送端口不可达报文
3,ip与端口都匹配,则回调接收函数处理数据,应用层处理完数据后应该将数据释放。


void udp_input(struct pbuf *p, struct netif *inp)
{
  struct udp_hdr *udphdr; //输入数据包的udp首部
  struct udp_pcb *pcb, *prev; //查找的udp控制块
  struct udp_pcb *uncon_pcb;  //未建立连接的pcb
  u16_t src, dest;  //src:数据包源端口,dest:数据包目的端口
  u8_t broadcast; //1:广播包
  u8_t for_us = 0;  //1:数据包ip地址是本地

  LWIP_UNUSED_ARG(inp);

  PERF_START;

  UDP_STATS_INC(udp.recv);

  //检查最小长度
  if (p->len < UDP_HLEN) {
    /* drop short packets */
    LWIP_DEBUGF(UDP_DEBUG,
                ("udp_input: short UDP datagram (%"U16_F" bytes) discarded\n", p->tot_len));
    UDP_STATS_INC(udp.lenerr);
    UDP_STATS_INC(udp.drop);
    MIB2_STATS_INC(mib2.udpinerrors);
    pbuf_free(p);
    goto end;
  }

  udphdr = (struct udp_hdr *)p->payload;  //获取输入数据包的udp首部

  //检查是否是广播(当前首部目的地址和网络接口判断)
  broadcast = ip_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif());

  src = lwip_ntohs(udphdr->src);  //获取首部中的端口
  dest = lwip_ntohs(udphdr->dest);

  pcb = NULL;
  prev = NULL;
  uncon_pcb = NULL;

  //遍历已连接的udp链表
  for (pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) {

    //查找 “本地端口=数据包目的端口 && 本地ip=数据包目的ip” 的pcb
    //判断udp中的本地端口,ip地址与数据报中的是否匹配
    if ((pcb->local_port == dest) &&
        (udp_input_local_match(pcb, inp, broadcast) != 0)) {
      
      //判断该udp是否已经建立连接
      if (((pcb->flags & UDP_FLAGS_CONNECTED) == 0) &&
          ((uncon_pcb == NULL))) {
        /* the first unconnected matching PCB */
        uncon_pcb = pcb;  //记录第一个未连接的匹配udp
      }
      //接下来判断 pcb的目的端口和目的ip是否和数据包的源端口,源ip匹配
      if ((pcb->remote_port == src) &&  //本地目的地址匹配成功
          (ip_addr_isany_val(pcb->remote_ip) || //ip匹配成功
          ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()))) {
       //到此,说明数据包与pcb完全匹配
        if (prev != NULL) {
          //将匹配的udp移动到链表首部
          prev->next = pcb->next;
          pcb->next = udp_pcbs;
          udp_pcbs = pcb;
        } else {
          UDP_STATS_INC(udp.cachehit);
        }
        break;
      }
    }

    prev = pcb; //匹配下一个
  }
  //遍历了所有已连接的控制块,都没有完全匹配的,则将第一个匹配成功的未连接控制块作为结果
  if (pcb == NULL) {
    pcb = uncon_pcb;
  }

  if (pcb != NULL) {  //找到完全匹配的pcb
    for_us = 1;
  } else {  //无完全匹配的控制块。则判断数据包是否是发送到本地ip的
#if LWIP_IPV6
    //若当前数据报是ipv6,且它确实是发送到本地ip的,则for_us=1,但pcb=null
    if (ip_current_is_v6()) {
      for_us = netif_get_ip6_addr_match(inp, ip6_current_dest_addr()) >= 0;
    }
#endif /* LWIP_IPV6 */
#if LWIP_IPV4
    //若当前数据报不是ipv6,道理一样
    if (!ip_current_is_v6()) {
      for_us = ip4_addr_cmp(netif_ip4_addr(inp), ip4_current_dest_addr());
    }
#endif /* LWIP_IPV4 */
  }

  //数据包确实是给我们的,ip地址是对的
  if (for_us) {
   
        if (udphdr->chksum != 0) {  //数据包中填写了校验和,则必须校验
          if (ip_chksum_pseudo(p, IP_PROTO_UDP, p->tot_len,
                               ip_current_src_addr(),
                               ip_current_dest_addr()) != 0) {
            goto chkerr;  //检验和错误
          }
       }
    }
    //校验和成功,将payload移动到udp数据区
    if (pbuf_header(p, -UDP_HLEN)) {
      /* Can we cope with this failing? Just assert for now */
      LWIP_ASSERT("pbuf_header failed\n", 0);
      UDP_STATS_INC(udp.drop);
      MIB2_STATS_INC(mib2.udpinerrors);
      pbuf_free(p); //移动失败!
      goto end;
    }

    //若有匹配的控制块,则调用用户的回调函数处理
    if (pcb != NULL) {
      MIB2_STATS_INC(mib2.udpindatagrams);
      /* callback */
      if (pcb->recv != NULL) {
        /* now the recv function is responsible for freeing p */
        pcb->recv(pcb->recv_arg, pcb, p, ip_current_src_addr(), src); //调用应用回调函数处理接收的数据
      } else {
     
        pbuf_free(p); //无回调函数,释放内存
        goto end;
      }
    } else {  //若无匹配的控制块,说明端口不正确,返回端口不可达报文

     //确定不是广播或多播的数据报
      if (!broadcast && !ip_addr_ismulticast(ip_current_dest_addr())) {
       
        pbuf_header_force(p, (s16_t)(ip_current_header_tot_len() + UDP_HLEN));  //payload指向ip首部
        icmp_port_unreach(ip_current_is_v6(), p); //发送端口不可达报文
      }
#endif /* LWIP_ICMP || LWIP_ICMP6 */
      UDP_STATS_INC(udp.proterr);
      UDP_STATS_INC(udp.drop);
      MIB2_STATS_INC(mib2.udpnoports);
      pbuf_free(p);
    }
  } else {  //不是给我们的,ip地址错误
    pbuf_free(p);
  }
end:
  PERF_STOP("udp_input");
  return;
#if CHECKSUM_CHECK_UDP
chkerr:

  UDP_STATS_INC(udp.chkerr);
  UDP_STATS_INC(udp.drop);
  MIB2_STATS_INC(mib2.udpinerrors);
  pbuf_free(p);
  PERF_STOP("udp_input");
#endif /* CHECKSUM_CHECK_UDP */
}

四,小结

udp相对tcp,简单了许多,但同时也是不可靠的传输。
在这里插入图片描述

  • 1
    点赞
  • 0
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:深蓝海洋 设计师:CSDN官方博客 返回首页

打赏作者

killer-p

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值