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

来源:互联网 发布:matlab遗传算法cae 编辑:程序博客网 时间:2024/05/29 15:59

客户端在连接服务器时,可能会出现问题,导致三次握手无法完成,持续重试,表现在客户端程序的行为就是卡在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 0
原创粉丝点击