轉udp 超時設定(select函式的一種用法)
from
最近專案中,需要編寫一個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 intsec,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超時之旅就結束了,希望這篇文章對各位有幫助。