UDP应用程序客户不与服务器建立连接,而是只管使用sendto函数给服务器发送数据报,其中必须指定目的地的地址作为参数。
下图给出典型的UDP客户/服务器程序的函数调用。
recvfrom和sendto函数
这两个函数类似于标准的read和write函数,不过需要3个额外的参数
#include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,const struct sockaddr *to, socklen_t addrlen); //均返回:若成功则为读或写的字节数,若出错则为-1
UDP回射服务器程序
main函数
1 #include "unp.h" 2 3 int 4 main(int argc, char **argv) 5 { 6 int sockfd; 7 struct sockaddr_in servaddr, cliaddr; 8 9 sockfd = Socket(AF_INET, SOCK_DGRAM, 0); 10 11 bzero(&servaddr, sizeof(servaddr)); 12 servaddr.sin_family = AF_INET; 13 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 14 servaddr.sin_port = htons(SERV_PORT); 15 16 Bind(sockfd, (SA *) &servaddr, sizeof(servaddr)); 17 18 dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr)); 19 }
dg_echo函数
1 #include "unp.h" 2 3 void 4 dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen) 5 { 6 int n; 7 socklen_t len; 8 char mesg[MAXLINE]; 9 10 for ( ; ; ) { 11 len = clilen; 12 n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len); 13 14 Sendto(sockfd, mesg, n, 0, pcliaddr, len); 15 } 16 }
UDP回射客户程序
main函数
1 #include "unp.h" 2 3 int 4 main(int argc, char **argv) 5 { 6 int sockfd; 7 struct sockaddr_in servaddr; 8 9 if (argc != 2) 10 err_quit("usage: udpcli <IPaddress>"); 11 12 bzero(&servaddr, sizeof(servaddr)); 13 servaddr.sin_family = AF_INET; 14 servaddr.sin_port = htons(SERV_PORT); 15 Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); 16 17 sockfd = Socket(AF_INET, SOCK_DGRAM, 0); 18 19 dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); 20 21 exit(0); 22 }
dg_cli函数
1 #include "unp.h" 2 3 void 4 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) 5 { 6 int n; 7 char sendline[MAXLINE], recvline[MAXLINE + 1]; 8 9 while (Fgets(sendline, MAXLINE, fp) != NULL) { 10 11 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); 12 13 n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); 14 15 recvline[n] = 0; /* null terminate */ 16 Fputs(recvline, stdout); 17 } 18 }
验证接收到的相应
知道客户临时端口的任何进程都可往客户发送数据报,而且这些数据报会与正常的服务器应答混杂。
我们应该修改recvfrom调用以返回数据报的发送者的IP地址和端口号,保留来自数据报所发往服务器的应答,而忽略任何其他数据报。
下面是验证返回套接字地址的dg_cli函数版本
1 #include "unp.h" 2 3 void 4 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) 5 { 6 int n; 7 char sendline[MAXLINE], recvline[MAXLINE + 1]; 8 socklen_t len; 9 struct sockaddr *preply_addr; 10 11 preply_addr = Malloc(servlen); 12 13 while (Fgets(sendline, MAXLINE, fp) != NULL) { 14 15 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); 16 17 len = servlen; 18 n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len); 19 if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0) { 20 printf("reply from %s (ignored)\n", 21 Sock_ntop(preply_addr, len)); 22 continue; 23 } 24 25 recvline[n] = 0; /* null terminate */ 26 Fputs(recvline, stdout); 27 } 28 }
UDP的connect函数
UDP套接字可以调用connect,但是跟TCP套接字不一样,它不会有三次握手过程。
对于已连接的套接字,与默认的未连接套接字相比,发生了三个变化:
1.我们再也不能给输出操作指定目的IP地址和端口号。也就是说,我们不使用sendto而改用write或send。
2.我们并不必使用recvfrom以获悉数据报的发送者,而改用read、recv或recvmsg。
3.由已连接UDP套接字引发的异步错误会返回给它们所在的进程,而未连接UDP套接字不接受任何异步错误。
一个已连接的UDP套接字可以再次调用connect以用于:
1.指定新的IP地址和端口号
2.断开套接字
dg_cli函数(修订版)
把上面dg_cli函数重写成调用connect的新函数
1 #include "unp.h" 2 3 void 4 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) 5 { 6 int n; 7 char sendline[MAXLINE], recvline[MAXLINE + 1]; 8 9 Connect(sockfd, (SA *) pservaddr, servlen); 10 11 while (Fgets(sendline, MAXLINE, fp) != NULL) { 12 13 Write(sockfd, sendline, strlen(sendline)); 14 15 n = Read(sockfd, recvline, MAXLINE); 16 17 recvline[n] = 0; /* null terminate */ 18 Fputs(recvline, stdout); 19 } 20 }
使用select的TCP和UDP回射服务器程序
1 /* include udpservselect01 */ 2 #include "unp.h" 3 4 int 5 main(int argc, char **argv) 6 { 7 int listenfd, connfd, udpfd, nready, maxfdp1; 8 char mesg[MAXLINE]; 9 pid_t childpid; 10 fd_set rset; 11 ssize_t n; 12 socklen_t len; 13 const int on = 1; 14 struct sockaddr_in cliaddr, servaddr; 15 void sig_chld(int); 16 17 /* 4create listening TCP socket */ 18 listenfd = Socket(AF_INET, SOCK_STREAM, 0); 19 20 bzero(&servaddr, sizeof(servaddr)); 21 servaddr.sin_family = AF_INET; 22 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 23 servaddr.sin_port = htons(SERV_PORT); 24 25 Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 26 Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); 27 28 Listen(listenfd, LISTENQ); 29 30 /* 4create UDP socket */ 31 udpfd = Socket(AF_INET, SOCK_DGRAM, 0); 32 33 bzero(&servaddr, sizeof(servaddr)); 34 servaddr.sin_family = AF_INET; 35 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 36 servaddr.sin_port = htons(SERV_PORT); 37 38 Bind(udpfd, (SA *) &servaddr, sizeof(servaddr)); 39 /* end udpservselect01 */ 40 41 /* include udpservselect02 */ 42 Signal(SIGCHLD, sig_chld); /* must call waitpid() */ 43 44 FD_ZERO(&rset); 45 maxfdp1 = max(listenfd, udpfd) + 1; 46 for ( ; ; ) { 47 FD_SET(listenfd, &rset); 48 FD_SET(udpfd, &rset); 49 if ( (nready = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) { 50 if (errno == EINTR) 51 continue; /* back to for() */ 52 else 53 err_sys("select error"); 54 } 55 56 if (FD_ISSET(listenfd, &rset)) { 57 len = sizeof(cliaddr); 58 connfd = Accept(listenfd, (SA *) &cliaddr, &len); 59 60 if ( (childpid = Fork()) == 0) { /* child process */ 61 Close(listenfd); /* close listening socket */ 62 str_echo(connfd); /* process the request */ 63 exit(0); 64 } 65 Close(connfd); /* parent closes connected socket */ 66 } 67 68 if (FD_ISSET(udpfd, &rset)) { 69 len = sizeof(cliaddr); 70 n = Recvfrom(udpfd, mesg, MAXLINE, 0, (SA *) &cliaddr, &len); 71 72 Sendto(udpfd, mesg, n, 0, (SA *) &cliaddr, len); 73 } 74 } 75 } 76 /* end udpservselect02 */
sig_chld信号处理函数
1 Signal(SIGCHLD,sig_chld); 2 3 4 #include "unp.h" 5 6 void 7 sig_chld(int signo) 8 { 9 pid_t pid; 10 int stat; 11 12 pid = wait(&stat); 13 printf("child %d terminated\n", pid); 14 return; 15 }