socket编程之connect非阻塞模型-初探

本文探讨了网络编程中的非阻塞IO模型,重点介绍了connect函数与select函数的使用方法及其局限性,并通过示例代码展示了如何利用Epoll解决大规模并发连接的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自:http://hi.baidu.com/kelz/blog/item/14d14c4f726f3a01b3de05a3.html

2009-06-05 10:46

说明:本文所说的相关技术已经很陈旧了,实在不适合用“初探”这个词,但是于我个人而言,却又的确是初探,现总结出来,分享之。本文难免有错漏之处,还请各位高人斧正,别直接拿斧子找我。:)另感谢Neill老大友情支持。

一:connect()

    在介绍connect非阻塞模型之前。让我们先来认识一下这个函数——connect()。connect函数将一个流套接字连接到指定IP地址的指定端口上。(cnctloveyu注:connect也可以用于UDP,只是没有3次握手,见http://blog.youkuaiyun.com/cnctloveyu/archive/2009/06/13/4266564.aspx connect函数的用法:
int connect(SOCKET s,const struct sockaddr FAR* name,int namelen);
参数s指定用于连接的套接字句柄,name参数指向一个sockaddr_in结构,用来指定要连接到的服务器的IP地址和端口,namelen参数则指 定sockaddr_in结构的长度。这个参数连接成功的时候,函数返回0,否则返回值是SOCKET_ERROR。connect到目标主机的时候,这 个超时是不由我们来设置的。不过正常情况下这个超时都很长,并且connect又是一个阻塞方法,一个主机不能连接,等着connect返回还能忍受,你 的程序要是要试图连接多个主机,恐怕遇到多个不能连接的主机的时候,会塞得你受不了的.正常情况下,因为TCP三次握手需要一些时间,也就是说确认连接用 的数据包需要一定的往返时间,当连接互联网上的主机时,连接的过程往往需要几秒的时间,如何让connect设置超时时间呢,下面我们就说说 select()

二:select()

函数说明:
int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);

select()用来等待文件描述词状态的改变。参数n代表最大的文件描述词加1,参数readfds、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读,写或例外的状况。底下的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set); 用来清除描述词组set的全部位

参数:timeout为结构timeval,用来设置select()的等待时间,其结构定义如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};

返回值:如果参数timeout设为NULL则表示select()没有timeout。

错误代码:执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。
EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足

三:代码。

上面简单的说明了connect和select函数的用法以及返回值错误代码之类的,其他的我这里不详细说明了,网上一找一堆,还是直接举个例子吧,下面放一个linux c扫描器的源码,没什么技术含量。只是一个简单的示例而已。源码如下:

#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>
#define ZERO (fd_set *)0

int main(int argc, char** argv)
{
    char *ip;
    int startport,endport;
    in_addr_t startip;
    in_addr_t endip;
    int sockfd;
    int nCurrentIPval; //,flags;
    struct sockaddr_in to; /* 定义to为sockaddr_in类型 */
    //int ret;
//    unsigned long mode=1;
    int flags;
    int error;
   
    void msg()
{
    system("clear");
    printf("scan startip endip startport endport/n");
    printf("scan 192.168.1.1 192.168.1.255 20 50/n");
    printf("scan 192.168.1.1 20 50/n");
}

    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;
        }
                                   
        to.sin_family=AF_INET; /* to结构体中的sin_family定义地址族 */
        to.sin_addr.s_addr=inet_addr(ip); /* 填写IP,并转换成in_addr */
        struct timeval timeout;
        fd_set mask;

        int i;
        for (i =startport; i <=endport ; i++)
        {  
            to.sin_port=htons(i); /* 将端口转换成网络字节序 */
           
            sockfd=socket(AF_INET,SOCK_STREAM,0);/* 创建套接字 */         
            if( -1 == sockfd )
            {
                printf( "Err number: %d/n", errno );
                perror( "socket" );
                //exit( -1 );
                continue;
            }
           
            flags=fcntl(sockfd,F_GETFL,0);
            fcntl(sockfd,F_SETFL,flags|O_NONBLOCK);
           
          int nRet = connect( sockfd, (struct sockaddr *)&to, sizeof(to) );
                        
            if ( -1 == nRet )
            {        
                if(errno != EINPROGRESS)
                {
                    perror("connect");
                    //return -1;
                    continue;
                }
               
                FD_ZERO(&mask);
                FD_SET(sockfd,&mask);

                timeout.tv_sec=0;
                timeout.tv_usec=500;
       
                switch (select(sockfd + 1, NULL, &mask, NULL, &timeout))
                {
                    case -1:
                        close(sockfd);
                        printf("select error/n");
                        perror( "select" );
                        break;
                    case 0:
                        close(sockfd);
                        break;
                    default:
                        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, i);
                                close(sockfd);
                            }
                        }
                        break;
                }
            }
            else if( 0 == nRet )
            {    
                close(sockfd);
                printf("Connected: %s %d open/n", ip, i);
            }
        }
    }
    else if( 5 == argc )
    {
        startip=ntohl(inet_addr(argv[1]));
        endip=ntohl(inet_addr(argv[2]));
       
        if( endip < startip )
        {
            in_addr_t nswap = endip;
            endip = startip;
            startip = nswap;
        }
       
        startport=atoi(argv[3]);
        endport=atoi(argv[4]);
        if (startport<1||endport>65535)
        {
            printf("port error    1<port<65535/n");
            return 0;
        }
                       
        to.sin_family=AF_INET;
        //to.sin_addr.s_addr=inet_addr(startip);
        //to.sin_addr.s_addr=inet_addr(endip);
       
        for (nCurrentIPval =startip; nCurrentIPval<=endip; nCurrentIPval++)
        {
            to.sin_addr.s_addr = htonl(nCurrentIPval);
       
            printf( "/n---------------------------------/n/n" );
            int i;
            for (i =startport; i <=endport ; i++)
            {
                sockfd=socket(AF_INET,SOCK_STREAM,0);
                if( -1 == sockfd )
                {
                    printf( "Err number: %d/n", errno );
                    exit( -1 );
                }
           
                to.sin_port=htons(i);
               
                if (connect(sockfd, (struct sockaddr *)&to, sizeof(struct sockaddr)) == 0)
                {
                    printf("%s %d/n",inet_ntoa(to.sin_addr),i);
                }
                close( sockfd );
            }          
        }
    }
   
    return 0;
}

四:Epoll函数

   上面的代码在执行大规模并发连接的时候,就会出现个问题,select()函数的 FD_SETSIZE 限制。linux系统不能超过1024。也就是说,你同时扫描,不能超过1024个端口。超过就会报错。有人会说用多线程,不就能解决此问题了吗,使用多 线程,并发数达到1000时将严重影响系统的性能。此时引出一个函数。EPOLL。epoll的IO效率不随FD数目增加而线性下降,传统的 select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。内核实现中epoll是根据每个fd上面的callback函数实现的。 只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会。如果所有的socket基本上都是活跃的---比如一个高速LAN环境,过多使用 epoll,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE    1024
表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。epoll的接口非常简单,一共就三个函数:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
其他函数说明篇幅原因,也不多说了,具体可以自己去man一下或网上查。直接放出修改过后的源码吧。如下:
#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);
            }
        }
}}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值