socket connec连接超时处理

来源:互联网 发布:java的try catch 编辑:程序博客网 时间:2024/04/29 08:06

最近把win32下的网关服务转到linux平台时遇到connect连接超时问题,经过多方收集资料简单整理下方便以后查找socket <wbr>connec连接超时处理

linux或者win32控制台程序中connect函数默认是阻塞的,成功则返回0,失败返回-1,错误码可以用函数GetLastError获得。如果连接一个打开的服务器一般回立即返回并且成功连接socket连接,如果连接一个存在但是没有开启的服务器会阻塞一段时间(不会太长)然后返回-1. 还有一种就是连接一个网内不存在的服务器,比如192.168.0.256。这时就会阻塞很长的时间大概20+秒。这篇文章最初就是为了解决这个问题而写的。

 

废话不多说了进入正题(读者:你的废话已经够多了)。。。。。。。

 

为了处理connect的连接超时可以在调用socket函数之后使用fcntl函数将sock描述符设置为非阻塞,然后进行连接(connect),会立即返回-1,判断错误码是否等于EINPROGRESS,也就是判断连接是不是正在进行中,如果是那么通过select查询在一段时间内该描述符是否可写来判断连接是成功还是失败。如果错误码不等于EINPROGRESS那么连接失败。下面给出linux和window具体实现方法

WINDOWS:

int main()
{
 SOCKET sock;
 SOCKADDR_IN addr;
 fd_set r;
 unsigned long ul = 1;

 struct timeval timeo = {3, 0};
 //创建socket描述符
 if((sock = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
 {
  printf("socket fail");
  return -1;
 }
    //设置阻塞
 if(ioctlsocket(sock, FIONBIO, (unsigned long*)&ul) == SOCKET_ERROR)
 {
  printf("ioctlsocket fail");
  return -1;
 }
 //初始化addr。。。。。。
 

 
 //发起连接
 if(connect(sock, (const struct sockaddr*)addr, sizeof(addr)) == SOCKET_ERROR)
 {
  FD_ZERO(&r);
  FD_SET(scok, &r);
  if(select(0, &r, 0, 0, &timeo) <= 0)) //需要注意select函数第一个参数在winsock被忽略了,

                                        //在linux必须是sock+1;
  {
   printf("connect fail");
   return -1;
  }
  else
  {
   printf("connect success");
   //to do
  }
 }
}

 

LINUX:(转自中关村 作者:天新网 http://server.zol.com.cn/127/1271166.html)

#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <time.h>

int main(int argc,char*argv[])
{
        int fd, retval;
        structsockaddr_in addr;
        structtimeval timeo={3, 0} //初始化超时时间
        socklen_t len=sizeof(timeo);
        fd_set set;

        fd =socket(AF_INET,SOCK_STREAM, 0);
        if(argc== 4)
                timeo.tv_sec=atoi(argv[3]);
        fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)| O_NONBLOCK)//将fd设置为非阻塞
        addr.sin_family=AF_INET;
        addr.sin_addr.s_addr= inet_addr(argv[1]);
        addr.sin_port=htons(atoi(argv[2]));
        printf("%dn",time(NULL));
        if(connect(fd,(structsockaddr*)&addr,sizeof(addr))== 0){
                printf("connectedn")      
                return 0;
        }
        if(errno!= EINPROGRESS){
                perror("connect");
                return-1;
        }
        FD_ZERO(&set);
        FD_SET(fd,&set);
        retval = select(fd+ 1,NULL,&set,NULL,&timeo);
        if(retval==-1){
                perror("select");
                return-1;
        }elseif(retval== 0){
                fprintf(stderr,"timeoutn");
                printf("%dn",time(NULL));
                return 0;
        }
        printf("connectedn");

        return 0;
}

xiaosuo@gentux perl $ ./a.out 10.16.101.1 90
1180289276
timeout
1180289279
xiaosuo@gentux perl $ ./a.out 10.16.101.1 90 1
1180289281
timeout
1180289282

可以看到程序完全按照预期运行,如果连接成功后需要将该连接设成阻塞的那么再次调用fcntl函数

fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) ^ O_NONBLOCK) 

 

通过select判断是否连接成功并处理完全可以实现connect超时问题,但是这未免有点复杂。读Linux内核源码的时候偶然发现其实connect的超时参数竟然和用SO_SNDTIMO设置发送超时的参数一致:
File: net/ipv4/af_inet.c

 

   559      timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
   560
   561      if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
   562         
   563          if (!timeo || !inet_wait_for_connect(sk, timeo))
   564              goto out;
   565
   566          err = sock_intr_errno(timeo);
   567          if (signal_pending(current))
   568              goto out;
   569      }

 

 

这意味着:在Linux平台下,可以通过在connect之前设置SO_SNDTIMO来达到控制连接超时的目的。简单的写了份测试代码:
#include<stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>

int main(int argc,char*argv[])
{
        int fd;
        structsockaddr_in addr;
        structtimeval timeo={3, 0};
        socklen_t len=sizeof(timeo);

        fd =socket(AF_INET,SOCK_STREAM, 0);
        if(argc== 4)
                timeo.tv_sec=atoi(argv[3]);
        setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO,&timeo, len);
        addr.sin_family=AF_INET;
        addr.sin_addr.s_addr= inet_addr(argv[1]);
        addr.sin_port=htons(atoi(argv[2]));
        if(connect(fd,(structsockaddr*)&addr,sizeof(addr))==-1){
                if(errno== EINPROGRESS){
                        fprintf(stderr,"timeoutn");
                        return-1;
                }
                perror("connect");
                return 0;
        }
        printf("connectedn");

        return 0;
}

执行结果

xiaosuo@gentux perl $ ./a.out 10.16.101.1 90
1180290583

timeout
1180290586

xiaosuo@gentux perl $ ./a.out 10.16.101.1 90 1
1180290590
timeout
1180289592

正如期待的那样。

 

 

例如:

//----------------------------------------TCP-------------------------------
int connection_TCP(int *fd,char* servername,int port) // fd  socket句柄 servername 服务器IP地址 port 端口
{
 int iret=0;
 struct sockaddr_in to;
  memset(&to,0,sizeof(to));
 to.sin_family = AF_INET;
 to.sin_addr.s_addr = inet_addr(servername);
 to.sin_port = htons(port);
 //connect timeout
 struct timeval timeout ;
  timeout.tv_sec = 15;
  timeout.tv_usec = 1000;
  socklen_t len = sizeof(timeout);
  setsockopt(*fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len);
 //--
 iret = connect(*fd,(struct sockaddr *)&to,sizeof(to));
 if(iret < 0)
 {   
  return -1; 
 }
 else
 {
  return 1;
  }   
}