非阻塞socket的连接

连接套接字,阻塞的套接字超时时间很长无法接受,而是用非阻塞套接字时使用的方案也有多种。后者是个比较好的方法 
方案1:不断重试,直到连接上或者超时:

int connect_socket_timeout(int sockfd,char *dest_host, int port, int timeout)
{
  struct sockaddr_in address;
  struct in_addr inaddr;
  struct hostent *host;
  int  err, noblock=1 , connect_ok=0, begin_time=time(NULL);
  log_debug("connect_socket to %s:%dn",dest_host,port);
  if (inet_aton(dest_host, &inaddr))
  {
         log_debug("inet_aton ok now gethostbyaddr %sn",dest_host);
         memcpy(&address.sin_addr, &inaddr, sizeof(address.sin_addr));
  }
  else
  {
          log_debug("inet_aton fail now gethostbyname %s n",dest_host);
          host = gethostbyname(dest_host);
          if (!host) {
         /* We can't find an IP number */
           log_error("error looking up host  %s : %dn",dest_host,errno);
            return -1;
           }
            memcpy(&address.sin_addr, host->h_addr_list[0], sizeof(address.sin_addr));
  }
  address.sin_family = AF_INET;
  address.sin_port = htons(port);
  /* Take the first IP address associated with this hostname */
  ioctl(sockfd,FIONBIO,&noblock);
  /** connect until timeout */
  /*
  EINPROGRESS   A nonblocking socket connection cannot be completed immediately.
  EALREADY     The socket is nonblocking and a  previous connection attempt has not been completed.
  EISCONN      The socket is already connected.
  */
  if (connect(sockfd, (struct sockaddr *) &address, sizeof(address)) < 0)
  {
  err = errno;
  if (err != EINPROGRESS)
  {
         log_error("connect = %d connecting to host %sn", err,dest_host);
  }
  else
  {
  //                                log_notice("connect pending, return %d n", err);
    while (1) /* is noblocking connect, check it until ok or timeout */
    {
            connect(sockfd, (struct sockaddr *) &address, sizeof(address));
            err = errno;
            switch (err)
            {
                  case EISCONN:   /* connect ok */
                       connect_ok = 1;
                        break;
                 case EALREADY:  /* is connecting, need to check again */
                      //log_info("connect again return EALREADY check again...n");
                     usleep(50000);
                      break;
              default:   /* failed, retry again ? */
                     log_error("connect fail err=%d n",err);
                      connect_ok = -1;
                      break;
             }
          if (connect_ok==1)
           {
                //log_info ("connect ok try time =%d n", (time(NULL) - begin_time) );
                break;
           }
          if (connect_ok==-1)
          {
               log_notice ("connect failed try time =%d n", (time(NULL) - begin_time) );
       
          }
         if ( (timeout>0) && ((time(NULL) - begin_time)>timeout) )
          {
              log_notice("connect failed, timeout %d secondsn", (time(NULL) - begin_time));
               break;
          }
        }
    }
  }
  else        /* Connect successful immediately        */
  {
   // log_info("connect immediate success to host %sn", dest_host);
       connect_ok = 1;
  }
  /** end of try connect */
  return ((connect_ok==1)?sockfd:-1);
  }


方案2:
  补充关于select在异步(非阻塞)connect中的应用,刚开始搞socket编程的时候我一直都用阻塞式的connect,非阻塞connect的问题是由于当时搞proxy scan
  而提出的呵呵,通过在网上与网友们的交流及查找相关FAQ,总算知道了怎么解决这一问题.同样用select可以很好地解决这一问题.大致过程是这样的:
  1.将打开的socket设为非阻塞的,可以用fcntl(socket, F_SETFL, O_NDELAY)完成(有的系统用FNEDLAY也可).
  2.发connect调用,这时返回-1,但是errno被设为EINPROGRESS,意即connect仍旧在进行还没有完成.
  3.将打开的socket设进被监视的可写(注意不是可读)文件集合用select进行监视,如果可写,用getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, sizeof(int));
  来得到error的值,如果为零,则connect成功.
  在许多unix版本的proxyscan程序你都可以看到类似的过程,另外在solaris精华区->编程技巧中有一个通用的带超时参数的connect模块.
  我们知道,缺省状态下的套接字都是阻塞方式的,这意味着一个套接口的调用不能立即完成时,进程将进入睡眠状态,并等待操作完成。对于某些应用,需要及时可控的客户响应,而阻塞的方式可能会导致一个较长的时间段内,连接没有响应。造成套接字阻塞的操作主要有recv, send, accept, connect.
  下面主要以connect为例,讲讲非阻塞的connect的工作原理。当一个TCP套接字设置为非阻塞后,调用connect,会立刻返回一个EINPROCESS的错误。但TCP的三路握手继续进行,我们将用select函数检查这个连接是否建立成功。建立非阻塞的connect有下面三个用途:
  1.可以在系统做三路握手的时候做些其它事情,这段时间你可以为所欲为。
  2 可以用这个技术同时建立多个连接,在web应用中很普遍。
  3.可以缩短connect的超时时间,多数实现中,connect的超时在75秒到几分钟之间,累傻小子呢?
  虽然非阻塞的conncet实现起来并不复杂,但我们必须注意以下的细节:
  * 即使套接字是非阻塞的,如果连接的服务器是在同一台主机,connect通常会立刻建立。(connect 返回 0 而不是 EINPROCESS)
  * 当连接成功建立时,描述字变成可写
  * 当连接出错时,描述字变成可读可写


int connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
 {
  int flags, n, error;
  socklen_t len;
  fd_set rset, wset;
  struct timeval tval;
  // 获取当前socket的属性, 并设置 noblocking 属性
  flags = fcntl(sockfd, F_GETFL, 0);
  fcntl(sockfd, F_SETFL, flags | O_NOBLOCK);
  errno = 0;
  if ( (n = connect(sockfd, saptr, salen)) < 0)
  if (errno != EINPROGRESS)
       return (-1);
  // 可以做任何其它的操作
  if (n == 0)
     goto done; // 一般是同一台主机调用,会返回 0
  FD_ZERO(&rset);
  FD_SET(sockfd, &rset);
  wset = rset;  // 这里会做 block copy
  tval.tv_sec = nsec;
  tval.tv_usec = 0;
  // 如果nsec 为0,将使用缺省的超时时间,即其结构指针为 NULL
  // 如果tval结构中的时间为0,表示不做任何等待,立刻返回
  if ((n = select(sockfd+1, &rset, &west, NULL,nsec ?tval:NULL)) == 0) {
        close(sockfd);
         errno = ETIMEOUT;
          return (-1);
  }
  if(FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &west)) {
       len = sizeof(error);
  // 如果连接成功,此调用返回 0
      if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
           return (-1);
  }
  else   
       err_quit(“select error: sockfd  not set”);
  done:
       fcntl(sockfd, F_SETFL, flags); // 恢复socket 属性
      if (error) {
         close(sockfd);
         errno = error;
         return (-1);
      }
      return (0);
  }


现在在网络服务器编程中使用epoll比较普遍,创建一个socket,设为异步socket(fcntl)

connect到远端(此时connect调用返回非0,但errno为EINPROGRESS,表示正在建立连接中)

由epoll负责监听fd的状态,epoll_wait之捕获到EPOLLOUT事件,收到EPOLLOUT也不能认为是


TCP层次上connect(2)已经成功,要调用getsockopt看SOL_SOCKET的SO_ERROR是否为0。若为0,

才表明真正的TCP层次上connect成功。至于应用层次的server是否收/发数据,那是另一回事了。



 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值