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

转自: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.csdn.net/cnctloveyu/archive/2009/06/13/4266564.aspxconnect函数的用法:
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,效率相比还有稍微的下降。但是一旦使用idleconnections模拟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);
            }
        }
}}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园的建设目标是通过数据整合、全面共享,实现校园内教学、科研、管理、服务流程的数字化、信息化、智能化和多媒体化,以提高资源利用率和管理效率,确保校园安全。 智慧校园的建设思路包括构建统一支撑平台、建立完善管理体系、大数据辅助决策和建设校园智慧环境。通过云架构的数据中心与智慧的学习、办公环境,实现日常教学活动、资源建设情况、学业水平情况的全面统计和分析,为决策提供辅助。此外,智慧校园还涵盖了多媒体教学、智慧录播、电子图书馆、VR教室等多种教学模式,以及校园网络、智慧班牌、校园广播等教务管理功能,旨在提升教学品质和管理水平。 智慧校园的详细方案设计进一步细化了教学、教务、安防和运维等多个方面的应用。例如,在智慧教学领域,通过多媒体教学、智慧录播、电子图书馆等技术,实现教学资源的共享和教学模式的创新。在智慧教务方面,校园网络、考场监控、智慧班牌等系统为校园管理提供了便捷和高效。智慧安防系统包括视频监控、一键报警、阳光厨房等,确保校园安全。智慧运维则通过综合管理平台、设备管理、能效管理和资产管理,实现校园设施的智能化管理。 智慧校园的优势和价值体现在个性化互动的智慧教学、协同高效的校园管理、无处不在的校园学习、全面感知的校园环境和轻松便捷的校园生活等方面。通过智慧校园的建设,可以促进教育资源的均衡化,提高教育质量和管理效率,同时保障校园安全和提升师生的学习体验。 总之,智慧校园解决方案通过整合现代信息技术,如云计算、大数据、物联网和人工智能,为教育行业带来了革命性的变革。它不仅提高了教育的质量和效率,还为师生创造了一个更加安全、便捷和富有智慧的学习与生活环境。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值