免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1567 | 回复: 0
打印 上一主题 下一主题

非阻塞socket 的连接(收藏) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2010-02-20 16:39 |只看该作者 |倒序浏览
方案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:%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) && ((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:
补充关于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模块.
[color="#000000"]我们知道,缺省状态下的套接字都是阻塞方式的,这意味着一个套接口的调用不能立即完成时,进程将进入睡眠状态,并等待操作完成。对于某些应用,需要及时可控的客户响应,而阻塞的方式可能会导致一个较长的时间段内,连接没有响应。造成套接字阻塞的操作主要有recv, send, accept, connect.
[color="#000000"]       下面主要以connect为例,讲讲非阻塞的connect的工作原理。当一个TCP套接字设置为非阻塞后,调用connect,会立刻返回一个EINPROCESS的错误。但TCP的三路握手继续进行,我们将用select函数检查这个连接是否建立成功。建立非阻塞的connect有下面三个用途:
[color="#000000"][color="#000000"]1.       可以在系统做三路握手的时候做些其它事情,这段时间你可以为所欲为。
[color="#000000"]2.       可以用这个技术同时建立多个连接,在web应用中很普遍。
[color="#000000"]3.       可以缩短connect的超时时间,多数实现中,connect的超时在75秒到几分钟之间,累傻小子呢?
[color="#000000"]虽然非阻塞的conncet实现起来并不复杂,但我们必须注意以下的细节:
[color="#000000"]l         即使套接字是非阻塞的,如果连接的服务器是在同一台主机,connect通常会立刻建立。(connect 返回 0 而不是 EINPROCESS)
[color="#000000"]l         当连接成功建立时,描述字变成可写
[color="#000000"]l         当连接出错时,描述字变成可读可写
[color="#000000"] [color="#000000"]例程:定义一个非阻塞的 connect 函数 connect_nonb
[color="#000000"]int connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
[color="#000000"]{
[color="#000000"]       int flags, n, error;
[color="#000000"]       socklen_t len;
[color="#000000"]       fd_set rset, wset;
[color="#000000"]       struct timeval tval;
[color="#000000"]   [color="#000000"]    // 获取当前socket的属性, 并设置 noblocking 属性
[color="#000000"]       flags = fcntl(sockfd, F_GETFL, 0);
[color="#000000"]       fcntl(sockfd, F_SETFL, flags | O_NOBLOCK);
[color="#000000"]   [color="#000000"]    errno = 0;
[color="#000000"]       if ( (n = connect(sockfd, saptr, salen))  
[color="#000000"]              if (errno != EINPROGRESS)
[color="#000000"]                     return (-1);
[color="#000000"]  [color="#000000"]     // 可以做任何其它的操作
[color="#000000"]       if (n == 0)
[color="#000000"]              goto done; // 一般是同一台主机调用,会返回 0
[color="#000000"]  [color="#000000"]     FD_ZERO(&rset);
[color="#000000"]       FD_SET(sockfd, &rset);
[color="#000000"]       wset = rset;  // 这里会做 block copy
[color="#000000"]       tval.tv_sec = nsec;
[color="#000000"]       tval.tv_usec = 0;
[color="#000000"] [color="#000000"]      // 如果nsec 为0,将使用缺省的超时时间,即其结构指针为 NULL
[color="#000000"]       // 如果tval结构中的时间为0,表示不做任何等待,立刻返回
[color="#000000"]       if ((n = select(sockfd+1, &rset, &west, NULL,nsec ?tval:NULL)) == 0) {
[color="#000000"]              close(sockfd);
[color="#000000"]              errno = ETIMEOUT;
[color="#000000"]              return (-1);
[color="#000000"]              }
[color="#000000"]  [color="#000000"]            if(FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &west)) {
[color="#000000"]                     len = sizeof(error);
[color="#000000"]                     // 如果连接成功,此调用返回 0
[color="#000000"]                     if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len)  
[color="#000000"]                            return (-1);
[color="#000000"]       }
[color="#000000"]       else err_quit(“select error: sockfd  not set”);
[color="#000000"] [color="#000000"]done:
[color="#000000"]       fcntl(sockfd, F_SETFL, flags); // 恢复socket 属性
[color="#000000"]       if (error) {
[color="#000000"]              close(sockfd);
[color="#000000"]              errno = error;
[color="#000000"]              return (-1);
[color="#000000"]       }
[color="#000000"]       Return (0);
[color="#000000"]}
[color="#000000"]
[color="#000000"]注意事项:
[color="#000000"]l         如果select调用之前,连接已经建立成功,并且有数据发送过来了,这时套接字将是即可读又可写,和连接失败时是一样的。所以我们必须用getsockopt来检查套接字的状态。
[color="#000000"]l         如果我们不能确定套接字可写是成功的唯一情况时,我们可以采用以下的调用
[color="#000000"](1)           调用getpeername,如果调用失败,返回ENOTCONN,表示连接失败
[color="#000000"](2)           调用read,长度参数为0,如果read失败,表示connect失败。
[color="#000000"](3)           再调用connect一次,其应该失败,如果错误是EISCONN,表示套接字已建立而且连接成功。
[color="#000000"]l         如果在一个阻塞的套接字上调用的connect,在TCP三路握手前被中断,如果connect不被自动重启,会返回EINTR。但是我们不能调用connect等待连接完成,这样会返回EADDRINUSE,此时我们必须调用select,和非阻塞的方式一样。
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/8818/showart_2182919.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP