1.概述
使用UDP編寫的一些常用應用程式有:DNS(網域名稱系統)、NFS(網路檔案系統)和SNMP(簡易網路管理通訊協定)
給出典型的UDP客戶/伺服器程式的函數調用:
2.recvfrom和sendto函數
兩個函數類似於標準的read和write函數,不過需要三個額外的參數
#include <sys/socket.h> 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
前三個參數:sockfd, buff, nbytes等同於read和write的前三個參數:描述字,指向讀入或者寫出緩衝區的指標,讀寫位元組數。
函數sendto的參數to是一個含有資料將發往的協議地址(例如IP地址和連接埠號碼)的套介面地址結構,它的大小由addrlen來指定。函數recvfrom用資料報寄件者的協議地址裝填由from所指的套介面地址結構,儲存在此套介面地址結構中的位元組數也以addrlen所指的整數返回給調用者。注意,sendto的最後一個參數是一個整數值,而recvfrom的最後一個參數值是一個指向整數值的指標(值-結果參數)。
recvfrom的最後兩個參數類似於accept的最後兩個參數:返回時套介面地址結構的內容告訴我們是誰發送了資料報(UDP情況下)或是誰發起了串連(TCP情況下)。sendto的最後兩個參數類似於connect的最後兩個參數:我們用資料報將發往(UDP情況下)或與之建立串連(TCP情況下)的協議地址來裝填套介面地址結構。
寫一個長度為0的資料報是可行的,這也意味著對於資料報協議,recvfrom返回0值也是可行的;它不表示對方已經關閉了串連,這與TCP套介面上的read返回0的情況不同。由於UDP是不需連線的,這就沒有諸如關閉UDP串連之類的事情。
PS:預設情況recvfrom函數沒有接收到對方資料時候是阻塞的
3.UDP的connect函數
我們可以給UDP套介面調用connect,但這樣做的結果卻與TCP串連毫不相同:沒有三路握手過程。核心只是記錄對方的IP地址和連接埠號碼,它們包含在傳遞給connect的套介面地址結構中,並立即返回給調用進程。
對於已串連UDP套介面,與預設的未串連套介面相比,發生了三個變化:
1).我們再也不能給輸出操作指定宿IP和連接埠號碼,也就是說我們不使用sendto,而改用write或send,寫到已串連UDP套介面上的任何內容都自動發送到由connect指定的協議地址(例如IP地址和連接埠號碼)
2).我們不必使用recvfrom以獲悉資料報的寄件者,而改用read,recv或recvmsg,在一個已串連UDP套介面上由核心為輸入操作返回的資料報僅僅是那些來自connect所指定協議地址的資料報。目的地為這個已串連UDP套介面的本地協議地址,發源地卻不是該套介面早先connect到的協議地址的資料報,不會投遞到該套介面。這樣就限制了一個已串連UDP套介面而且僅能與一個對端交換資料報。
3).由已串連的UDP套介面引發的非同步錯誤返回給他們所在的進程,而未串連UDP通訊端不接受任何非同步錯誤。
擁有一個已串連的UDP通訊端的進程出於下列目的再次調用connect
第一個目的(即給一個已串連UDP套介面指定新的對端)不同於TCP套介面中connect的使用:對於TCP套介面,connect只能調用一次。
為了斷開一個已connect的UDP套介面串連,我們再次調用connect時把套介面地址結構的地址簇成員(sin_family)設定為AF_UNSPEC。 這麼做可能返回一個EAFNOSUPPORT錯誤,不過沒有關係。 使得套介面中斷連線的是在已串連UDP套介面上調用connect的進程。
4.UDP回射服務程式
1).main函數
#include "unp.h"intmain(int argc, char **argv){ int sockfd; struct sockaddr_in servaddr, cliaddr; sockfd = Socket(AF_INET, SOCK_DGRAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(sockfd, (SA *) &servaddr, sizeof(servaddr)); dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));}
2).dg_echo函數
#include "unp.h"voiddg_echo(int sockfd, SA *pcliaddr, socklen_t clilen){ int n; socklen_t len; char mesg[MAXLINE]; for ( ; ; ) { len = clilen; n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len); Sendto(sockfd, mesg, n, 0, pcliaddr, len); }}
5.UDP回射客戶程式
1).main函數
#include "unp.h"intmain(int argc, char **argv){ int sockfd; struct sockaddr_in servaddr; if(argc != 2) err_quit("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);}
2).dg_cli函數(非connect)
#include "unp.h"voiddg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen){ int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); }}
3).dg_cli函數(connect)
#include "unp.h"voiddg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen){ int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; Connect(sockfd, (SA *) pservaddr, servlen); while (Fgets(sendline, MAXLINE, fp) != NULL) { Write(sockfd, sendline, strlen(sendline)); n = Read(sockfd, recvline, MAXLINE); recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); }}
6.使用select函數的TCP和UDP回射伺服器程式
#include "unp.h"intmain(int argc, char **argv){ int listenfd, connfd, udpfd, nready, maxfdp1; char mesg[MAXLINE]; pid_t childpid; fd_set rset; ssize_t n; socklen_t len; const int on = 1; struct sockaddr_in cliaddr, servaddr; void sig_chld(int); /* create listening TCP socket */ listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); /* create UDP socket */ udpfd = Socket(AF_INET, SOCK_DGRAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(udpfd, (SA *) &servaddr, sizeof(servaddr)); Signal(SIGCHLD, sig_chld); /* must call waitpid() */ FD_ZERO(&rset); maxfdp1 = max(listenfd, udpfd) + 1; for ( ; ; ) { FD_SET(listenfd, &rset); FD_SET(udpfd, &rset); if ( (nready = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) { if (errno == EINTR) continue; /* back to for() */ else err_sys("select error"); } if (FD_ISSET(listenfd, &rset)) { len = sizeof(cliaddr); connfd = Accept(listenfd, (SA *) &cliaddr, &len); if ( (childpid = Fork()) == 0) { /* child process */ Close(listenfd); /* close listening socket */ str_echo(connfd); /* process the request */ exit(0); } Close(connfd); /* parent closes connected socket */ } if (FD_ISSET(udpfd, &rset)) { len = sizeof(cliaddr); n = Recvfrom(udpfd, mesg, MAXLINE, 0, (SA *) &cliaddr, &len); Sendto(udpfd, mesg, n, 0, (SA *) &cliaddr, len); } }}