TCP/IP實現(十一) UDP使用者資料報協議
一.已連線UDP
我們可以對UDP套接字呼叫connect進行連線,但與TCP連線的差別很大。UDP進行連線並不進行三次握手,核心只是核心只是檢查一些立即可知的錯誤(如一個顯然不可達的目的地),並將對端的IP地址和埠號記錄在PCB協議控制塊中,之後立即返回到呼叫程序
1.與未連線UDP的區別
a) 不能在給輸出操作指定目的IP和埠號,否則將返回錯誤EISCONN。b) 不必再呼叫rcvfrom以獲悉傳送者,而改用read,recv,recvmsg。因為在一個已連線的UDP套接字上,由核心為輸入操作返回的資料報只有那些來自connnect所指定端的資料報(核心會匹配PCB中的地址)。【注】來自非connect指定端發來的資料報,本端UDP套接字是無法收到的(原因檢視
2.非同步錯誤
還有一個上面未提到的與未連線UDP的區別就是關於非同步錯誤的處理。非同步錯誤即不是與呼叫函式同時返回的錯誤,比如當伺服器未開啟時,伺服器主機會響應一個ICMP埠不可達報文,但是當UDP是未連線時,該ICMP錯誤不會返回給客戶程序,這樣一來,客戶端將永遠不知道該錯誤,並可能一直等待下去。我們稱這種ICMP錯誤為非同步錯誤,該錯誤可能由sendto引起,但sendto卻成功返回,這是因為UDP輸出操作成功返回僅僅表示在介面輸出佇列中有存放包含該UDP的IP資料報的空間(可參《TCP/IP實現 (三) 乙太網的資料收發》
3.已連線UDP與未連線UDP的效能差異
要比較兩者的效能差異,我們首選要知道當在一個未連線的UDP上呼叫sendto函式時,會先建立一個臨時連線,當傳送資料後再斷開連線,當呼叫兩次sendto,核心便要複製兩次含有目的IP地址和埠號的地址結構,而已連線的UDP只需再connect時複製一次。有人指出,臨時連線未連線的UDP套接字大約會耗費每個UDP傳輸三分之一的開銷。
二.UDP的輸出
當程序進行寫系統呼叫時,插口層會直接或間接呼叫sosend函式,該函式根據協議型別,將資料交付給相應的協議層(可以參考《TCP/IP實現(九) 插口I/O》),而對於UDP而言便是將資料交給udp_output函式進行處理。該函式處理流程如下:
// struct inpcb *inp 指向socket對應的協議控制塊,其中含有本地和外部地址資訊
// struct mbuf *m 要傳送的資料
// struct mbuf *addr 目的地址資訊
// struct mbuf *control控制資訊(在呼叫sendmsg時傳入的,UDP不適用直接丟棄)
int udp_output(struct inpcb *inp, struct mbuf *m, struct mbuf *addr, struct mbuf *control)
{
if(control)
m_free(control);// UDP從不使用控制資訊,直接丟棄(因此通過sendmsg傳入也沒用,但也無妨)
if(addr){ // 如果指定了要傳送的目的地址,說明該UDP本應該是未連線的
if(inp->inp_faddr.s_addr != INADDR_ANY) { // 若該socket以連線了一個外部地址則,不能再在傳送資料時指定目的地址
error = EISCONN;
goto release;
}
error = in_pcbconnect(inp,addr); //進行臨時連線
if(error) goto release;
}// if
else {
if(inp->inp_faddr.s_addr == INADDR_ANY) { // 若在未連線的UDP上呼叫sendto,但又未指定目的地址則返回錯誤
error = ENOTCONN;
goto release;
}
}
進行UDP首部填充和部分IP資料報的首部填充,並計算檢驗和
將添加了IP&UDP首部的資料報交付給ip層輸出函式ip_output;
}
UDP協議層判斷一個套接字是否已連線不是通過socket結構中的狀態欄位來判斷的,而是根據其對應的PCB表是否設定了外部地址來判斷的。當呼叫了ip_output後又會將資料轉交給介面層,介面層處理函式(如乙太網的ether_output)將資料放至介面輸出佇列中,介面輸出佇列也是UDP輸出過程中的唯一佇列。
三.UDP的輸入
當IP層收到一個完整的資料報(重組後的),則根據IP首部中協議型別查詢相應的協議轉換表,從而呼叫相應協議的輸入函式(會將IP資料報傳入,因為比如UDP協議計算校驗碼時需要用到IP首部中的某些欄位),UDP協議的輸入函式為udp_input。下面只對UDP收取資料報的操作進行簡要說明:
void udp_input(struct mbuf* m, int iplen)
{
// 第一步
進行首部長度檢驗,和校驗和檢驗
// 第二部
如果資料報的目的地址是一個廣播或多播地址,則搜尋udp的PCB表,將資料報交付給所有匹配地址pcb對應的socket
// 第三部
//如果資料報的目的地址是一個單播地址則進行以下處理
查詢udp的pcb表,找到最匹配的一個pcb(最少通配原則)。
if(未找到) {
//增加UDP未知埠錯誤的計數,並返回ICMP埠不可達錯誤
udpstat.udps_noportbcast++;// 可通過netstat -s檢視
icmp_error(xx,ICMP_UNREACH, ICMP_UNREACH_PORT...);
return;
}
// 找到了
若找到了匹配的pcb,則將資料報放入該pcb對應套接字的接收快取中
喚醒阻塞在該套接字上的程序
}
四.ICMP源站抑制差錯
由於UDP沒有流量控制,因此有可能傳送方傳送資料的速度比接收方的處理速度快,此時便可能產生ICMP源站一直差錯。
五.UDP資料報的資料大小原則
UDP資料報的資料大小一般不應該超過512位元組,因為要求主機必須至少能夠接收576位元組的IP資料報,因此許多UDP應用程式限制UDP中的資料大小不應超過512位元組