控制connect超时时间(linux版本和Windows版本)

客户端在连接服务器时,可能会出现问题,导致三次握手无法完成,持续重试,表现在客户端程序的行为就是卡在connect调用上无法返回,这样的客户端是非常不友好的。
大致的原理就是设置socket为非阻塞,这是connect会马上返回,之后通过select控制超时,并通过FD_ISSET()检测,再通过getsockopt()检测SO_ERROR,最后再把socket设置为阻塞模式。之后就可以愉快地通讯了。
linux正确程序:

    #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>
    #include   <arpa/inet.h> 
    int   main(int   argc,   char   *argv[])
    {
        int fd, retval;
        struct sockaddr_in addr;
        struct   timeval   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]);
        int   savefl   =   fcntl(fd,F_GETFL);
        fcntl(fd,   F_SETFL,   savefl   |   O_NONBLOCK);
        addr.sin_family   =   AF_INET;
        addr.sin_addr.s_addr   =   inet_addr(argv[1]);
        addr.sin_port   =   htons(atoi(argv[2]));
        printf( "%d\n ",   time(NULL));
        if  (connect(fd,   (struct   sockaddr*)&addr,   sizeof(addr))==0)
        {
            close(fd);
            printf( "connected..1\n ");
            return   0;
        }    
        if   (errno   !=   EINPROGRESS){
        close(fd);
        perror( "connect..2 ");
        return   -1;
        }
        FD_ZERO(&set);
        FD_SET(fd,   &set);
        retval = select(fd + 1,   NULL,   &set,   NULL,   &timeo);
        if   (retval   ==   -1)
        {
            close(fd);
            perror( "select ");
            return   -1;
        }
        else   if(retval   ==   0)
        {
            close(fd);
            fprintf(stderr,   "timeout\n ");
            printf( "%d\n ",   time(NULL));
            return   0;
        }    
        if(FD_ISSET   (fd,&set))
        {
        int   error   =   0; 
        socklen_t   len   =   sizeof   (error);
        if(getsockopt(fd,   SOL_SOCKET,   SO_ERROR,   &error,   &len)   <   0)
        {
            printf   ( "getsockopt  fail,connected  fail\n ");
            return   -1;    
        }
        if   (error   ==   ETIMEDOUT)
        {
            printf   ( "connected   timeout\n ");
        }    
        if(error   ==   ECONNREFUSED)
        {
            printf( "No   one   listening   on   the   remote   address.\n ");
            return   -1;
        }
        }
        printf   ( "connected   ..   3\n ");
        fcntl(fd,   F_SETFL,   savefl);
        close   (fd);
        return   0;
    }

注意:

  • 一定要用getsockopt检测SO_ERROR,这个检测在man connect中被指定了要检测
  • EINPROGRESS The socket is non-blocking and the connection cannot be
    completed immediately. It is possible to select(2) or poll(2) for
    completion by selecting the socket for writing. After select(2)
    indicates writability, use getsockopt(2) to read the SO_ERROR option
    at level SOL_SOCKET to determine whether connect() completed
    successfully (SO_ERROR is zero) or unsuccessfully (SO_ERROR is one of
    the usual error codes listed here, explaining the reason for the
    failure).

windows下的程序类似,只是换了相应的函数。
windows代码如下:

INT InitTcpSocket(LPCTSTR ip, INT port)
{
    SOCKET sock;
    struct sockaddr_in     serveraddr;
    struct timeval timeo = {3, 0}; 

    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family  = AF_INET;
    serveraddr.sin_port    = htons(port);
    serveraddr.sin_addr.s_addr = inet_addr(ip);

    if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET )
        return INVALID_SOCKET;  
    u_long arg = 1;
    if(ioctlsocket(sock, FIONBIO, &arg) == SOCKET_ERROR)
    {
        LogUtil::Logger(LOG_DEBUG,"ioctlsocket 设置失败");
        return INVALID_SOCKET;
    }
    CString esg("测试网络");
    LogUtil::Logger(LOG_DEBUG,esg);
    int i=0;
    if ((i=connect(sock,(struct sockaddr *)&serveraddr, sizeof(serveraddr)))== 0)
    {
        LogUtil::Logger(LOG_DEBUG,"connect successful!");
        arg = 0;
        ioctlsocket(sock, FIONBIO, &arg);       
        return sock;       
    }

    int nErrCode = WSAGetLastError();
    esg.Format("%s%d,%s%d","connect return:",i,"nError:",nErrCode);
    LogUtil::Logger(LOG_DEBUG,esg);
    if   (nErrCode  != WSAEWOULDBLOCK)
    {
        closesocket(sock);
        esg.Format("%s%d","connect failed!",errno);
        LogUtil::Logger(LOG_DEBUG,esg);
        return   INVALID_SOCKET;
    }

    FD_ZERO(&r);
    FD_SET(sock, &r);
    int rev=select(sock+1, 0, &r, 0, &timeo); //需要注意select函数第一个参数在winsock被忽略了,在linux必须是sock+1;
    if( rev == 0)
    {
        LogUtil::Logger(LOG_DEBUG,"select timeout!");
        closesocket(sock);
        return INVALID_SOCKET;
    }
    if (rev ==-1)
    {
        LogUtil::Logger(LOG_DEBUG,"select error!");
        closesocket(sock);
        return INVALID_SOCKET;
    }
    if(FD_ISSET(sock,&r))
    {
        int   error   =   0; 
        int   len   =   sizeof   (error);
        LogUtil::Logger(LOG_DEBUG,"FD_ISSET");
        if(getsockopt(sock,   SOL_SOCKET,   SO_ERROR,  (char *) &error,   &len)   <   0)
        {
            LogUtil::Logger(LOG_DEBUG,"getsockopt  fail,connected  fail!");

            return   INVALID_SOCKET;

        }


        if   (error   ==   WSAETIMEDOUT)
        {
            LogUtil::Logger(LOG_DEBUG,"connected   timeout!");
            return INVALID_SOCKET;
        }

        if(error   ==   WSAECONNREFUSED)
        {
            LogUtil::Logger(LOG_DEBUG,"No   one   listening   on   the   remote   address!");
            return   INVALID_SOCKET;
        }
    }
    arg = 0;
    ioctlsocket(sock, FIONBIO, &arg);
    return sock;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值