LWIP学习笔记(8)UDP协议

前面讲发送回送请求时介绍了原始协议控制块raw_pcb,udp的实现和它原理一样,内核会为udp维护udp控制块链表通过遍历来寻找匹配的控制块,然后执行控制块内回调函数。udp的实现在udp.c/h

数据结构

udp.h
在这里插入图片描述

udp控制块

udp.h中,这里可以和raw_pcb对比下,后面的操作都可以和原始控制块操作对比下
在这里插入图片描述

udp raw编程接口
新建udp_pcb

在这里插入图片描述
如果MEMP_UDP_PCB类型的内存池用完就无法申请,个数可以在lwipotps.h中定义

绑定本地ip、port

实质是设置udp_pcb内部本地ip、port字段并把他添加到udp_pcbs链表上

/**
 * Bind an UDP PCB.
 *
 * @param pcb UDP PCB to be bound with a local address ipaddr and port.
 * @param ipaddr local IP address to bind with. Use IP_ADDR_ANY to
 * bind to all local interfaces.
 * @param port local UDP port to bind with. Use 0 to automatically bind
 * to a random port between UDP_LOCAL_PORT_RANGE_START and
 * UDP_LOCAL_PORT_RANGE_END.
 *
 * ipaddr & port are expected to be in the same byte order as in the pcb.
 *
 * @return lwIP error code.
 * - ERR_OK. Successful. No error occured.
 * - ERR_USE. The specified ipaddr and port are already bound to by
 * another UDP PCB.
 *
 * @see udp_disconnect()
 */
err_t udp_bind(struct udp_pcb *pcb, ip_addr_t *ipaddr, u16_t port)
{
  struct udp_pcb *ipcb;
  u8_t rebind;//是否已经插入局部变量

  rebind = 0;//遍历udp_pcbs,判断是否已经插入并判断是否重复绑定
  /* Check for double bind and rebind of the same pcb */
  for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
    /* is this UDP PCB already on active list? */
    if (pcb == ipcb) {
      /* pcb may occur at most once in active list */
      LWIP_ASSERT("rebind == 0", rebind == 0);
      /* pcb already in list, just rebind */
      rebind = 1;
    }

    /* By default, we don't allow to bind to a port that any other udp
       PCB is alread bound to, unless *all* PCBs with that port have tha
       REUSEADDR flag set. */
#if SO_REUSE
    else if (!ip_get_option(pcb, SOF_REUSEADDR) &&
             !ip_get_option(ipcb, SOF_REUSEADDR)) {
#else /* SO_REUSE */
    /* port matches that of PCB in list and REUSEADDR not set -> reject */
    else {
#endif /* SO_REUSE */
      if ((ipcb->local_port == port) &&
          /* IP address matches, or one is IP_ADDR_ANY? */
          (ip_addr_isany(&(ipcb->local_ip)) ||
           ip_addr_isany(ipaddr) ||
           ip_addr_cmp(&(ipcb->local_ip), ipaddr))) {
        /* other PCB already binds to this local IP and port */
        LWIP_DEBUGF(UDP_DEBUG,
                    ("udp_bind: local port %"U16_F" already bound by another pcb\n", port));
        return ERR_USE;//重复绑定则返回错误
      }
    }
  }

  ip_addr_set(&pcb->local_ip, ipaddr);

  /* no port specified? */
  if (port == 0) {
    port = udp_new_port();//没有填端口则自动选择一个有效端口
    if (port == 0) {
      /* no more ports available in local range */
      LWIP_DEBUGF(UDP_DEBUG, ("udp_bind: out of free UDP ports\n"));
      return ERR_USE;
    }
  }
  pcb->local_port = port;
  /* pcb not active yet? */
  if (rebind == 0) {//没有插入则插入
    /* place the PCB on the active list if not already there */
    pcb->next = udp_pcbs;
    udp_pcbs = pcb;
  }
  return ERR_OK;
}
连接控制块

本质是设置udp_pcb中远程ip、port字段并设置已连接标志

