UNP学习笔记(第八章 基本UDP套接字编程)

时间:2021-04-03 10:19:36

UDP应用程序客户不与服务器建立连接,而是只管使用sendto函数给服务器发送数据报,其中必须指定目的地的地址作为参数。

下图给出典型的UDP客户/服务器程序的函数调用。

UNP学习笔记(第八章 基本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回射服务器程序

UNP学习笔记(第八章 基本UDP套接字编程)

main函数

UNP学习笔记(第八章 基本UDP套接字编程)UNP学习笔记(第八章 基本UDP套接字编程)
 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 }
View Code

dg_echo函数

UNP学习笔记(第八章 基本UDP套接字编程)UNP学习笔记(第八章 基本UDP套接字编程)
 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 }
View Code

 

 

 

UDP回射客户程序

main函数

UNP学习笔记(第八章 基本UDP套接字编程)UNP学习笔记(第八章 基本UDP套接字编程)
 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 }
View Code

dg_cli函数

UNP学习笔记(第八章 基本UDP套接字编程)UNP学习笔记(第八章 基本UDP套接字编程)
 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 }
View Code

 

 

 

验证接收到的相应

知道客户临时端口的任何进程都可往客户发送数据报,而且这些数据报会与正常的服务器应答混杂。

我们应该修改recvfrom调用以返回数据报的发送者的IP地址和端口号,保留来自数据报所发往服务器的应答,而忽略任何其他数据报。

下面是验证返回套接字地址的dg_cli函数版本

UNP学习笔记(第八章 基本UDP套接字编程)UNP学习笔记(第八章 基本UDP套接字编程)
 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 }
View Code

 

 

 

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的新函数

UNP学习笔记(第八章 基本UDP套接字编程)UNP学习笔记(第八章 基本UDP套接字编程)
 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 }
View Code

 

 

 

使用select的TCP和UDP回射服务器程序

UNP学习笔记(第八章 基本UDP套接字编程)UNP学习笔记(第八章 基本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 */
View Code

sig_chld信号处理函数

UNP学习笔记(第八章 基本UDP套接字编程)UNP学习笔记(第八章 基本UDP套接字编程)
 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 }
View Code