LWIP之UDP協議

IP協議提供了在各個主機之間傳送數據報的功能,但是數據的最終目的地是主機上的特定應用程序。傳輸層協議就承擔了這樣的責任,典型的傳輸層協議有UDP和TCP兩種。許多著名的上層應用都是基於UDP實現的,比如DNS、DHCP、IGMP、SNMP等。

 

UDP協議稱爲用戶數據包協議,是一種無連接、不可靠的傳輸協議。UDP協議只是簡單地完成應用程序到應用程序的交互,並不提供流量控制機制、複雜的差錯控制方法、確認機制。雖然UDP可靠性非常差,但是UDP適用於那些輕微數據差錯不敏感的應用,比如視頻傳輸、網絡電話等。

 

每臺主機都包含稱爲協議端口的抽象目的點,端口號範圍爲0~65535,進程綁定到端口號上。這樣數據包只要遞交到相應的端口即可。

 

報文格式

僞首部:真實傳輸的UDP數據包中沒有該字段,只在計算校驗和時虛擬出該字段。

struct udp_hdr {
  PACK_STRUCT_FIELD(u16_t src);    //源端口號
  PACK_STRUCT_FIELD(u16_t dest);   //目的端口號
  PACK_STRUCT_FIELD(u16_t len);    //總長度(UDP首部+UDP數據)
  PACK_STRUCT_FIELD(u16_t chksum); //校驗和(UDP僞首部+UDP首部+UDP數據)(0:不計算校驗和 0xFFFF:檢驗和爲0)
} PACK_STRUCT_STRUCT;

 

先看一下UDP控制塊

/* 所有控制塊共有部分 */
#define IP_PCB \
  struct ip_addr local_ip; \    //本機IP
  struct ip_addr remote_ip; \   //遠端IP
  u16_t so_options;      \      //套接字選項
  u8_t tos;              \      //服務類型
  u8_t ttl               \      //生存時間
  IP_PCB_ADDRHINT

/* 不進行校驗 */
#define UDP_FLAGS_NOCHKSUM 0x01U
/* UDP-LITE協議 */
#define UDP_FLAGS_UDPLITE  0x02U
/* 已綁定遠程端口 */
#define UDP_FLAGS_CONNECTED  0x04U

/* UDP控制塊 */
struct udp_pcb {
  IP_PCB;                            //IP控制塊相關字段
  struct udp_pcb *next;              //用於將UDP控制塊連接成鏈表
  u8_t flags;                        //控制塊狀態
  u16_t local_port, remote_port;     //本地端口、遠程端口

  /* 接收回調函數 */
  void (* recv)(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, u16_t port);
  /* 用戶提供的接收回調函數參數 */
  void *recv_arg;  
};
/* UDP控制塊鏈表 */
extern struct udp_pcb *udp_pcbs;

UDP控制塊最終被組織成一張表

 

下面分析UDP創建和刪除的一些API

/* 創建一個UDP控制塊 */
struct udp_pcb *udp_new(void)
{
  struct udp_pcb *pcb;

  /* 爲UDP控制塊申請內存空間 */
  pcb = memp_malloc(MEMP_UDP_PCB);
  if (pcb != NULL) {
    /* 清空UDP控制塊 */
    memset(pcb, 0, sizeof(struct udp_pcb));
	/* 設置UDP控制塊的TTL值 */
    pcb->ttl = UDP_TTL;
  }
  
