網絡編程學習(五)

udp套接字編程:sendto、recvfrom

如果recvfrom的from參數是一個空指針,那麼相應的長度參數(addrlen)也必須是一個空指針,表示我們並不關心數據發送者的協議地址。


void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
    int n;
    char sendline[MAXLINE], recvline[MAXLINE + 1];
    socklen_t len;
    struct sockaddr *preply_addr;
    preply_addr = Malloc(servlen);
    while(Fgets(sendline, MAXLINE, fp) != NULL)
    {
        Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
        len = servlen;
        n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, len);
        if(len != servlen || memcmp(pservaddr, preply_addr, len) != 0)
        {
            printf("reply from %s (ignored)\n", Sock_ntop(preply_addr, len));
            continue;
        }
        recvline[n] = 0; //null terminate
        Fputs(recvline, stdout);
    }
}


針對有兩個接口和兩個IP地址的主機freebsd執行:

$./host freebsd

freebsd.unpbook.com has address 172.31.37.96

freebsd.unpbook.com has address 136.197.16.100

$./cli 136.197.16.100

hello

reply from 172.31.37.96:6 (ignored)

goodbye

reply from 172.31.37.96:6 (ignored)

我們指定的服務器IP地址不與客戶主機共享同一個子網。


recvfrom返回的IP地址不是我們所發送數據報的目的IP地址,主機freebsd內核中的路由功能爲之選擇172.31.37.96作爲外出接口。既然服務器沒有在其套接字上綁定一個實際的IP地址(服務器綁定在其套接字上的是通配IP地址,這一點可通過在freebsd上運行netstat來驗證),因此內核將爲封裝這些應答的IP數據報選擇源地址,選爲源地址的是外出接口的主IP地址。

一個解決辦法是:得到由recvfrom返回的IP地址後,客戶通過在DNS(11)中查找服務器主機的名字來驗證該主機的域名(而不是它的IP地址)。

另一個解決辦法是:UDP服務器給服務器主機上配置的每個IP地址創建一個套接字,用bind捆綁每個IP地址到各自的套接字,然後在所有這些套接字上使用select(等待其中任何一個變得可讀),再從可讀的套接字給出應答。既然用於給出應答的套接字上綁定的IP地址就是客戶請求的目的IP地址(否則該數據報不會被投遞到該套接字),這就保證應答的源地址與請求的目的地址相同(22.6)。


一個基本規則是:對於一個UDP套接字,由它引發的異步錯誤卻並不返回給它,除非它已連接。


我們確實可以給UDP套接字調用connect,然而這樣做的結果卻與TCP連接大相徑庭:沒有三路握手過程。內核只是檢查是否存在立即可知的錯誤,記錄對端的IP地址和端口號(取自傳遞給connect的套接字地址結構),然後立即返回到調用進程。

對於已連接套接字:

1.我們再也不能給輸出操作指定IP地址和端口號。也就是說,我們不使用sendto,而改用write或send。寫道已連接UDP套接字上的任何內容都自動發送到由connect指定的協議地址(例如IP地址和端口號)。

2.我們不必使用recvfrom以獲悉數據報的發送者,而改用read、recv或recvmsg。在一個已連接UDP套接字上,由內核爲輸入操作返回的數據報只有那些來自connect所指定協議地址的數據報。目的地爲這個已連接UDP套接字的本地協議地址(例如IP地址和端口號),發源地不是該套接字早先connect到的協議地址的數據報,不會投遞到該套接字。這樣就限制一個已連接UDP套接字能且僅能與一個對端(更確切地說,是一個IP地址)交換數據報。

3.由已連接UDP套接字引發的異步錯誤會返回給它們所在的進程,而未連接UDP套接字不接受任何異步錯誤。

擁有一個已連接UDP套接字的進程可處於下列兩個目的之一再次調用connect:

指定新的IP地址和端口號(對於TCP套接字,connect只能調用一次);斷開套接字(地址族成員設置爲AF_UNSPEC)

當應用進程知道自己要給同一目的地址發送多個數據報時,顯示連接套接字效率更高。


UDP發送端淹沒其接收端是輕而易舉的事情,例如因套接字緩衝區滿而丟棄的數據報

增加緩衝區的大小會稍有改善,不過仍然不能從根本上解決問題

已連接UDP套接字還可用來確定用於某個特定目的地的外出接口,這是由connect函數應用到UDP套接字時的一個副作用造成的:內核選擇本地IP地址(假設其進程未曾調用bind顯示指派它)。這個本地IP地址通過爲目的IP地址搜索路由表得到外出接口,然後選用該接口的主IP地址而選定。

在UDP套接字上調用connect並不給對端主機發送任何消息,它完全是一個本地操作,只是保存對端的IP地址和端口號。

我們還看到,在一個爲綁定端口號的UDP套接字上調用connect同時也給該套接字指派一個臨時端口。


把TCP回射程序轉換成UDP程序比較容易,然而TCP提供的許多功能也消失了:檢測丟失的分組並重傳,驗證響應是否來自正確的對端,等等。

UDP套接字可能產生異步錯誤,例如服務器主機沒有啓動應答程序等,它們是在分組發送完一段時間後才報告的錯誤。TCP套接字總是給應用進程報告這些錯誤,但是UDP套接字必須已連接才能接收這些錯誤。

UDP沒有流量控制,因此一般UDP程序不用於傳送大量數據


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章