非阻塞connect的实现

http://www.cnblogs.com/GameDeveloper/p/3406565.html 经典

 

http://blog.csdn.net/shellching/article/details/7663086

http://blog.csdn.net/shellching/article/details/7663086

步骤1: 设置非阻塞,启动连接

实现非阻塞 connect ,首先把 sockfd 设置成非阻塞的。这样调用

connect 可以立刻返回,根据返回值和 errno 处理三种情况:

(1) 如果返回 0,表示 connect 成功。

(2) 如果返回值小于 0, errno 为 EINPROGRESS,  表示连接

      建立已经启动但是尚未完成。这是期望的结果,不是真正的错误。

(3) 如果返回值小于0,errno 不是 EINPROGRESS,则连接出错了。

 

步骤2:判断可读和可写

然后把 sockfd 加入 select 的读写监听集合,通过 select 判断 sockfd

是否可写,处理三种情况:

(1) 如果连接建立好了,对方没有数据到达,那么 sockfd 是可写的

(2) 如果在 select 之前,连接就建立好了,而且对方的数据已到达,

      那么 sockfd 是可读和可写的。

(3) 如果连接发生错误,sockfd 也是可读和可写的。

判断 connect 是否成功,就得区别 (2) 和 (3),这两种情况下 sockfd 都是

可读和可写的,区分的方法是,调用 getsockopt 检查是否出错。

 

步骤3:使用 getsockopt 函数检查错误

getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len)

在 sockfd 都是可读和可写的情况下,我们使用 getsockopt 来检查连接

是否出错。但这里有一个可移植性的问题。

如果发生错误,getsockopt 源自 Berkeley 的实现将在变量 error 中

返回错误,getsockopt 本身返回0;然而 Solaris 却让 getsockopt 返回 -1,

并把错误保存在 errno 变量中。所以在判断是否有错误的时候,要处理

这两种情况。

 

代码:

 

int conn_nonb(int sockfd, const struct sockaddr_in *saptr, socklen_t salen, int nsec)
{
    int flags, n, error, code;
    socklen_t len;
    fd_set wset;
    struct timeval tval;

    flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

    error = 0;
    if ((n == connect(sockfd, saptr, salen)) == 0) {
        goto done;
    } else if (n < 0 && errno != EINPROGRESS){

       //返回EINPROGRESS不一定代表对方端口开放了
        return (-1);
    }

    /* Do whatever we want while the connect is taking place */

    FD_ZERO(&wset);
    FD_SET(sockfd, &wset);
    tval.tv_sec = nsec;
    tval.tv_usec = 0;

    if ((n = select(sockfd+1, NULL, &wset,
                    NULL, nsec ? &tval : NULL)) == 0) {
        close(sockfd);  /* timeout */
        errno = ETIMEDOUT;
        return (-1);
    }

    if (FD_ISSET(sockfd, &wset)) {
        len = sizeof(error);
        code = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
        /* 如果发生错误,Solaris实现的getsockopt返回-1,
         * 把pending error设置给errno. Berkeley实现的
         * getsockopt返回0, pending error返回给error.
         * 我们需要处理这两种情况 */
        if (code < 0 || error) {
            close(sockfd);
            if (error)
                errno = error;
            return (-1);
        }
    } else {
        fprintf(stderr, "select error: sockfd not set");
        exit(0);
    }

done:
    fcntl(sockfd, F_SETFL, flags);  /* restore file status flags */
    return (0);
}

linux c 非阻塞connect版端口扫描程序

之前有个connect的端口扫描程序,那个东西只适合扫localhost,一扫其他机器就×××××,反正是不能扫内网其他机器,更不要说外网主机了。这是由于connect的返回超时设置问题,《UNP》第一卷 第三版 P85-P86指出伯克利系统的超时时限为75s,Solaris9超时时限为4分钟,所以一般认为是75s到几分钟不等,而我测试的时限为189s(Linux Kernel 2.6.24),SYN6次重传~~
    好了,这就是为什么要用非阻塞connect了。
一.非阻塞connect的一般步骤:
   0. sockfd=socket();
   1. fcntl设置sockfd为非阻塞;
   2. connect();
   3. select();
   4. FD_ISSET();
      上面这个步骤也就只能执行一次,一个connect.
