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;
}