udp 超时设置(select函数的一种用法)
最近项目中,需要编写一个udp接收程序。
传统的recvfrom是阻塞进行的,即调用recvfrom之后程序就会阻塞,等待数据包的到来,如果没有数据包,程序就永远等待。
在很多场景中,我们需要设置超时参数,如果该套接口超时之后仍然没有数据包到来,那么就直接返回。
socket编程中这样的超时机制可以使用select和recvfrom这两个函数实现
实现代码如下
1 #define RECV_LOOP_COUNT 100 2 int recv_within_time(int fd, char *buf, size_t buf_n,struct sockaddr* addr,socklen_t *len,unsigned int sec,unsigned usec) 3 { 4 struct timeval tv; 5 fd_set readfds; 6 int i=0; 7 unsigned int n=0; 8 for(i=0;i<RECV_LOOP_COUNT;i++) 9 { 10 FD_ZERO(&readfds); 11 FD_SET(fd,&readfds); 12 tv.tv_sec=sec; 13 tv.tv_usec=usec; 14 select(fd+1,&readfds,NULL,NULL,&tv); 15 if(FD_ISSET(fd,&readfds)) 16 { 17 if((n=recvfrom(fd,buf,buf_n,0,addr,len))>=0) 18 { 19 return n; 20 } 21 } 22 } 23 return -1; 24 }
其中关键代码是第10行到第17行,
第10行将集合readfds清零,
第11行将我们关注的sock加入集合readfds中(置fd对应的bit为1),
第12和13行设置超时参数,
第14行以非阻塞的方式调用select,如果tv时间内有数据则返回并设置readfds中fd对应的bit位为1,如果tv时间内没有数据则返回并设置readfds中对应的bit位为0;
第15行FD_ISSET测试readfds中fd位有没有置1,如果置一则返回成功,否则失败
这里要强调两点:
第一:如果tv时间内没有数据到来,你还想继续等待N次,那么一定要注意重新设置readfds,因为它已经被select破坏了,如果不重新设置的话,你的select语句会返回-1,strerr时会打印出参数设置出错,主要是由于readfds中全部为零,select不知道该去监视哪个sock;
第二:重复等待时不光要注意重新设置readfds,同时还要注意重新设置一下tv的值,因为select同时也破坏了tv的值(select在返回时会改变tv,改变的公式是tv=tv-等待的时间,所以如果tv时间内没有数据到达的话,select返回时tv会变成0)。
好的,到此你已经掌握了使用select和recvfrom 进行超时处理的全部知识了,赶紧打开编辑器,试试吧。
以下是接收端的一个完整的程序,存为test_server.c,然后将 my_addr.sin_addr.s_addr=inet_addr("192.168.127.130");这行中的地址改为你自己的ip地址。
然后使用gcc -o test_server test_server.c
编译得到可执行程序test_server
#include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <netdb.h> #include <stdarg.h> #include <string.h> #define RECV_LOOP_COUNT 100 int main() { unsigned short expect_sn=0; int sockfd; struct sockaddr_in my_addr; //struct sockaddr_in their_addr; int addr_len; if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1) { printf("error in socket"); return -2; } my_addr.sin_family=AF_INET; my_addr.sin_port=htons(9450); my_addr.sin_addr.s_addr=inet_addr("192.168.127.130"); memset(my_addr.sin_zero,0,8); addr_len = sizeof(struct sockaddr); int re_flag=1; int re_len=sizeof(int); setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&re_flag,re_len); if(bind(sockfd,(const struct sockaddr *)&my_addr,addr_len)==-1) { printf("error in binding"); return -3; } struct timeval tv; fd_set readfds; int i=0; unsigned int n=0; char buf[1024]; struct sockaddr addr; socklen_t len; while(1) { FD_ZERO(&readfds); FD_SET(sockfd,&readfds); tv.tv_sec=3; tv.tv_usec=10; select(sockfd+1,&readfds,NULL,NULL,&tv); if(FD_ISSET(sockfd,&readfds)) { if((n=recvfrom(sockfd,buf,1024,0,&addr,&len))>=0) { printf("in time ,left time %d s ,%d usec\n",tv.tv_sec,tv.tv_usec); } } else printf("timeout ,left time %d s ,%d usec\n",tv.tv_sec,tv.tv_usec); } return 0; }
下面是一个发送端的测试程序:
保存为,test_client.c
然后修改 my_addr.sin_addr.s_addr=inet_addr("192.168.127.130");中的ip地址为你自己的ip地址,注意一定要和test_server.c中的ip地址一样。
然后使用gcc -o test_client test_client.c
编译成test_client可执行程序
#include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <netdb.h> #include <stdarg.h> #include <string.h> #define RECV_LOOP_COUNT 100 int main() { unsigned short expect_sn=0; int sockfd; struct sockaddr_in my_addr; //struct sockaddr_in their_addr; int addr_len; if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1) { printf("error in socket"); return -2; } my_addr.sin_family=AF_INET; my_addr.sin_port=htons(9449); my_addr.sin_addr.s_addr=inet_addr("192.168.127.130"); memset(my_addr.sin_zero,0,8); addr_len = sizeof(struct sockaddr); struct sockaddr_in send_addr; send_addr.sin_family=AF_INET; send_addr.sin_addr.s_addr=inet_addr("192.168.127.130"); send_addr.sin_port=htons(9450); memset(my_addr.sin_zero,0,8); int sens_addr_len=sizeof(struct sockaddr_in); char sends[]="hello"; char input[100]; while(1) { scanf("%s",input); sendto(sockfd,sends,6,0,(struct sockaddr*)&send_addr,sens_addr_len); } }
接着就是测试了
先运行服务端:
./test_server
然后运行客户端
./test_client
不在客户端输入数据时,服务端会不断打印超时信息,如果在服务端输入数据,然后回车之后服务端就会接到客户端的数据,就会打印非超时信息。
至此,我们的udp超时之旅就结束了,希望这篇文章对各位有帮助。
posted on 2012-04-28 15:21 liang_87 阅读(24951) 评论(3) 编辑 收藏 举报