本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie
最初代码:
#include "unp.h" int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) err_quit("usage: udpcli <IPaddress>"); //1.指明服务器的 IP 地址和端口 bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); //2.创建一个 UDP 套接字 sockfd = Socket(AF_INET, SOCK_DGRAM, 0); //3.把 UDP 套接字和服务器套接字地址结构传递给 dg_cli 函数 dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit(0); } #include "unp.h" void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; //1.fgets 从标准输入读入一个文本行 while (Fgets(sendline, MAXLINE, fp) != NULL) { //2.用 sendto 从sockfd 套接字将文本行发送给pservaddr指明的服务器 Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); //3.用 recvfrom 从sockfd 套接字读取服务器的回射 n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); //4.用 fputs 将回射文本显示到标准输出 recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } }
问题: recvfrom 指定的第五和第六个参数是空指,这样任何进程不论是在与本客户进程相同的主机上
还是在不同的主机上,都可以向本客户的 IP 地址和端口发送数据报
改善1:验证接收到的响应,保留来自数据报所发往服务器的应答,而忽略任何其他数据报。
/** * UDP 验证服务器地址 * **/ #include "unp.h" void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; socklen_t len; struct sockaddr *preply_addr; //1.分配另一个套接字地址结构 preply_addr = Malloc(servlen); while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); len = servlen; n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len); //2.比较返回的地址 if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0) { printf("reply from %s (ignored)\n", Sock_ntop(preply_addr, len)); continue; } recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } }
改善1问题:如果没有接收到服务器返回的应答就一直阻塞在 recvfrom
改善2.1:使用 SIGALRM 为 recvfrom 设置超时
#include "unp.h" static void sig_alrm(int); void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; //1.设置超时信号 SIGALRM 的处理函数 Signal(SIGALRM, sig_alrm); while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); //2.每次调用 recvfrom 前通过调用 alarm 设置一个 5 秒钟的超时 alarm(5); if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0) { //3.如果 errno 为 EINTR,即 recvfrom 因超时而中断了 if (errno == EINTR) fprintf(stderr, "socket timeout\n"); else err_sys("recvfrom error"); } //4.如果读到一行来自服务器的文本,那就关掉报警时钟并输出服务器的应答 else { alarm(0); recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } } } //SIGALRM 信号处理函数 static void sig_alrm(int signo) { return; /* just interrupt the recvfrom() */ }
改善2.2:使用 select 为 recvfrom 设置超时
/* include readable_timeo */ #include "unp.h" //等待一个描述符最多在指定的秒数内变为可读 int readable_timeo(int fd, int sec) { fd_set rset; struct timeval tv; //1.设置描述符集 FD_ZERO(&rset); FD_SET(fd, &rset); //2.设置等待的秒数 tv.tv_sec = sec; tv.tv_usec = 0; //3.阻塞在select 上 return(select(fd+1, &rset, NULL, NULL, &tv)); /* 4> 0 if descriptor is readable */ } /* end readable_timeo */ int Readable_timeo(int fd, int sec) { int n; if ( (n = readable_timeo(fd, sec)) < 0) err_sys("readable_timeo error"); return(n); } #include "unp.h" void dg_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); //1.直到 readable_timeo 告诉所关注的描述符已变为可读后我们才调用 recvfrom ,这一点保证 recvfrom 不会阻塞 if (Readable_timeo(sockfd, 5) == 0) { fprintf(stderr, "socket timeout\n"); } else { //2.接收数据并输出到 stdout n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } } }
改善2.3:使用 SO_RCVTIMEO 套接字选项为 recvfrom 设置超时
#include "unp.h" void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; struct timeval tv; //1.在套接字上设置超时 tv.tv_sec = 5; tv.tv_usec = 0; Setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); 2. 接收数据,如果超时的话,recvfrom 会返回 EWOULDBLOCK 错误 n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); if (n < 0) { if (errno == EWOULDBLOCK) { fprintf(stderr, "socket timeout\n"); continue; } else err_sys("recvfrom error"); } recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } }
时间: 2024-10-23 23:27:37