1. 程式人生 > >TCP/IP實現(十一) UDP使用者資料報協議

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套接字是無法收到的(原因檢視

《TCP/IP實現(十) 協議控制塊》

2.非同步錯誤

       還有一個上面未提到的與未連線UDP的區別就是關於非同步錯誤的處理。非同步錯誤即不是與呼叫函式同時返回的錯誤,比如當伺服器未開啟時,伺服器主機會響應一個ICMP埠不可達報文,但是當UDP是未連線時,該ICMP錯誤不會返回給客戶程序,這樣一來,客戶端將永遠不知道該錯誤,並可能一直等待下去。我們稱這種ICMP錯誤為非同步錯誤,該錯誤可能由sendto引起,但sendto卻成功返回,這是因為UDP輸出操作成功返回僅僅表示在介面輸出佇列中有存放包含該UDP的IP資料報的空間(可參《TCP/IP實現 (三) 乙太網的資料收發》

),還有一點順便說一下,即UDP的輸出過程中有且僅有一個緩衝佇列,即介面層輸出佇列。下面會對UDP資料報的傳送接收流程做一個說明。綜上所述,即由已連線的UDP套接字引發的非同步錯誤會返回給它們所在的程序,而未連線套接字不接收任何非同步錯誤。

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位元組