如何解决Connect超时导致的阻塞问题

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/clirus/article/details/50577352
        这几天发现一个现象,客户端正常连接服务器connect显然不会出现问题。

        在异常情况下,如果是服务器出现异常,connect能够立即返回失败;但是当客户端出现异常的情况下,分为两种情况:

        一种是不插网线,客户端没有获得ip地址,在这种情况下,connect也可以立即返回错误;

        二是但是当客户端插上网线,但是连接网络失败,也就是说能够获取到ip地址,但是和服务器是ping不通的。这种情况下connect就可能会发生阻塞,因为按照《UNIX 网络编程》中讲解,connect的在进行三次握手,如果失败情况,需要等待75s的超市时间的。

        我们主要讨论第二种情况如何解决,可以让connect快速返回结果,不至于阻塞等待超长的时间。

        如下是我的代码

/******************************
* Time out for connect()
******************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
 
 
#define TIME_OUT_TIME 20 //connect超时时间20秒
 
bool setBlockOpt(int m_fd,bool blocked)
{
            
#ifndef WIN32
    int  flags;
    flags = fcntl(m_fd, F_GETFL, 0);
    if(flags < 0)
    {
        return false;
    }
    if(blocked)
    {    
        printf("Set BLOCK !!!\n");
        flags &= ~O_NONBLOCK;
    }
    else
    {
        printf("Set NONBLOCK !!!\n");
        flags |= O_NONBLOCK;
    }
    if(fcntl(m_fd, F_SETFL, flags) < 0)
    {
        return false;
    }
    
#else
    u_long ulValue;
    if(blocked)
    {
        ulValue = 1;
    }
    else
    {
        ulValue = 0;
    }    
    int n = ioctlsocket(m_fd, FIONBIO, &ulValue);
    if (n != 0)
    {
        return false;
    }
#endif
    return true;
}
 
int connectWithTimeout(int m_fd,int timeout)
{
    int selectFlag = -1;
    int error=-1, len;
    len = sizeof(int);
    bool ret = false;
    int connectFlag = -1;
    
    const char* m_ip = "115.239.210.27";
    int m_port = 80;
    
    if("" == m_ip || 0 > m_port)
    {
        return -1;
    }
    
    if(m_fd < 0 && "" != m_ip && m_port >=0)
    {
        m_fd =  socket(AF_INET, SOCK_STREAM, 0);
        if(m_fd < 0)
        {
            return -1;
        }
    }
    
    if(m_fd < 0)
    {
        return -1;
    }
 
    struct sockaddr_in servAddr;
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_port = htons((unsigned short)m_port);
    servAddr.sin_addr.s_addr = inet_addr(m_ip);
 
    setBlockOpt(m_fd,false);    //设置为非阻塞模式
    if( (connectFlag= connect(m_fd, (struct sockaddr *) &servAddr, sizeof(servAddr)) < 0))
    {
        if(errno != EINPROGRESS)
        {
            goto done;
        }
    }
    else
    {
        ret = true;
        goto done;
    }
    timeval tm;
    tm.tv_sec = timeout/1000;
    tm.tv_usec = timeout%1000;
    fd_set rest, west;
    FD_ZERO(&rest);
    FD_ZERO(&west);
    FD_SET(m_fd, &rest);
    FD_SET(m_fd, &west);
 
    if( (selectFlag = select(m_fd+1, &rest, &west, NULL, &tm)) > 0)
    {
        //如果套接口及可写也可读,需要进一步判断
        if(FD_ISSET(m_fd, &rest) && FD_ISSET(m_fd, &west)) 
        {
            if(getsockopt(m_fd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len) < 0)
            {
                printf("getsockopt error!!!\n");
            }
            else
            {
                if(error == 0)
                {
                    ret = true;
                }
                else 
                {
                    printf("connect getsockopt error!!! %d\n",error);
                }
            }
        }
        //如果套接口可写不可读,则链接完成
        else if(FD_ISSET(m_fd, &west) && !FD_ISSET(m_fd, &rest)) 
        { 
            ret = true;
        }
    }
    else if(selectFlag == 0)
    {
        printf("connect select timeout!!!\n");
    }
    else 
    {
        printf("connect select error!!!\n");
    }
    
done:
    setBlockOpt(m_fd,true);// 设置为阻塞模式
    if(!ret)
    {
        return -1;
    }
    return 0;
}
 
 
int main(int argc,char* argv[])
{
    if(argc <= 1)
    {
        printf("input error!!!\n");
        exit(1);
    }
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0) 
    {
        exit(1);
    }
    if(connectWithTimeout(sockfd,atoi(argv[1])) == 0)
    {
        printf("connect sucess!!!\n");
    }
    else
    {
        printf("connect filed!!!\n");
    }
    close(sockfd);
    
    
    return 0;
}


      

       原理很简单,就是先把套接字设置为非阻塞,因为在非阻塞情况下,connect的结果是立即返回的,然后我们再使用select或者poll等机制来检测套接字一定的时间,如果在超时时间内不可写,则认为connect失败,然后需要把套接字重新设置为阻塞,当然如果你不需要在阻塞模式下工作,可以不用设置。

      如上,我们就可以对connect的超时进行可控。
--------------------- 
版权声明:本文为CSDN博主「clirus」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/clirus/article/details/50577352

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值