最近項目中,需要編寫一個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超時之旅就結束了,希望這篇文章對各位有幫助。