二.所以下面是执行多个connect的步骤:
for(;;)
{
   0. sockfd=socket();
   1. fcntl设置sockfd为非阻塞;
   2. connect();
}
   3. select();
   4. FD_ISSET();
  NOTE:
     下面的程序在select()之前设置了sleep(5),也可设置其他时间,这个似乎比较重要,因为这样让select之前返回所以应该返回的connect.(重要性有待考证,欢迎留言哦!!!)
三.代码如下:

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <errno.h>
#include <netdb.h>
#define MINPORT 0
#define MAXPORT 1000
int main(int argc,char **argv)
{
    int fd[MAXPORT];
    int flags,n,i,rst,maxfd,count=0;
    int port[MAXPORT];
    struct sockaddr_in addr[MAXPORT];
    fd_set rset,wset;
    struct timeval tm;
    int addrsize=sizeof(struct sockaddr);
    struct servent *sent;
    FD_ZERO(&rset);
    FD_ZERO(&wset);
    tm.tv_sec=5;
    tm.tv_usec=0;
for(i=MINPORT;i<MAXPORT;i++) /* 可能在此循环完前就有connect返回 */
{
    if((fd[i]=socket(AF_INET,SOCK_STREAM,0))==-1)
    {
        perror("Socket");
        exit(-1);
    }
    /* 设置fd为非阻塞 */
    flags=fcntl(fd[i],F_GETFL,0);
    fcntl(fd[i],F_SETFL,flags|O_NONBLOCK);
    port[i]=i+1;
    bzero((struct sockaddr*)&addr[i],addrsize);
    addr[i].sin_family=AF_INET;
    addr[i].sin_addr.s_addr=inet_addr(argv[1]);
    addr[i].sin_port=htons((short)port[i]);
    if((n=connect(fd[i],(struct sockaddr *)&addr[i],addrsize))<0)
        if(errno!=EINPROGRESS)
            {printf("Connecting 1 error!\n"); exit(1);}
    else if(n==0)
    { //This case may be happen on localhost
        printf("Connecting 1 success! \n");
        exit(0);
    }
    FD_SET(fd[i],&rset);
}
    wset=rset;
    sleep(5);
    rst=select(MAXPORT+3, &rset,&wset,NULL,&tm);
    switch (rst) {
    case -1:
        perror("Select error"); exit(-1);
    case 0:
        for(i=MINPORT;i<MAXPORT;i++)
            close(fd[i]);
        printf("Timed Out!\n");
         break;
    default:
        for(i=MINPORT;i<MAXPORT;i++)
        {
            if (FD_ISSET(fd[i],&rset)||FD_ISSET(fd[i],&wset)) {
                int error;
                socklen_t len = sizeof (error);
                if(getsockopt(fd[i],SOL_SOCKET,SO_ERROR,&error,&len) < 0)
                {
                    printf ("getsockopt fail,connected fail\n");
                    return -1;
                }
                if(error==0)
                {
                    if((sent=getser
vbyport(htons(i+1),"tcp"))==NULL)
                    {
                        printf("Unknown service(port %d) is available. \n",i+1);
                    }
                    else {
                        printf("%s\tservice(port %d) is avilable. \n",sent->s_name,i+1);
                    }
                    count++;
                }
            }
            close(fd[i]);
        }
    }
    if(count==0)
        printf("There is no port open or the domain has down!\n");
    return 0;
}


NOTE:
    1. MINPORT其实不是端口号,而是最小端口号-1,方便循环而已~~
    2. select的第一个参数最大为1024,所以设置的MAXPORT 1000检测1000个端口
四.问题???
    1. 有时还是有问题,用nmap能扫到的端口用这个扫不到。确实很无解,只能先放一放了~
    2. 扫内网有一台机器时21端口始终扫不出来,nmap可以,难道是拒绝链接?OR?继续无解
    3. UBUNTU中文论坛的
服务器真牛B,开了十多个端口,不过用这个扫有一个扫不到~
    4.问题就是还需要实现扫更多的端口,暂时不忙这个,似乎不难~有兴趣的朋友可以做一下然后给我说哦
PS: 31号就百度之星了~虽然没有任何希望,还是去见识下,呵呵~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值