  return pcb;
}
/* 綁定本地端口,0表示自動分配端口號 */
err_t udp_bind(struct udp_pcb *pcb, struct ip_addr *ipaddr, u16_t port)
{
  struct udp_pcb *ipcb;
  u8_t rebind;

  rebind = 0;
  
  /* 遍歷UDP控制塊鏈表 */
  for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
    /* 該控制塊已經綁定本地端口 */
    if (pcb == ipcb) {
      rebind = 1;
    }
  }

  /* 設置UDP控制塊的本地IP地址 */
  ip_addr_set(&pcb->local_ip, ipaddr);

  /* 自動分配端口號 */
  if (port == 0) {
#define UDP_LOCAL_PORT_RANGE_START 4096
#define UDP_LOCAL_PORT_RANGE_END   0x7fff

	/* 遍歷整個UDP控制塊鏈表,查找最小的未使用端口號 */
    port = UDP_LOCAL_PORT_RANGE_START;
    ipcb = udp_pcbs;
    while ((ipcb != NULL) && (port != UDP_LOCAL_PORT_RANGE_END)) {
      if (ipcb->local_port == port) {
        port++;
        ipcb = udp_pcbs;
      } else
        ipcb = ipcb->next;
    }
    /* 沒有可用端口號 */
    if (ipcb != NULL) {
      return ERR_USE;
    }
  }

  /* 設置本地端口號 */
  pcb->local_port = port;
  
  /* 將UDP控制塊插入鏈表 */
  if (rebind == 0) {
    pcb->next = udp_pcbs;
    udp_pcbs = pcb;
  }

  return ERR_OK;
}
/* 綁定遠程端口 */
err_t udp_connect(struct udp_pcb *pcb, struct ip_addr *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;
  /* 將UDP控制塊狀態設置爲已綁定遠程端口 */
  pcb->flags |= UDP_FLAGS_CONNECTED;

  /* UDP控制塊如果沒有插入鏈表,則將其插入鏈表 */
  for (ipcb = udp_pcbs; ipcb != NULL; ipcb = ipcb->next) {
    if (pcb == ipcb) {
      return ERR_OK;
    }
  }
  pcb->next = udp_pcbs;
  udp_pcbs = pcb;
  
  return ERR_OK;
}
/* 取消綁定遠程端口 */
void udp_disconnect(struct udp_pcb *pcb)
{
  /* 重置UDP控制塊遠程IP和端口號 */
  ip_addr_set(&pcb->remote_ip, IP_ADDR_ANY);
  pcb->remote_port = 0;
  
  /* 將UDP控制塊狀態設置爲未綁定遠程端口 */
  pcb->flags &= ~UDP_FLAGS_CONNECTED;
}
/* 設置UDP控制塊回調函數,和回調函數用戶參數 */
void udp_recv(struct udp_pcb *pcb, void (* recv)(void *arg, struct udp_pcb *upcb, struct pbuf *p, struct ip_addr *addr, u16_t port), void *recv_arg)
{
  pcb->recv = recv;
  pcb->recv_arg = recv_arg;
}
/* 刪除UDP控制塊 */
void udp_remove(struct udp_pcb *pcb)
{
  struct udp_pcb *pcb2;

  /* 將UDP控制塊從鏈表中移除 */
  if (udp_pcbs == pcb) {
    udp_pcbs = udp_pcbs->next;
  } else
    for (pcb2 = udp_pcbs; pcb2 != NULL; pcb2 = pcb2->next) {
      if (pcb2->next != NULL && pcb2->next == pcb) {
        pcb2->next = pcb->next;
      }
    }

  /* 釋放UDP控制塊內存空間 */
  memp_free(MEMP_UDP_PCB, pcb);
}

接下來看一下UDP發送API

/* 發送UDP數據包(已經綁定遠程主機端口) */
err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)
{
  return udp_sendto(pcb, p, &pcb->remote_ip, pcb->remote_port);
}

/* 指定遠程端口,發送UDP數據包 */
err_t udp_sendto(struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *dst_ip, u16_t dst_port)
{
  struct netif *netif;

  /* 根據IP地址選擇一個合適(和目的主機處於同一子網)的網絡接口 */
  netif = ip_route(dst_ip);
  if (netif == NULL) {
    return ERR_RTE;
  }

  /* 指定網絡接口發送UDP數據包 */
  return udp_sendto_if(pcb, p, dst_ip, dst_port, netif);
}

