使用TCP編寫的應用程序和使用UDP編寫的應用程序之間存在一些本質差別,其緣由在於這兩個傳輸層之間的差異:UDP是無鏈接不可靠的數據報協議,很是不一樣於TCP提供的面向鏈接的可靠字節流。然而相比TCP,有些場合更適合UDP。使用UDP編寫的一些常見應用程序有:DNS(域名系統)、NFS(網絡文件系統)和SNMP(簡單網絡管理協議)。緩存
下圖給出了典型的UDP客戶/服務器程序的函數調用。客戶沒必要與服務器創建鏈接,而是隻管使用sendto函數給服務器發送數據報,其中必須指定目的地(即服務器)的地址做爲參數。相似的,服務器不接受來自客戶的鏈接,而是隻管調用recvfrom函數,等待來自某個客戶的數據到達。recvfrom將於所接受的數據報一道返回客戶的協議地址,所以服務器能夠把響應發送給正確的客戶。服務器
這個函數相似於標準的read和write函數,不過須要三個額外的參數:網絡
#include<sys/socket.h> ssize_t recvfrom(int sockfd, void *buf, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen); ssize_t sendto(int sockfd, const void* buf, size_t nbytes, int flags, const struct sockaddr* to, sockelen_t addrlen);
前三個參數sockfd,buf和nbytes等同於read和write函數的三個參數:描述符、指向讀入或寫出緩衝區的指針和讀寫字節數。併發
sendto的to參數指向一個含有數據報接收者的協議地址(如IP地址和端口號)的套接字地址結構,其大小是由addrlen參數指定。recvfrom的from參數指向一個將由該函數在返回時填寫的數據報發送者的協議地址的套接字地址結構,而在該套接字結構中填寫的字節數則在addrlen參數所指的整數中返回給調用者。注意,sendto的最後一個參數是一個整數值,而recvfrom的最後一個參數是一個指向整數值的指針(即值-結果參數)異步
recvfrom的最後兩個參數相似於accept的最後兩個參數:返回時其中套接字的地址結構告訴咱們是誰發送了數據報(UDP狀況下)或是誰發起了鏈接(TCP狀況下)。sendto的最後兩個參數相似於connect的最後兩個參數:調用時其中套接字的地址結構被咱們填入數據報將發往(UDP狀況下)或與之創建鏈接(TCP狀況下)的協議地址。socket
這兩個函數都把所讀寫的數據的長度做爲函數返回值。在recvfrom使用數據報協議的典型用途中,返回值就是所接收數據報中的用戶數據量。函數
寫一個長度爲0的數據報是可行的。在UDP狀況下,這會造成一個只包含一個IP首部(對於IPV4一般爲20字節,對於IPV6一般是40字節)和一個8字節的UDP首部而沒有數據的IP數據報。這也意味着對於數據報協議,recvfrom返回0值是能夠接受的:它不像TCP套接字上read返回0值那樣表示對端已關閉鏈接。既然UDP是無鏈接的,所以也就沒有諸如關閉一個UDP鏈接之類的事情。性能
若是recvfrom的from是一個空指針,那麼相應的長度參數(addrlen)也必須是一個空指針,表示咱們並不關心數據報發送者的協議地址。3d
通常來講,大多數TCP服務器是併發的,而大多數UDP服務器是迭代的。指針
事實上每一個UDP套接字都有一個接收緩衝區,到達該套接字的每一個數據報都進入這個套接字的接收緩衝區。這樣,在進程可以讀該套接字任何已排好隊的數據報以前,若是有多個數據報到達該套接字,那麼相繼到達的數據報僅僅加到該套接字的接收緩衝區中。然而這個緩衝區的大小是由限制的
能夠給UDP套接字調用connect,然而這樣作的結果與TCP鏈接截然不同:沒有三路握手過程。內核只是檢查是否存在當即可知的錯誤(例如一個顯然不可達的目的地),記錄對端的IP地址和端口號(取自傳遞給connect的套接字地址結構),而後當即返回到調用進程。
對於已鏈接UDP套接字(調用connect後),與默認的未鏈接UDP套接字相比,發生了三個變化:
咱們不再能給輸出操做指定目的IP地址和端口號。也就是說,咱們不使用sento,而改用write或send。寫到已鏈接UDP套接字上的任何內容都自動發送到由connect指定的協議地址(如IP地址和端口號)。
其實咱們能夠給已鏈接UDP套接字調用sendto,可是不能指定目的地址。sendto的第五個參數(指向指明目的地址的套接字地址結構的指針)必須爲空指針,第六個參數(該套接字地址結構的大小)應該爲0。POSIX規範指出當第五個參數爲空指針時,第六個參數的取值就再也不考慮。
咱們沒必要使用recvfrom以獲悉數據報的發送者,而改用read、recv或recvmsg。在一個已鏈接UDP套接字上,由內核爲輸入操做返回的數據報只有那些來自connect所指定協議地址的數據報。目的地爲這個已鏈接UDP套接字的本地協議地址(如IP地址和端口號),發源地卻不是該套接字早先connect到的協議地址的數據報,不會投遞到該套接字。這樣就限制一個已鏈接UDP套接字能且僅能與一個對端交換數據報。
確切地說,一個已鏈接UDP套接字僅僅與一個IP地址交換數據報,由於connect到多播或廣播地址是可能的。
由已鏈接UDP套接字引起的異步錯誤會返回給它們所在的進程,而未鏈接UDP套接字不接收任何異步錯誤。
應用進程首先調用connect指定對端的IP地址和端口號,而後使用read和write與對端進程交換數據。
來自任何其餘IP地址或端口的數據報(上圖中的"???"表示)不投遞給這個已鏈接套接字,由於它們要麼源IP地址要麼源UDP端口不與該套接字connect到的協議地址相匹配。這些數據報可能投遞給同一個主機上的其餘某個UDP套接字。若是沒有相匹配的其餘套接字,UDP將丟棄它們並生成相應的ICMP端口不可達錯誤。
擁有一個已鏈接UDP套接字的進程可出於下列兩個目的之一再次調用connect:
第一個目的(即給一個已鏈接UDP套接字指定新的對端)不一樣於TCP套接字中connect的使用:對於TCP套接字,connect只能調用一次。
爲了斷開一個已鏈接UDP套接字,咱們再次調用connect時把套接字地址結構的地址族成員(對於IPv4爲sin_family,對於IPv6爲sin6_family)設置爲AF_UNSPEC。這麼作可能會返回一個EAFNOSUPPORT錯誤,不過沒有關係。使套接字斷開鏈接的是在已鏈接UDP套接字上調用connect的進程。
當應用進程在一個爲鏈接的UDP套接字上調用sendto時,源自Berkeley的內核暫時鏈接該套接字,發送數據報,而後斷開該鏈接。在一個未鏈接UDP套接字上給兩個數據報調用sendto函數因而涉及內核執行下列6個步驟:
斷開套接字鏈接
另外一個考慮是搜索路由表的次數。第一次臨時鏈接需爲目的IP地址搜索路由表並高速緩存這條信息。第二次臨時鏈接注意到目的地址等於已高速緩存的路由表信息的目的地(咱們假設這兩個sendto調用相同的目的地址),因而就沒必要再次查找路由表。
當應用進程知道本身要給同一目的地址發送多個數據報時,顯示鏈接套接字的效率更高。調用connect後調用兩次write涉及內核執行如下步驟:
在這種狀況下,內核只複製一次含有目的IP地址和端口號的套接字地址結構,相反當調用sendto時,須要賦值兩次。