/**
 * Connect an UDP PCB.
 *
 * This will associate the UDP PCB with the remote address.
 *
 * @param pcb UDP PCB to be connected with remote address ipaddr and port.
 * @param ipaddr remote IP address to connect with.
 * @param port remote UDP port to connect with.
 *
 * @return lwIP error code
 *
 * ipaddr & port are expected to be in the same byte order as in the pcb.
 *
 * The udp pcb is bound to a random local port if not already bound.
 *
 * @see udp_disconnect()
 */
err_t udp_connect(struct udp_pcb *pcb, ip_addr_t *ipaddr, u16_t port)
{
  struct udp_pcb *ipcb;

  if (pcb->local_port == 0) {
    err_t err = udp_bind(pcb, &pcb->local_ip, pcb->local_port);
    if (err != ERR_OK) {
      return err;
    }
  }

  ip_addr_set(&pcb->remote_ip, ipaddr);
  pcb->remote_port = port;
  pcb->flags |= UDP_FLAGS_CONNECTED;

  /* Insert UDP PCB into the list of active UDP PCBs. */
  for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
    if (pcb == ipcb) {
      /* already on the list, just return */
      return ERR_OK;
    }
  }
  /* PCB not yet on the list, add PCB now */
  pcb->next = udp_pcbs;
  udp_pcbs = pcb;
  return ERR_OK;
}
断开控制块

本质是清除udp_pcb中远程ip、port字段、已连接标志
在这里插入图片描述

注册数据接收函数

在这里插入图片描述

发送函数

udp_sendudp_sendto都可以,实质是调用udp_sendto_if

/**
 * Send data using UDP.
 *
 * @param pcb UDP PCB used to send the data.
 * @param p chain of pbuf's to be sent.
 *
 * The datagram will be sent to the current remote_ip & remote_port
 * stored in pcb. If the pcb is not bound to a port, it will
 * automatically be bound to a random port.
 *
 * @return lwIP error code.
 * - ERR_OK. Successful. No error occured.
 * - ERR_MEM. Out of memory.
 * - ERR_RTE. Could not find route to destination address.
 * - More errors could be returned by lower protocol layers.
 *
 * @see udp_disconnect() udp_sendto()
 */
err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)
{
  /* send to the packet using remote ip and port stored in the pcb */
  return udp_sendto(pcb, p, &pcb->remote_ip, pcb->remote_port);
}

/**
 * Send data to a specified address using UDP.
 *
 * @param pcb UDP PCB used to send the data.
 * @param p chain of pbuf's to be sent.
 * @param dst_ip Destination IP address.
 * @param dst_port Destination UDP port.
 *
 * dst_ip & dst_port are expected to be in the same byte order as in the pcb.
 *
 * If the PCB already has a remote address association, it will
 * be restored after the data is sent.
 * 
 * @return lwIP error code (@see udp_send for possible error codes)
 *
 * @see udp_disconnect() udp_send()
 */
err_t udp_sendto(struct udp_pcb *pcb, struct pbuf *p,
  ip_addr_t *dst_ip, u16_t dst_port)
{
  struct netif *netif;

  LWIP_DEBUGF(UDP_DEBUG | LWIP_DBG_TRACE, ("udp_send\n"));
  //调用ip层函数,寻找发送报文的接口,即源ip
  netif = ip_route(dst_ip);

  /* no outgoing network interface could be found? */
  if (netif == NULL) {
    return ERR_RTE;
  }
  return udp_sendto_if(pcb, p, dst_ip, dst_port, netif);
}

/**
 * Send data to a specified address using UDP.
 * The netif used for sending can be specified.
 *
 * This function exists mainly for DHCP, to be able to send UDP packets
 * on a netif that is still down.
 *
 * @param pcb UDP PCB used to send the data.
 * @param p chain of pbuf's to be sent.
 * @param dst_ip Destination IP address.
 * @param dst_port Destination UDP port.
 * @param netif the netif used for sending.
 *
 * dst_ip & dst_port are expected to be in the same byte order as in the pcb.
 *
 * @return lwIP error code (@see udp_send for possible error codes)
 *
 * @see udp_disconnect() udp_send()
 */