/* 指定網絡接口和遠程端口,發送UDP數據包 */
err_t udp_sendto_if(struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *dst_ip, u16_t dst_port, struct netif *netif)
{
  struct udp_hdr *udphdr;
  struct ip_addr *src_ip;
  err_t err;
  struct pbuf *q;

  /* 沒有綁定本地端口,先綁定本地端口 */
  if (pcb->local_port == 0) {
    err = udp_bind(pcb, &pcb->local_ip, pcb->local_port);
    if (err != ERR_OK) {
      return err;
    }
  }

  /* 向前調整出UDP頭部空間,調整失敗 */
  if (pbuf_header(p, UDP_HLEN)) {
    /* 申請UDP頭部空間 */
    q = pbuf_alloc(PBUF_IP, UDP_HLEN, PBUF_RAM);
    if (q == NULL) {
      return ERR_MEM;
    }
    /* 將兩個pbuf串起來 */
    pbuf_chain(q, p);
  }
  /* 調整成功 */
  else {
    /* 直接獲取pbuf指針 */
    q = p;
  }

  /* UDP頭部指針 */
  udphdr = q->payload;
  /* 設置UDP頭部源端口號 */
  udphdr->src = htons(pcb->local_port);
  /* 設置UDP頭部目的端口號 */
  udphdr->dest = htons(dst_port);
  /* 0表示不校驗 */
  udphdr->chksum = 0x0000; 

  /* UDP本地IP爲0,表示自動選擇合適的網絡接口發送 */
  if (ip_addr_isany(&pcb->local_ip)) {
    /* 設置源IP地址 */
    src_ip = &(netif->ip_addr);
  } 
  else {
    /* 本地IP地址和合適的網絡接口IP地址不相同 */
    if (!ip_addr_cmp(&(pcb->local_ip), &(netif->ip_addr))) {
      /* 釋放數據包,返回錯誤 */
      if (q != p) {
        pbuf_free(q);
        q = NULL;
      }
      return ERR_VAL;
    }
    
    /* 本地IP地址和合適的網絡接口IP地址相同 */
    /* 設置源IP地址 */
    src_ip = &(pcb->local_ip);
  }

  {
    /* 設置UDP頭部中的總長度字段 */
    udphdr->len = htons(q->tot_len);
    
    /* 必須進行和校驗 */
    if ((pcb->flags & UDP_FLAGS_NOCHKSUM) == 0) {
      /* 計算並設置UDP和校驗 */
      udphdr->chksum = inet_chksum_pseudo(q, src_ip, dst_ip, IP_PROTO_UDP, q->tot_len);
      if (udphdr->chksum == 0x0000) udphdr->chksum = 0xffff;
    }

	/* 將UDP數據包通過IP輸出函數發送出去 */
    err = ip_output_if(q, src_ip, dst_ip, pcb->ttl, pcb->tos, IP_PROTO_UDP, netif);
  }

  /* 釋放數據包空間 */
  if (q != p) {
    pbuf_free(q);
    q = NULL;
  }

  return err;
}

最後看一下接口函數

