1. 程式人生 > >Linux 下socket超時(connect超時/recv超時)

Linux 下socket超時(connect超時/recv超時)

19733人閱讀 評論(1) 收藏 舉報

 connect超時:

目前各平臺通用的設定socket connect超時的辦法是通過select(),具體方法如下:

1.建立socket;

2.將該socket設定為非阻塞模式;

3.呼叫connect();

4.使用select()檢查該socket描述符是否可寫;

5.根據select()返回的結果判斷connect()結果;

6.將socket設回阻塞模式。

下面給出的是我寫的client程式(已經編譯通過):

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <unistd.h>
#include <asm/ioctls.h>
 
#define MAXDATASIZE 4926

int detect_imap(const char *server ,char *protocol,unsigned short port)
{
    int sockfd,numbytes;
    char buf[MAXDATASIZE];
    struct hostent *he;
    struct sockaddr_in their_addr;

    if ((he = gethostbyname(server)) == NULL)
    {
        herror("gethostbyname");
        return 0; 
    } 
   
    if ((sockfd=socket(AF_INET,SOCK_STREAM,0)) == -1)
    {
        perror("socket");
        return 0;
    }
   
    unsigned long ul=1;
    int rm=ioctl(sockfd,FIONBIO,&ul);
    if(rm==-1)
    {
      close(sockfd);
      return 0;  
    }
   
    their_addr.sin_family = AF_INET;
    their_addr.sin_port = htons(port);
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    bzero(&(their_addr.sin_zero), 8);
  
    if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == 0)
    {
     printf("connected/n");
    }
    if(errno!=EINPROGRESS)
    {
     perror("connect");
      printf("cannot connect:%s/n",server);
      return 0;
    }    
 
    struct timeval timeout;
    fd_set r;         
    FD_ZERO(&r);
    FD_SET(sockfd,&r);
    timeout.tv_sec=0;   
    timeout.tv_usec=100;
    int retval = select(sockfd+1,NULL,&r,NULL,&timeout);
    if(retval==-1)
    {
      perror("select");
      return 0;
    }
    else if(retval == 0)
    {
      fprintf(stderr,"timeout/n");
      return 0;
    }
    printf("%sconnected/n",server);

    unsigned long ul1=0;
    rm=ioctl(sockfd,FIONBIO,(unsigned long*)&ul1);
    if(rm==-1)
    {
      close(sockfd);
      return 0;     
    }
  
    if ((numbytes=recv(sockfd,buf,MAXDATASIZE,0)) == -1)
    {
      perror("recv");
      return 0; 
    }
   
    buf[numbytes] = '/0';
    if (0 == strncmp(buf,"* OK",4))
    {
      printf("Received: %s",buf);
      close(sockfd);
      return 1;
    }
    else
    {
      printf("Error protocol!");
      close(sockfd);
      return 0; 
    }
}

int
main(int argc,char *argv[])
{
   int i;
   if (argc!=2)
    {
       fprintf(stderr,"usage:client hostname/n");
       exit(1);
    }

   i = detect_imap(argv[1] ,argv[2],143);
   printf ("%d",i);
}

以上程式碼工作的很好,並且也可以通過getsockopt()獲得連線發生錯誤的確切資訊,但這總方法難免覺得有些複雜,因為要涉及到阻塞狀態的解除和回置。

這裡有個簡單的操作方法,同樣可以設定連線超時:即通過SO_SNDTIMO套節字引數讓超時操作跳過select。

原因是:Linux核心原始碼中connect的超時引數和SO_SNDTIMO操作的引數一致。

因此,在linux平臺下,可以通過connect之前設定SO_SNDTIMO來達到控制連線超時的目的。

部分修正程式碼如下:

    struct timeval timeo;
    socklen_t len = sizeof(timeo);
    timeo.tv_sec = overtime;


     if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeo, len) == -1)
    {
        strcpy(reason,strerror(errno));
        perror("setsockopt");
       return 0;
    }
   
    their_addr.sin_family = AF_INET;
    their_addr.sin_port = htons(serverStruct->port);
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    bzero(&(their_addr.sin_zero), 8);
 
    if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
    { 
      if (errno == EINPROGRESS)
       {
          strcpy(reason,"timeout");
          return 0;
        }  
       strcpy(reason,strerror(errno));
       perror("connect");
        return 0;
    } 

編譯執行:gcc client_select.c -o client_select

                    ./client_select imap.21cn.com

ps:列子是對imap協議進行分析時的程式碼。

recv超時:

如果要設定接收超時,可以用setsockopt()和send超時一樣,只需將如下程式碼新增在connect之後就可以了。

 timeout.tv_sec=0;   
    timeout.tv_usec=500;
    int result = setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout.tv_sec,sizeof(struct timeval));
    if (result < 0)
    {
      perror("setsockopt"); 
    }