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

本文深入探讨了Linux环境下非阻塞模型的connect与select函数的用法,包括设置超时、并发连接等高级特性。通过实例代码展示了如何使用这些函数进行大规模并发扫描操作,对比了传统方法与epoll函数在处理大量并发连接时的效率差异。重点阐述了epoll函数在避免FD_SETSIZE限制和提高IO效率方面的优势。

摘要生成于 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.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);
            }
        }
}}


基于数据挖掘的音乐推荐系统设计与实现 需要一个代码说明,不需要论文 采用python语言,django框架,mysql数据库开发 编程环境:pycharm,mysql8.0 系统分为前台+后台模式开发 网站前台: 用户注册, 登录 搜索音乐,音乐欣赏(可以在线进行播放) 用户登陆时选择相关感兴趣的音乐风格 音乐收藏 音乐推荐算法:(重点) 本课题需要大量用户行为(如播放记录、收藏列表)、音乐特征(如音频特征、歌曲元数据)等数据 (1)根据用户之间相似性或关联性,给一个用户推荐与其相似或有关联的其他用户所感兴趣的音乐; (2)根据音乐之间的相似性或关联性,给一个用户推荐与其感兴趣的音乐相似或有关联的其他音乐。 基于用户的推荐和基于物品的推荐 其中基于用户的推荐是基于用户的相似度找出相似相似用户,然后向目标用户推荐其相似用户喜欢的东西(和你类似的人也喜欢**东西); 而基于物品的推荐是基于物品的相似度找出相似的物品做推荐(喜欢该音乐的人还喜欢了**音乐); 管理员 管理员信息管理 注册用户管理,审核 音乐爬虫(爬虫方式爬取网站音乐数据) 音乐信息管理(上传歌曲MP3,以便前台播放) 音乐收藏管理 用户 用户资料修改 我的音乐收藏 完整前后端源码,部署后可正常运行! 环境说明 开发语言:python后端 python版本:3.7 数据库:mysql 5.7+ 数据库工具:Navicat11+ 开发软件:pycharm
MPU6050是一款广泛应用在无人机、机器人和运动设备中的六轴姿态传感器,它集成了三轴陀螺仪和三轴加速度计。这款传感器能够实时监测并提供设备的角速度和线性加速度数据,对于理解物体的动态运动状态至关重要。在Arduino平台上,通过特定的库文件可以方便地与MPU6050进行通信,获取并解析传感器数据。 `MPU6050.cpp`和`MPU6050.h`是Arduino库的关键组成部分。`MPU6050.h`是头文件,包含了定义传感器接口和函数声明。它定义了类`MPU6050`,该类包含了初始化传感器、读取数据等方法。例如,`begin()`函数用于设置传感器的工作模式和I2C地址,`getAcceleration()`和`getGyroscope()`则分别用于获取加速度和角速度数据。 在Arduino项目中,首先需要包含`MPU6050.h`头文件,然后创建`MPU6050`对象,并调用`begin()`函数初始化传感器。之后,可以通过循环调用`getAcceleration()`和`getGyroscope()`来不断更新传感器读数。为了处理这些原始数据,通常还需要进行校准和滤波,以消除噪声和漂移。 I2C通信协议是MPU6050与Arduino交互的基础,它是一种低引脚数的串行通信协议,允许多个设备共享一对数据线。Arduino板上的Wire库提供了I2C通信的底层支持,使得用户无需深入了解通信细节,就能方便地与MPU6050交互。 MPU6050传感器的数据包括加速度(X、Y、Z轴)和角速度(同样为X、Y、Z轴)。加速度数据可以用来计算物体的静态位置和动态运动,而角速度数据则能反映物体转动的速度。结合这两个数据,可以进一步计算出物体的姿态(如角度和角速度变化)。 在嵌入式开发领域,特别是使用STM32微控制器时,也可以找到类似的库来驱动MPU6050。STM32通常具有更强大的处理能力和更多的GPIO口,可以实现更复杂的控制算法。然而,基本的传感器操作流程和数据处理原理与Arduino平台相似。 在实际应用中,除了基本的传感器读取,还可能涉及到温度补偿、低功耗模式设置、DMP(数字运动处理器)功能的利用等高级特性。DMP可以帮助处理传感器数据,实现更高级的运动估计,减轻主控制器的计算负担。 MPU6050是一个强大的六轴传感器,广泛应用于各种需要实时运动追踪的项目中。通过 Arduino 或 STM32 的库文件,开发者可以轻松地与传感器交互,获取并处理数据,实现各种创新应用。博客和其他开源资源是学习和解决问题的重要途径,通过这些资源,开发者可以获得关于MPU6050的详细信息和实践指南
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值