err_t udp_sendto_if(struct udp_pcb *pcb, struct pbuf *p,
  ip_addr_t *dst_ip, u16_t dst_port, struct netif *netif)
{
  struct udp_hdr *udphdr;
  ip_addr_t *src_ip;
  err_t err;
  struct pbuf *q; /* q will be sent down the stack */

  /* if the PCB is not yet bound to a port, bind it here */
  if (pcb->local_port == 0) {//未绑定则绑定
    err = udp_bind(pcb, &pcb->local_ip, pcb->local_port);
    if (err != ERR_OK) {
      return err;
    }
  }
  //构造udp首部
  /* not enough space to add an UDP header to first pbuf in given p chain? */
  if (pbuf_header(p, UDP_HLEN)) {//这里判断用户数据pbuf前面是否能放的下udp首部
    /* allocate header in a separate new pbuf */
    q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);//放不下则要申请个装udp首部的pbuf
    /* new header pbuf could not be allocated? */
    if (q == NULL) {
      return ERR_MEM;
    }
    if (p->tot_len != 0) {
      /* chain header q in front of given pbuf p (only if p contains data) */
      pbuf_chain(q, p);//把2个pbuf连连来,头指针为q
    }
    /* first pbuf q points to header pbuf */
  } else { //装的下则直接q指向p
    /* adding space for header within p succeeded */
    /* first pbuf q equals given pbuf */
    q = p;
  }
  /* q now represents the packet to be sent */
  udphdr = (struct udp_hdr *)q->payload;//指向udp首部
  udphdr->src = htons(pcb->local_port);//填本地port
  udphdr->dest = htons(dst_port);//填远程port
  /* in UDP, 0 checksum means 'no checksum' */
  udphdr->chksum = 0x0000; 

  /* PCB local address is IP_ANY_ADDR? */
  if (ip_addr_isany(&pcb->local_ip)) {//如果本地ip是0则使用netif中ip
    /* use outgoing network interface IP address as source address */
    src_ip = &(netif->ip_addr);
  } else {  //如果本地ip和netif中ip不一至则不发送直接返回
    /* check if UDP PCB local IP address is correct 
     * this could be an old address if netif->ip_addr has changed */
    if (!ip_addr_cmp(&(pcb->local_ip), &(netif->ip_addr))) {
      /* local_ip doesn't match, drop the packet */
      if (q != p) {
        /* free the header pbuf */
        pbuf_free(q);
        q = NULL;
        /* p is still referenced by the caller, and will live on */
      }
      return ERR_VAL;
    }
    /* use UDP PCB local IP address as source address */
    src_ip = &(pcb->local_ip); //伪首部源ip
  }

    udphdr->len = htons(q->tot_len); //首部总长度
    /* calculate checksum */
    if ((pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
      u16_t udpchksum;
        //计算udp校验和
        udpchksum = inet_chksum_pseudo(q, src_ip, dst_ip, IP_PROTO_UDP, q->tot_len);
      /* chksum zero must become 0xffff, as zero means 'no checksum' */
      if (udpchksum == 0x0000) {
        udpchksum = 0xffff;
      }
      udphdr->chksum = udpchksum;
    }
    /* output to IP *///调用ip层函数发送
    err = ip_output_if(q, src_ip, dst_ip, pcb->ttl, pcb->tos, IP_PROTO_UDP, netif);

  /* did we chain a separate header pbuf earlier? */
  if (q != p) {
    /* free the header pbuf */
    pbuf_free(q);
    q = NULL;
    /* p is still referenced by the caller, and will live on */
  }
  return err;
}
接收函数

接收处理是ip层收到ip数据报后通过协议字段判断是udp后调用udp_input
udp_input根据报文中源目ip、port寻找匹配的udp控制块,
port匹配原则如下,
1.首先寻找与报文中源目port都匹配的且处于连接状态的控制块,若没有
2.寻找与报文中目地port匹配的非连接状态的控制块
ip匹配原则如下,
1.报文是非广播包,且本地绑定的是任意ip
2.绑定的ip和报文目的ip一致
3.对于广播包,控制块的ip要和广播包在同一网段
如果匹配不到会返回一个端口不可达icmp报文
匹配好后就调用之前注册的回调函数,注意收到的数据包由用户释放,即回调函数内部释放。代码在udp.c,我就不粘贴了。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值