前一節我們實現了基於RAW API的UDP服務器,在接下來,我們進一步利用RAW API實現UDP客戶端。
1、UDP協議簡述
UDP協議全稱是用戶數據報協議,在網絡中它與TCP協議一樣用於處理數據包,是一種無連接的協議。在OSI模型中,處於傳輸層,是IP協議的上層協議。UDP有不提供數據包分組、組裝和不能對數據包進行排序的缺點,也就是說,當報文發送之後,是無法得知其是否安全完整到達的。
UDP協議的主要作用是將網絡數據流量壓縮成數據包的形式。一個典型的數據包就是一個二進制數據的傳輸單位。每一個數據包的前8個字節用來包含報頭信息,剩餘字節則用來包含具體的傳輸數據。
UDP報頭由4個域組成,其中每個域各佔用2個字節,具體如下:源端口號、目標端口號、數據報長度、校驗值。其數據結構如下:
UDP協議使用端口號爲不同的應用保留其各自的數據傳輸通道。UDP和TCP協議正是採用這一機制實現對同一時刻內多項應用同時發送和接收數據的支持。數據發送一方(可以是客戶端或服務器端)將UDP數據包通過源端口發送出去,而數據接收一方則通過目標端口接收數據。有的網絡應用只能使用預先爲其預留或註冊的靜態端口;而另外一些網絡應用則可以使用未被註冊的動態端口。因爲UDP報頭使用兩個字節存放端口號,所以端口號的有效範圍是從0到65535。一般來說,大於49151的端口號都代表動態端口。
數據報的長度是指包括報頭和數據部分在內的總字節數。因爲報頭的長度是固定的,所以該域主要被用來計算可變長度的數據部分。數據報的最大長度根據操作環境的不同而各異。從理論上說,包含報頭在內的數據報的最大長度爲65535字節。不過,一些實際應用往往會限制數據報的大小,有時會降低到8192字節。
UDP協議使用報頭中的校驗值來保證數據的安全。校驗值首先在數據發送方通過特殊的算法計算得出,在傳遞到接收方之後,還需要再重新計算。如果某個數據報在傳輸過程中被第三方篡改或者由於線路噪音等原因受到損壞,發送和接收方的校驗計算值將不會相符,由此UDP協議可以檢測是否出錯。
2、UDP客戶端設計
前面我們簡要的介紹了UDP協議及其數據報,接下來我們將考慮怎麼實現基於UDP協議的客戶端。
首先,我們來看一看與UDP相關的API函數,並對它們作一個初步的介紹,應爲我們需要使用它們來實現我們的應用。函數及說明如下:
我們已經瞭解了UDP服務器的實現步驟,接下來我們說明一下UDP客戶端的實現步驟。
首先,依然是創建一個新的UDP控制塊。
接下來,建立與服務器的連接,配置包括服務器的地址、端口等信息。
接下來,如果連接無問題,則註冊客戶端回調函數。與服務器端的實現一樣,其複雜程度與需要實現的功能相關。我們只是實現一個簡單UDP客戶端,所以我們向服務器發送固定的信息,收到回覆後繼續發送對應的信息。
最後,由於客戶端是對話的發起方,所以在註冊完回調函數後,客戶端要發起首次對話。
3、UDP客戶端實現
對UDP服務器端的實現,我們依然將器分爲兩方面內容:一是,UDP客戶端的初始化配置部分;二是,UDP客戶端的具體實現內容,也就是回調函數的內容。
首先實現UDP客戶端的初始化配置部分。定義新的UDP控制塊,連接到指定服務器的地址及端口,同樣由於我們的驗證比較簡單我們採用迴環服務器端口。然後註冊回調函數,發起客戶端首次通訊。具體代碼如下:
/* UDP客戶端初始化配置 */
void UDP_Client_Initialization(void)
{
ip_addr_t DestIPaddr;
err_t err;
struct udp_pcb *upcb;
char data[]="This is a Client.";
/* 設置服務器端的IP地址 */
IP4_ADDR( &DestIPaddr,udpServerIP[0],udpServerIP[1],udpServerIP[2],udpServerIP[3]);
/* 創建一個新的UDP控制塊 */
upcb = udp_new();
if (upcb!=NULL)
{
/* 服務器端地址、端口配置 */
err= udp_connect(upcb, &DestIPaddr, UDP_ECHO_SERVER_PORT);
if (err == ERR_OK)
{
/* 註冊回調函數 */
udp_recv(upcb, UDPClientCallback, NULL);
/**數據發送,第一次連接時客戶端發送數據至服務器端,發送函數中會遍歷查找源IP地址的配置,如果源IP地址未配置,則數據發送失敗。該處出現的問題在後面總結中提到了**/
UdpClientSendPacket(upcb,data);
}
}
}
其次實現UDP客戶端的具體實現內容。由於我們實現的簡單的響應客戶端,所以我們只是給服務器回覆相同的內容。
/* 定義UDP客戶端數據處理回調函數 */
static void UDPClientCallback(void *arg,struct udp_pcb *upcb,struct pbuf *p,const ip_addr_t *addr,u16_t port)
{
udp_send(upcb, p); //數據回顯
pbuf_free(p);
}
/* 客戶端數據發送函數 */
void UdpClientSendPacket(struct udp_pcb *upcb,char* data)
{
struct pbuf *p;
/* 分配內存空間 */
p = pbuf_alloc(PBUF_TRANSPORT,strlen((char*)data), PBUF_POOL);
if (p != NULL)
{
/* 複製數據到pbuf */
pbuf_take(p, (char*)data, strlen((char*)data));
/* 發送數據 */
udp_send(upcb, p); //發送數據
/* 釋放pbuf */
pbuf_free(p);
}
}
當然,如果我們不想人云亦云的回覆服務器,則可以編輯我們自己的數據包然後發送回去。所以我們想要實現複雜的應用時,只需要重新編寫合適的回調函數就可以了!
4、結論
我們完成了簡單的,基於RAW API的UDP客戶端,其本身並不複雜。同樣的我們使用網絡軟件測試其功能,我們在電腦上建立一個服務器端,然後通過我們這個客戶端去連接它。能夠進行連接併發送接受數據,說明我們這個客戶端的設計是符合要求的。
至此我們完成了UDP客戶端及服務器的實現,後續我們將在次基礎上實現更爲複雜的應用。
歡迎關注: