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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM