非阻塞socket 的连接

非阻塞socket 的连接
2010-02-21 14:17
方案1:使用while和usleep的timeout
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:%d/n",dest_host,port);                                                          
if (inet_aton(dest_host, &inaddr))
{       
//        log_debug("inet_aton ok now gethostbyaddr %s/n",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 : %d/n",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 %s/n", 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) );
break;
}
if ( (timeout>0) && ((time(NULL) - begin_time)>timeout) )
{
log_notice("connect failed, timeout %d seconds/n", (time(NULL) - begin_time));
break;
}
}
}
}
else        /* Connect successful immediately        */
{
//                    log_info("connect immediate success to host %s/n", dest_host);
connect_ok = 1;
}
/** end of try connect */
return ((connect_ok==1)?sockfd:-1);
}

方案2:使用异步I/O的timeout

补充关于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 实现起来并不复杂,但我们必须注意以下的细节 :

l         即使套接字是非阻塞的,如果连接的服务器是在同一台主机, connect 通常会立刻建立。 (connect 返回 0 而不是 EINPROCESS)

l         当连接成功建立时,描述字变成可写

l         当连接出错时,描述字变成可读可写

  例程:定义一个非阻塞的 connect 函数 connect_nonb

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);

}

 

注意事项:

l         如果 select 调用之前,连接已经建立成功,并且有数据发送过来了,这时套接字将是即可读又可写,和连接失败时是一样的。所以我们必须用 getsockopt 来检查套接字的状态。

l         如果我们不能确定套接字可写是成功的唯一情况时,我们可以采用以下的调用

(1)           调用 getpeername ,如果调用失败,返回 ENOTCONN ,表示连接失败

(2)           调用 read ,长度参数为 0 ,如果 read 失败,表示 connect 失败。

(3)           再调用 connect 一次,其应该失败,如果错误是 EISCONN ,表示套接字已建立而且连接成功。

l         如果在一个阻塞的套接字上调用的 connect ,在 TCP 三路握手前被中断,如果 connect 不被自动重启,会返回 EINTR 。但是我们不能调用 connect 等待连接完成,这样会返回 EADDRINUSE ,此时我们必须调用 select ,和非阻塞的方式一样。

 

这里再补充一个使用epoll实现的timeout

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <asm/ioctls.h>
#include <errno.h>
#include <sys/select.h>
#include <fcntl.h>
#include <sys/epoll.h>
#define ZERO (fd_set *)0
#define MAXSOCKFDNUM 10000
#define TIMEOUT (20 * 1000)


struct epoll_datas

   {

    struct sockaddr servaddr;

    int sockfd;

   };

struct epoll_datas datas[MAXSOCKFDNUM];

void msg()
{
system("clear");
printf("scan startip endip startport endport/n");
printf("scan 192.168.1.1 192.168.1.255 20 2009/n");
printf("scan 192.168.1.1 20 2009/n");
}
int main(int argc, char** argv)
{
char *ip;
int startport,endport;
in_addr_t startip;
in_addr_t endip;
int sockfd,nRet;
int nCurrentIPval,CurrentPort; //,flags;
struct sockaddr_in to; /* 定义to为sockaddr_in类型 */
int flags;
int error;
int epoll_handle;
socklen_t len;
struct epoll_event ev;
struct epoll_event events[MAXSOCKFDNUM * 2];


if (argc!=4 && argc!=5)
{
msg();
return 0;
}
if( 4 == argc)
{
ip=argv[1];
startport=atoi(argv[2]); /* 转换为整型 */
endport=atoi(argv[3]);
if (startport<1||endport>65535)
{
printf("port error    1<port<65535/n");
return 0;
}

if ((epoll_handle = epoll_create(MAXSOCKFDNUM)) == -1)
{

        perror("epoll_create");

        return 0;

       }/* 创建一个epoll的句柄 */

to.sin_family=AF_INET; /* to结构体中的sin_family定义地址族 */
to.sin_addr.s_addr=inet_addr(ip); /* 填写IP,并转换成in_addr */           
int i;
for (CurrentPort =startport; CurrentPort <=endport ; CurrentPort++)
{  
to.sin_port=htons(CurrentPort); /* 将端口转换成网络字节序 */


sockfd=socket(AF_INET,SOCK_STREAM,0);/* 创建套接字 */    
if( -1 == sockfd )
{
printf( "Err number: %d/n", errno );
perror( "socket" );
}

flags=fcntl(sockfd,F_GETFL,0);
if (flags == -1)
{
perror("fcntl");
return 0;
}

fcntl(sockfd,F_SETFL,flags|O_NONBLOCK);

ev.events = EPOLLIN | EPOLLOUT | EPOLLET;

if (epoll_ctl(epoll_handle, EPOLL_CTL_ADD, sockfd, &ev) < 0)
{

            perror("epoll_ctl");

            return 0;

           }


int nRet = connect( sockfd, (struct sockaddr *)&to, sizeof(to) );

if ( -1 == nRet )
{        
if(errno != EINPROGRESS)
{
perror("connect");
//return -1;
}
}
if (nRet == 0)
{
ev.events = EPOLLIN | EPOLLOUT | EPOLLET; /* 可读、可写、设为ET模式 */

        len = sizeof (struct sockaddr_in);
//ev.data.fd = nRet;
epoll_ctl(epoll_handle,EPOLL_CTL_MOD, sockfd, &ev);
}

      if ((nRet = epoll_wait(epoll_handle,events,MAXSOCKFDNUM * 2, TIMEOUT)) == -1)
{
perror("epoll_wait");
}
for (i = 0;i <nRet;i++)
{
if(events[i].events == sockfd)
{       
error = 0;
socklen_t len = sizeof(int);

if (( 0 == getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len) ))
{  
if( 0 == error )
{
printf("%s %d open/n", ip, CurrentPort);

}

                        }
} close(sockfd);
}
}
}}

你可能感兴趣的:(tcp,socket,struct,Solaris,null,events)