zoukankan      html  css  js  c++  java
  • 一只简单的网络爬虫(基于linux C/C++)————主事件流程

    该爬虫的主事件流程大致如下:
    1.获取命令行参数,执行相应操作
    2.读取配置文件,解析得到各种设置
    3.载入各种模块
    4.种子入队,开启DNS解析线程(原始队列不为空时解析)
    5.创建epoll,开启任务,发起请求等等,关注事件
    6.while大循环中使用epoll_wait返回活跃的事件,每个事件开启一个线程处理(线程中主要是解析页面,保存页面,url处理等),在线程结束的时候可能会开启新的任务。
    创建epoll

    //创建epoll,参数为监听的数目(自从linux2.6.8之后,size参数是被忽略的)
        g_epfd = epoll_create(g_conf->max_job_num);

    开始若干个任务,关注事件

    while(ourl_num++ < g_conf->max_job_num) 
        {
            if (attach_epoll_task() < 0)//执行epoll任务,如果中途遇到队列为空的情况就退出循环
                break;
        }

    attach_epoll_task函数如下:

    //开始一个epoll任务,建立连接还有就是关注事件
    int attach_epoll_task()
    {
        struct epoll_event ev;
        int sock_rv;
        int sockfd;
        Url * ourl = pop_ourlqueue();//从url队列取出一个url
        if (ourl == NULL)
        {
            SPIDER_LOG(SPIDER_LEVEL_WARN, "Pop ourlqueue fail!");
            return -1;
        }
    
        // connect socket and get sockfd 
        //连接
        if ((sock_rv = build_connect(&sockfd, ourl->ip, ourl->port)) < 0) 
        {   
            SPIDER_LOG(SPIDER_LEVEL_WARN, "Build socket connect fail: %s", ourl->ip);
            return -1;
        }
    
        set_nonblocking(sockfd);//设定socket模式,非阻塞
        //发送请求
        if ((sock_rv = send_request(sockfd, ourl)) < 0) 
        {
            SPIDER_LOG(SPIDER_LEVEL_WARN, "Send socket request fail: %s", ourl->ip);
            return -1;
        } 
    ////保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)
    
    // typedef union epoll_data {
    //     void *ptr;
    //     int fd;
    //     __uint32_t u32;
    //     __uint64_t u64;
    // } epoll_data_t;
    //  //感兴趣的事件和被触发的事件
    // struct epoll_event {
    //     __uint32_t events; /* Epoll events */
    //     epoll_data_t data; /* User data variable */
    // };
        evso_arg * arg = (evso_arg *)calloc(1, sizeof(evso_arg));
        arg->fd = sockfd;
        arg->url = ourl;
        ev.data.ptr = arg;
        ev.events = EPOLLIN | EPOLLET;//边沿触发
    
        if (epoll_ctl(g_epfd, EPOLL_CTL_ADD, sockfd, &ev) == 0)//EPOLL_CTL_ADD注册事件
        {// add event  
            SPIDER_LOG(SPIDER_LEVEL_DEBUG, "Attach an epoll event success!");
        } 
        else 
        {
            SPIDER_LOG(SPIDER_LEVEL_WARN, "Attach an epoll event fail!");
            return -1;
        }
    
        g_cur_thread_num++; //当前正在执行抓取的任务数(关注的事件数)
        return 0;
    }

    while大循环

    ……
    ……
    ……
     while(1)
        {
            n = epoll_wait(g_epfd, events, 10, 2000);//epoll将会把发生的事件赋值到events数组中,超时时间单位毫秒
            printf("epoll:%d
    ",n);//打印活跃事件数
            if (n == -1)
                printf("epoll errno:%s
    ",strerror(errno));
            fflush(stdout);
    
            if (n <= 0) //退出是在这里
            {//判断事件数,队列是否为空,为空退出
                if (g_cur_thread_num <= 0 && is_ourlqueue_empty() && is_surlqueue_empty()) 
                {
                    sleep(1);
                    if (g_cur_thread_num <= 0 && is_ourlqueue_empty() && is_surlqueue_empty())
                        break;
                }
            }
    
            for (i = 0; i < n; i++) //n为活跃的事件数
            {
                evso_arg * arg = (evso_arg *)(events[i].data.ptr);
                //发生错误,挂起,不是epollin事件
                if ((events[i].events & EPOLLERR) ||(events[i].events & EPOLLHUP) ||(!(events[i].events & EPOLLIN))) 
                {
                    SPIDER_LOG(SPIDER_LEVEL_WARN, "epoll fail, close socket %d",arg->fd);
                    close(arg->fd);//关闭文件描述符
                    continue;
                }
                //注销事件
                epoll_ctl(g_epfd, EPOLL_CTL_DEL, arg->fd, &events[i]); // del event  
    
                printf("hello epoll:event=%d
    ",events[i].events);
                fflush(stdout);//清除读写缓冲区,需要立即把输出缓冲区的数据进行物理写入时
                //create_thread是自己封装的,在线程的头文件中
                //recv_response为回调函数
                //产生epollin事件就调用接收函数接收,产生一个线程处理任务
                create_thread(recv_response, arg, NULL, NULL);//创建线程处理
            }
        }
    ……
    ……
    ……

    create_thread(recv_response, arg, NULL, NULL);该函数创建线程,在线程结束的时候会开启新的任务,将事件添加进epoll关注,epoll_wait返回后就注销事件,并创建新的线程去处理这个事件
    注意下面的代码

     if (n <= 0) //退出是在这里
            {//判断事件数,队列是否为空,为空退出
                if (g_cur_thread_num <= 0 && is_ourlqueue_empty() && is_surlqueue_empty()) 
                {
                    sleep(1);
                    if (g_cur_thread_num <= 0 && is_ourlqueue_empty() && is_surlqueue_empty())
                        break;
                }
            }

    满足处理的线程数小于等于0,或者两个队列都为空则会退出循环,程序也就此结束。

  • 相关阅读:
    Set,List,Map的区别
    阅读笔记15
    阅读笔记14
    阅读笔记13
    阅读笔记12
    阅读笔记11
    阅读笔记10
    架构漫谈读后感
    阅读笔记1
    暑期周记8
  • 原文地址:https://www.cnblogs.com/sigma0-/p/12630463.html
Copyright © 2011-2022 走看看