下圖顯示了使用 udp 套接字編寫客戶/伺服器程式時的大致流程。
[img]
udp 中的客戶不需要與伺服器建立連線,而是採用 sendto 和 recvfrom 函式來傳送和接收資料。
#include
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
const struct sockaddr *to, socklen_t addrlen);
/* 兩個函式的返回值:若成功則為讀或寫的位元組數;否則為 -1 */
這兩個函式的前三個引數等同於 read 和 write 的三個引數:描述符、緩衝區和讀寫位元組數,flag 引數將在後面討論 recv、send 等函式時再介紹,目前先將其置為 0。
sendto 的 to 引數代表資料報接收者的位址,其大小由 addrlen 引數指定。recvfrom 的 from 引數代表資料報傳送者的位址,其大小被放置在 addrlen 引數指向的位置中(注意,如果 from 是空指標,則 addrlen 也必須是乙個空指標,表示不關心傳送者。這樣做存在乙個風險:知道客戶位址和臨時埠號的任何程序都可以冒充伺服器的應答向本客戶傳送資料報)。這兩個函式的最後兩個引數分別類似於 connect 和 accept 的最後兩個引數。儘管這兩個函式也可以用於 tcp,不過通常沒必要。
在 udp 中,寫乙個長度為 0 的資料報會形成乙個只包含 ip 首部(ipv4 通常為 20 個位元組,ipv6 通常為 40 個位元組)和乙個 8 位元組的 udp 首部而沒有資料的 ip 資料報。這也意味著 recvfrom 返回 0 值是可接受的,而並不像 tcp 套接字上 read 返回 0 值那樣表示對端已關閉連線。
下面是使用 udp 套接字編寫的簡單回射伺服器示例(省略了對函式返回值的檢測)。
#include
#include
#include
#include
#include
#define serv_port 9877
typedef struct sockaddr sa;
void dgram_echo(int);
int main(void)
#define maxline 2048
void dgram_echo(int sockfd)
}
由於無需連線,所以大多數 udp 伺服器提供的都是乙個迭代伺服器,而不是像 tcp 伺服器那樣需要提供乙個併發伺服器。
每個 udp 套接字都有乙個接收緩衝區,到達該套接字的每個資料報都進入這個緩衝區。當程序呼叫 recvfrom 時,緩衝區中的下乙個資料報就以 fifo 的順序遞送給程序。
接下來也給出了客戶端的**示例。
#include
#include
#include
#include
#include
#include
#include
#define serv_port 9877
#define maxline 2048
typedef struct sockaddr sa;
void dgram_cli(file *fp, int sockfd, const sa *paddr, socklen_t addrlen)
free(preply_addr);
}int main(int argc, char *argv)
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = af_inet;
servaddr.sin_port = htons(serv_port);
inet_pton(af_inet, argv[1], &servaddr.sin_addr);
int sockfd = socket(af_inet, sock_dgram, 0);
dgram_cli(stdin, sockfd, (sa *)&servaddr, sizeof(servaddr));
exit(0);
}
要注意的是,這裡的 udp 客戶/伺服器例子是不可靠的。因為如果乙個客戶資料報或者伺服器的應答丟失了,客戶都將永遠阻塞於 recvfrom 呼叫上。防止這樣永久阻塞的一般方法是給客戶的 recvfrom 設定乙個超時,但這也不是完整的解決辦法,因為這會使得我們無從判定超時原因是客戶的資料報沒有到達伺服器,還是伺服器的應答沒有回到客戶。後面會再繼續討論如何給 udp 客戶/伺服器程式增加可靠性。
另外,為了避免收到來自非請求伺服器的資料報,客戶程式對 recvfrom 返回的資料報傳送者的位址進行了過濾(或直接使用 connect,見下)。不過如果伺服器主機是多宿的,這種方式可能會使伺服器的某些應答資料報丟失。因為大多數 ip 實現接受目的位址為本主機任一 ip 位址的資料報,而不管資料報到達的介面(rfc 稱之為弱端系統模型。反之,強端系統模型則只接受到達介面與目的位址一致的資料報),所以當伺服器繫結到其套接字上的是通配 ip 位址時,核心會自行為封裝應答的 ip 資料報選擇乙個源位址作為外出介面的主 ip 位址,而這可能不是客戶請求的那個位址,因而被客戶程式誤過濾掉了。對該問題的乙個解決辦法是:客戶得到由 recvfrom 返回的 ip 位址後,通過在 dns 中查詢伺服器主機的名字來驗證該主機的網域名稱而非 ip 位址。另乙個解決辦法是:udp 伺服器給伺服器主機上配置的每個 ip 位址都建立乙個套接字,並繫結每個 ip 位址到各自的套接字。然後使用 select 來等待其中任何乙個變得可讀,再從可讀的套接字給出應答。
在 udp 套接字中,對於像由 sendto 等函式引起的非同步錯誤(如向乙個未執行的伺服器程式傳送資料報時會產生「埠不可達」的 icmp 錯誤資訊,該錯誤由 sendto 引起,但是 sendto 本身卻能成功返回)並不返回給它,除非它已連線。儘管 udp 是不需要連線的,不過也確實可以在 udp 套接字上呼叫 connect,只是它沒有三路握手過程,核心只是檢查是否存在立即可知的錯誤,並記錄對端的 ip 位址和埠後,然後就立即返回。
已連線的 udp 套接字與預設的未連線的 udp 套接字相比,發生了以下三個變化。
(1)不能再給輸出操作指定目的位址和埠號,即不使用 sendto(或者將其第五個和第六個引數分別置為空指標和 0)而改用 write 或 send。寫到已連線 udp 套接字上的任何內容都自動傳送到由 connect 指定的位址。
(2)同理,不必使用 recvfrom 以獲悉資料報的傳送者,而改用 read、recv 或 recvmsg,因為核心只返回那些來自 connect 所指定的目的位址的資料報。
(3)會返回非同步錯誤。
一般呼叫 connect 的通常是 udp 客戶,不過有些 udp 伺服器(如 tftp)會與單個客戶長時間通訊,在這種情況下,客戶和伺服器都可能呼叫 connect。
下圖的 dns 是乙個呼叫 connect 的例子。
[img]
通常通過在 /etc/resolv.conf 中列出伺服器主機的 ip 位址,乙個 dns 客戶就能被配置成使用乙個或多個 dns 伺服器。如果列出的是單個伺服器主機(如圖中第乙個客戶),客戶程序就可以呼叫 connect,但如果列出的是多個伺服器主機(如圖中第二個客戶),客戶程序就不能呼叫 connect。另外,dns 伺服器程序通常是處理多個客戶請求的,因此不能呼叫 connect。
此外,擁有乙個已連線 udp 套接字的程序可出於下列兩個目的之一再次呼叫 connect:
(1)指定新的 ip 位址和埠號。這種情況對於 tcp 套接字,connect 只能呼叫一次。
(2)斷開套接字。為了斷開乙個已連線的 udp 套接字,可以在再次呼叫 connect 時把套接字位址結構的位址族成員 sin_family 或 sin6_family 設定成 af_unspec,這麼做可能會返回乙個 eafnosupport 錯誤,不過沒有關係。使套接字斷開連線的是在已連線 udp 套接字上呼叫 connect 的程序。
當應用程序知道自己要給同一目的位址傳送多個資料報時,顯示連線套接字效率會更高。因為在乙個未連線的 udp 套接字上傳送資料報時,源自 berkeley 的核心會暫時連線該套接字,然後傳送資料報,之後又斷開該連線,即使下乙個資料報是傳送到同一位址也是如此。
UDP套接字程式設計
與面向連線的協議相比,面向無連線協議極為不同。其中乙個重要的不同點就是客戶端與伺服器之間不必建立連線。對於udp套接字程式設計而言,伺服器建立套接字後,呼叫bind 函式將套接字與準備接收資料的介面繫結在一起。和tcp程式設計不同的是,應用程式不必呼叫listen 和accept 函式等待客戶端的連...
UDP套接字程式設計
udp是使用者資料報協議,與tcp協議一樣,在網路中處理資料報,是一種無連線的傳輸層協議,具有不可靠性,udp有不提供資料報分組 組裝和不能對資料報進行排序的缺點,也就是說,當報文傳送之後,是無法得知其是否安全完整到達的。基於udp的客戶端伺服器 伺服器 include include includ...
基本UDP套接字程式設計
udp函式呼叫為 客戶不與伺服器建立連線,而是只管使用sendto函式給伺服器傳送資料報,其中必須指定目的地的位址為引數。類似地,伺服器不接受來客戶的連線,而是只管呼叫recvfrom函式,等待來自某個客戶的資料到達,recvfrom將與所接收的資料一道返回客戶的協議位址,因此可以把響應傳送給正確的...