/* UDP數據包接收處理函數 */
void udp_input(struct pbuf *p, struct netif *inp)
{
  struct udp_hdr *udphdr;
  struct udp_pcb *pcb, *prev;
  struct udp_pcb *uncon_pcb;
  struct ip_hdr *iphdr;
  u16_t src, dest;
  u8_t local_match;
  u8_t broadcast;

  /* IP頭部 */
  iphdr = p->payload;

  /* 檢驗UDP長度是否合理,pbuf指針向後調整剝離IP頭部 */
  if (p->tot_len < (IPH_HL(iphdr) * 4 + UDP_HLEN) || pbuf_header(p, -(s16_t)(IPH_HL(iphdr) * 4))) {
    pbuf_free(p);
    goto end;
  }

  /* UDP頭部指針 */
  udphdr = (struct udp_hdr *)p->payload;

  /* 判斷IP地址是不是廣播地址 */
  broadcast = ip_addr_isbroadcast(&(iphdr->dest), inp);

  /* 取出源IP和目的IP */
  src = ntohs(udphdr->src);
  dest = ntohs(udphdr->dest);

  {
    prev = NULL;
    local_match = 0;
    uncon_pcb = NULL;
    /* 遍歷UDP控制塊鏈表 */
    for (pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) {
      local_match = 0;

      /* 判斷該UDP數據包是不是發給該UDP控制塊(廣播、IP和端口號相同) */
      if ((pcb->local_port == dest) && ((!broadcast && ip_addr_isany(&pcb->local_ip)) || ip_addr_cmp(&(pcb->local_ip), &(iphdr->dest)) || (broadcast))) {
		/* 匹配成功 */
        local_match = 1;

        /* 記錄第一個匹配到的沒有綁定遠程端口的控制塊 */
        if ((uncon_pcb == NULL) &&  ((pcb->flags & UDP_FLAGS_CONNECTED) == 0)) {
          uncon_pcb = pcb;
        }
      }
      
      /* 判斷該數據包是不是綁定的遠程端口相同 */
      if ((local_match != 0) && (pcb->remote_port == src) && (ip_addr_isany(&pcb->remote_ip) || ip_addr_cmp(&(pcb->remote_ip), &(iphdr->src)))) {
		/* 將控制塊移到鏈表首部,增加效率 */
        if (prev != NULL) {
          prev->next = pcb->next;
          pcb->next = udp_pcbs;
          udp_pcbs = pcb;
        }
        break;
      }
      prev = pcb;
    }

    /* 沒有找到完全匹配的,就用沒有綁定遠程端口的控制塊 */
    if (pcb == NULL) {
      pcb = uncon_pcb;
    }
  }

  /* 匹配到合適的控制塊 */
  if (pcb != NULL || ip_addr_cmp(&inp->ip_addr, &iphdr->dest)) {
    {
	  /* 需要檢查校驗和 */
      if (udphdr->chksum != 0) {
      	/* 進行檢驗和檢查(UDP僞首部+UDP首部+UDP數據) */
        if (inet_chksum_pseudo(p, (struct ip_addr *)&(iphdr->src), (struct ip_addr *)&(iphdr->dest), IP_PROTO_UDP, p->tot_len) != 0) {
          pbuf_free(p);
          goto end;
        }
      }
    }

    /* 向後調整剝離UDP首部 */
    if(pbuf_header(p, -UDP_HLEN)) {
      pbuf_free(p);
      goto end;
    }

    /* 已經匹配到UDP控制塊 */
    if (pcb != NULL) {
      /* 調用UDP接收回調函數 */
      if (pcb->recv != NULL) {
        pcb->recv(pcb->recv_arg, pcb, p, &iphdr->src, src);
      } 
      else {
        pbuf_free(p);
        goto end;
      }
    } 
    /* 沒有匹配到UDP控制塊 */
    else {
	  /* 該UDP數據包不是廣播也是不組播 */
      if (!broadcast && !ip_addr_ismulticast(&iphdr->dest)) {
		/* 發送ICMP目的不可達報文(端口不可達) */
        pbuf_header(p, (IPH_HL(iphdr) * 4) + UDP_HLEN);
        icmp_dest_unreach(p, ICMP_DUR_PORT);
      }
      pbuf_free(p);
    }
  } else {
    pbuf_free(p);
  }
end:
  return;
}

 

UDP客戶端步驟

1.udp_new,創建UDP控制塊

2.udp_bind,綁定本地端口(該步驟可省略,省略時自動分配本地端口號)

3.udp_recv,註冊接收回調函數

4.udp_sendto,指定遠程端口,發送數據包

1.udp_new,創建UDP控制塊

2.udp_bind,綁定本地端口(該步驟可省略,省略時自動分配本地端口號)

3.udp_connect,綁定遠程端口

4.udp_recv,註冊接收回調函數

5.udp_send,發送數據包

 

UDP服務器步驟

1.udp_new,創建UDP控制塊

2.udp_bind,綁定本地端口

3.udp_recv,註冊接收回調函數

4.udp_sendto,指定遠程端口,發送數據包

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章