上一篇我們提到,除非套接字已連接,否則異步錯誤是不會返回到UDP套接字的。我們確實可以給UDP套接字調用connect,然而這樣做的結果卻與TCP連接大相徑庭:沒有三次握手。內核只是檢查是否存在立即可知的錯誤(例如一個顯然不可達的目的地),記錄對端的IP地址和端口號(取自傳遞給connect的套接字地址結構),然後立即返回到調用進程。
有了這個能力後,我們必須區分:
(1)未連接UDP套接字,新創建UDP套接字默認如此;
(2)已連接UDP套接字,對UDP套接字調用connect的結果。
對於已連接UDP套接字,與默認的未連接UDP套接字相比,發生了三個變化:
(1)我們再也不能給輸出操作指定目的IP地址和端口號。也就是說,我們不使用sendto,而改用write或send。寫到已連接UDP套接字上的任何內容都自動發送到由connect指定的協議地址(例如IP地址和端口號)。(其實我們可以給已連接UDP套接字調用sendto,但是不能指定目的地址。sendto的第五個參數必須為空,第六個參數應該為0)。
後面有在ubuntu 10.04系統下的驗證。
(2)我們不必使用recvfrom以獲悉數據報的發送者,而改用read,recv或recvmsg。在一個已連接UDP套接字上,由內核為輸入操作返回的數據報只有那些來自connect所指定協議地址的數據報。(確切的說,一個已連接的UDP套接字僅僅與一個IP地址交換數據報,因為connect到多播或廣播地址是可能的)。
(3)由已連接的UDP套接字引發的異步錯誤會返回給他們所在的進程,而未連接UDP套接字不接受任何異步錯誤。
應用進程首先調用connect指定對端的IP地址和端口號,然後使用read和write與對端進程交換數據。來自任何其他IP地址或端口的數據報(上中我們用“???”表示)不投遞給這個已連接套接字,因為他們要麼源IP地址要麼源UDP端口不與該套接字connect到的協議地址相匹配。這些數據報可能投遞給同一個主機上的其他某個UDP套接字。如果沒有相匹配的其他套接字,UDP將丟棄他們並生成相應的ICMP端口不可達錯誤。
1.給一個UDP套接字多次調用connect
擁有一個已連接UDP套接字的進程可出於下列兩個目的之一再次調用connect:
指定新的IP地址和端口號;
斷開套接字。
第一個目的(即給一個已連接UDP套接字指定新的對端)不同於TCP套接字中的connect的使用:對於TCP套接字,connect只能調用一次。
為了斷開一個已UDP套接字連接,我們再次調用connect時把套接字地址結構的地址族成員(sin_family)設置為AF_UNSPEC。使套接字斷開連接的是在已連接UDP套接字上調用connect的進程。
查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/
2.性能
在一個未連接的UDP套接字上給兩個數據報調用sendto函數於是涉及內核執行下列6個步驟:
(1)連接套接字;
(2)輸出第一個數據報;
(3)斷開套接字連接;
(4)連接套接字;
(5)輸出第二個數據報;
(6)斷開套接字連接。
調用connect後調用兩次write涉及內核執行3個步驟:
(1)連接套接字;
(2)輸出第一個數據報;
(3)輸出第二個數據報。
客戶端程序:
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define SERV_PORT 3333 #define MAXLINE 1024 #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) typedef struct sockaddr SA; void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; ///////////////////////////////////////////////////////////////////////// struct sockaddr_in servaddr; bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET, "192.168.2.103", &servaddr.sin_addr); ///////////////////////////////////////////////////////////////////////////// connect(sockfd, (SA *) pservaddr, servlen); while (fgets(sendline, MAXLINE, fp) != NULL) { n = write(sockfd, sendline, strlen(sendline)); //n = sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); //n = sendto(sockfd, sendline, strlen(sendline), 0, &servaddr, sizeof(servaddr)); //n = sendto(sockfd, sendline, strlen(sendline), 0, NULL, 0); if (n == -1) { if (errno == EISCONN) ERR_EXIT("sendto"); else perror("sendto huangcheng"); } //struct sockaddr_in preply_addr; //socklen_t addrlen; n = read(sockfd, recvline, MAXLINE); //n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); //n = recvfrom(sockfd, recvline, MAXLINE, 0, (SA*)&preply_addr, &addrlen); if (n == -1) { if (errno == EINTR) continue; ERR_EXIT("recvfrom"); } //printf("reply from %s \n",inet_ntoa(preply_addr.sin_addr)); recvline[n] = 0; /* null terminate */ fputs(recvline, stdout); } } int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) ERR_EXIT("usage: udpcli <IPaddress>"); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET, argv[1], &servaddr.sin_addr); sockfd = socket(AF_INET, SOCK_DGRAM, 0); dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit(0); }
查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/
運行結果:
huangcheng@ubuntu:~$ ./cli 127.0.0.1 huangcheng recvfrom: Connection refused
虛擬機:
huangcheng@ubuntu:~$ ifconfig eth0 Link encap:以太網 硬件地址 00:0c:29:88:e0:1f inet 地址:192.168.2.103 廣播:192.168.2.255 掩碼:255.255.255.0 inet6 地址: fe80::20c:29ff:fe88:e01f/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 躍點數:1 接收數據包:43472 錯誤:0 丟棄:0 過載:0 幀數:0 發送數據包:19785 錯誤:0 丟棄:0 過載:0 載波:0 碰撞:0 發送隊列長度:1000 接收字節:52561935 (52.5 MB) 發送字節:1925585 (1.9 MB) 中斷:19 基本地址:0x2000 lo Link encap:本地環回 inet 地址:127.0.0.1 掩碼:255.0.0.0 inet6 地址: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:16436 躍點數:1 接收數據包:396 錯誤:0 丟棄:0 過載:0 幀數:0 發送數據包:396 錯誤:0 丟棄:0 過載:0 載波:0 碰撞:0 發送隊列長度:0 接收字節:38912 (38.9 KB) 發送字節:38912 (38.9 KB) huangcheng@ubuntu:~$
驗證UDP套接字,已連接:
write或send:可以
不指定目的地址的sendto:可以
指定目的地址的send:
(1)connect指定的IP地址是:127.0.0.1,sendto指定的IP地址為:127.0.0.1或者192.168.2.103 均正常。
(2)connect指定的IP地址是:127.0.0.1,sendto指定的IP地址為:192.168.4.103 即不為虛擬機的IP地址時,運行結果sendto huangcheng:Invalid argument,即出錯。
注意:在<<UNIX網絡編程——基於UDP協議的網絡程序>>中也有UDP connect的介紹。
作者:csdn博客 ctthuangcheng