1. 程式人生 > >UDP】- 實現UDP通訊

UDP】- 實現UDP通訊

目錄

基於LwIP實現UDP通訊

1 什麼是UDP

UDP,即使用者資料包協議,屬於TCP/IP 中的傳輸層。同樣,TCP,即傳輸控制協議,也是屬於TCP/IP傳輸層。這兩者區別在此處不加以解釋,本文主要講解如何通過LwIP實現UDP傳輸。

UDP在傳輸資料之前不需建立連線。遠端收到UDP使用者資料報,是不需要給出任何應答。雖然UDP是一種不可靠的交付,但在某些情況下UDP卻是一種有效的傳輸方式。

2 基於raw/callback API的UDP

raw/callback API提供了多個UDP相關的介面,如下所示:

struct udp_pcb * udp_new        (void); // 動態分配一個UDP控制塊
void             udp_remove     (struct udp_pcb *pcb); // 釋放一個UDP控制塊
err_t            udp_bind       (struct udp_pcb *pcb, ip_addr_t *ipaddr, 
                                 u16_t port); // UDP控制塊繫結本地IP和本地埠
err_t            udp_connect    (struct udp_pcb *pcb, ip_addr_t *ipaddr,
                                 u16_t port); // UDP控制塊連線遠端IP和遠端埠
void             udp_disconnect (struct udp_pcb *pcb); // 取消UDP控制塊與遠端socket的連線關係
void             udp_recv       (struct udp_pcb *pcb, udp_recv_fn recv,
                                 void *recv_arg); // UDP回撥
err_t            udp_sendto_if  (struct udp_pcb *pcb, struct pbuf *p,
                                 ip_addr_t *dst_ip, u16_t dst_port,
                                 struct netif *netif); // 指定網絡卡及遠端socket傳送UDP使用者資料報
err_t            udp_sendto     (struct udp_pcb *pcb, struct pbuf *p,
                                 ip_addr_t *dst_ip, u16_t dst_port); // 指定遠端socket傳送UDP使用者資料報
err_t            udp_send       (struct udp_pcb *pcb, struct pbuf *p); // 根據UDP控制塊連線的遠端socket傳送UDP使用者資料報

// 以下幾個用於UDP校驗,當UDP不使用校驗,則校驗預設為0。當使用校驗,如果校驗值為0,則改成0xFFFF。
#if LWIP_CHECKSUM_ON_COPY
err_t            udp_sendto_if_chksum(struct udp_pcb *pcb, struct pbuf *p,
                                 ip_addr_t *dst_ip, u16_t dst_port,
                                 struct netif *netif, u8_t have_chksum,
                                 u16_t chksum);
err_t            udp_sendto_chksum(struct udp_pcb *pcb, struct pbuf *p,
                                 ip_addr_t *dst_ip, u16_t dst_port,
                                 u8_t have_chksum, u16_t chksum);
err_t            udp_send_chksum(struct udp_pcb *pcb, struct pbuf *p,
                                 u8_t have_chksum, u16_t chksum);
#endif /* LWIP_CHECKSUM_ON_COPY */

#define          udp_flags(pcb) ((pcb)->flags) // 
#define          udp_setflags(pcb, f)  ((pcb)->flags = (f))

/* The following functions are the lower layer interface to UDP. */
void             udp_input      (struct pbuf *p, struct netif *inp); // UDP輸入,獲取資料

void             udp_init       (void);    // UDP初始化,主要做UDP的記憶體池和記憶體棧初始化

1.1 udp_init

與UDP記憶體池和記憶體棧初始化相關。

1.2 udp_input

處理UDP使用者資料報相關。

1.3 udp_new和udp_remove

udp_new: 動態分配一個UDP控制塊,該控制塊記錄本地socket和遠端socket等資訊。

udp_remove: 釋放UDP控制塊

至於剩下的介面,上面的註釋已經解釋很清楚了。以下主要分析udp_bind、udp_connect和幾個傳送介面。

3 raw/callback API UDP的繫結、連線和傳送

udp_connect和幾個傳送介面,在沒有開發者沒有為UDP控制塊繫結本地socket時,這幾個介面預設會呼叫udp_bind,為UDP控制塊繫結socket,其中埠是動態分配的。

udp_conncet與udp_bind的區別在於UDP控制塊的標誌是否被設定為連線態(UDP_FLAGS_CONNECTED),因此我們可以對這個進行劃分:

  • 連線態: udp_conncet
  • 非連線態: udp_bind

udp_conncet用有特定遠端socket,而udp_bind用於任何遠端socket。也就是說,只要傳送給開發板的資料報文的遠端socket與開發板本地socket一致,開發板都會正常接收該UDP資料報。如果開發板有UDP控制塊指定了遠端socket,也有無指定的遠端socket控制塊,則會優先匹配有遠端socket的控制塊。當然,收到的UDP資料報的源socket不與開發板任何一個UDP,只要本地socket匹配都會處理該資料報。當然,這種現象是不可能的,畢竟一個UDP控制塊對應一個唯一的埠。總的來說,udp_conncet只處理指定遠端socket的UDP資料報,而udp_bind可以處理任何遠端socket的UDP資料報(前提該資料報符合本地socket)。

下列給出上述兩種情況的例子:

(1) 指定遠端socket例子

void udp_demo_callback(void *arg, struct udp_pcb *pcb, struct pbuf *p,
    ip_addr_t *addr, u16_t port)
{
	struct ip_addr my_ipaddr;
	unsigned char *temp = (unsigned char *)addr;
	IP4_ADDR(&my_ipaddr, temp[0], temp[1], temp[2], temp[3]); // 儲存源IP
	udp_sendto(pcb, p, &my_ipaddr, port); // 將報文返回給原主機
	pbuf_free(p);
}

u8 udp_demo(void)
{
	struct udp_pcb *pcb;
	ip_addr_t remote_ip;
	pcb	= udp_new();
	if(pcb == NULL)	 // 申請失敗
	{
		return 1;
	}else
	{
		IP4_ADDR(&remote_ip,192, 168, 1, 100);
		if(udp_connect(pcb, &remote_ip, 8080) == ERR_OK ) // 連線到指定的IP地址和埠
		{
			udp_recv(pcb, udp_demo_callback, NULL); // 註冊報文處理回撥				
			printf("local_port %d\r\n", pcb->local_port);
		}else
			return 1;
	}
	return 0;
}

(2) 不指定遠端socket例子

void udp_demo_callback(void *arg, struct udp_pcb *pcb, struct pbuf *p,
    ip_addr_t *addr, u16_t port)
{
	struct ip_addr my_ipaddr;
	unsigned char *temp = (unsigned char *)addr;
	IP4_ADDR(&my_ipaddr, temp[0], temp[1], temp[2], temp[3]); // 儲存源IP
	udp_sendto(pcb, p, &my_ipaddr, port); // 將報文返回給原主機
	pbuf_free(p);
}

u8 udp_demo(void)
{
	struct udp_pcb *pcb;
	ip_addr_t remote_ip;
	pcb	= udp_new();
	if(pcb == NULL)	 // 申請失敗
	{
		return 1;
	}else
	{
 
		if(udp_bind(pcb, IP_ADDR_ANY, 8080) == ERR_OK ) // 為本地IP繫結埠,IP_ADDR_ANY為0,其實說明使用本地IP地址,推薦優先使用。因為DHCP情況下,我們是無法事先知道IP的。
		{
			udp_recv(pcb, udp_demo_callback, NULL);     // 註冊報文處理回撥				
			printf("local_port %d\r\n", pcb->local_port);
		}else
			return 1;
	}
	return 0;
}