1. 程式人生 > >轉udp 超時設定(select函式的一種用法)

轉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 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超時之旅就結束了,希望這篇文章對各位有幫助。