• wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(3) -----使用


    接着上文介绍的函数fdevent_linux_sysepoll_event_add 讲解,首先看函数的第三个参数events,他是一个整型,其没以为对应一种IO事件。
    上面fdevent_event_add()函数的额第三个参数是FDEVENT_IN,这是一个宏

    /*
     * 用于标记文件描述符的状态
     */
     #define FDEVENT_IN     BV(0)     //文件描述符是否可写
     #define FDEVENT_PRI    BV(1)      //不阻塞的可读高优先级的数据 poll
     #define FDEVENT_OUT    BV(2)     //文件描述符是否可读
    #define FDEVENT_ERR    BV(3)     //文件描述符是否出错
    #define FDEVENT_HUP    BV(4)     //已挂断 poll
    #define FDEVENT_NVAL   BV(5)     //描述符不引用一打开文件 poll

    其中BV也是一个宏,定义在settings.c文件中:

    #define BV(x) (1 << x)

    其作用就是将一个整数变量第x位置1,其余为0。
    那么上面FDEVENT_XX的宏定义就是定义了一系列者养的整数,通过这些宏的或操作,可以在一个整数中用不同的位表示不同的事件。这些宏和struct epoll_event结构体中的events变量的对应的宏定义对应(有点绕。。。)。说白了就是和epoll.h中的枚举EPOLL_EVENTS对应。
    这个函数的主要工作就是设置一些值然后调用epoll_ctl函数。虽然函数的名称是event_add,但是如果第二个参数fde_ndx不等于-1(那肯定就等于后面的参数fd),那这个时候可以肯定fd已经在epoll的监测中了,这时候不再是将fd增加到epoll中,而是修改其要监听的IO事件,就是最后一个参数表示的事件。fdevent_linux_sysepoll_event_add()增加成功后返回fd,在fdevent_event_add中,将这个返回值赋给fde_ndx,所以,fde_ndx==fd。
    至此,监听fd的注册流程已经全部结束了,由于当有连接请求是,监听fd的表现是有数据课读,因此,只监听其FDEVENT_IN事件。注册之后,监听fd就开始等待连接请求。
    接着,进程执行到了下面的语句:

    //启动事件轮询。底层使用的是IO多路转接。
    
    if ((n = fdevent_poll(srv->ev, 1000)) > 0)
    {
                /*
                 * nn是事件的数量(服务请求啦,文件读写啦什么的。。。)
                 */
                int revents;
                int fd_ndx = -1;
                /**
                 * 这个循环中逐个的处理已经准备好的请求,知道所有的请求处理结束。
                 */
                do
                {
                    fdevent_handler handler;
                    void *context;
                    handler_t r;
    
                    fd_ndx = fdevent_event_next_fdndx(srv->ev, fd_ndx);
                    revents = fdevent_event_get_revent(srv->ev, fd_ndx);
                    fd = fdevent_event_get_fd(srv->ev, fd_ndx);
                    handler = fdevent_get_handler(srv->ev, fd);
                    context = fdevent_get_context(srv->ev, fd);
                    /*
                     * connection_handle_fdevent needs a joblist_append
                     */
                    /**
                     * 这里,调用请求的处理函数handler处理请求!
                     */
                    switch (r = (*handler) (srv, context, revents))
                    {
                    case HANDLER_FINISHED:
                    case HANDLER_GO_ON:
                    case HANDLER_WAIT_FOR_EVENT:
                    case HANDLER_WAIT_FOR_FD:
                        break;
                    case HANDLER_ERROR:
                        SEGFAULT();
                        break;
                    default:
                        log_error_write(srv, __FILE__, __LINE__, "d", r);
                        break;
                    }
                }while (--n > 0);
            }
            else if (n < 0 && errno != EINTR)
            {
                log_error_write(srv, __FILE__, __LINE__, "ss","fdevent_poll failed:", strerror(errno));
            }
    }

    这段语句是worker子进程的工作重心所在。首先调用fdevent_poll()函数等待IO事件发生,如果没有IO事件,程序会阻塞在这个函数中。如果有fd发生了IO事件,则从fdevent_poll函数中返回,返回值是发生了IO事件的fd的数量。接着,程序进入do-while循环,循环中对每个fd,调用一些列fdevent系统的接口函数,最后调用event_handler处理IO事件。
      fdevent_poll()函数调用fdevents结构体中的poll,最终调用的是epoll_wait()函数。epoll_wait()函数将发生了IO事件的fd对应的epoll_evet结构体实例的存储在fdevents结构体的epoll_events数组成员中。fdevent_event_next_fdndx函数返回epoll_events数组中下一个元素的下标,fdevent_event_get_revent函数调用ev->event_get_revent()获得fd发生的IO事件,最终调用的是:

    static int fdevent_linux_sysepoll_event_get_revent(fdevents * ev, size_t ndx)
    {
        int events = 0, e;
        e = ev->epoll_events[ndx].events;
        if (e & EPOLLIN)
            events |= FDEVENT_IN;
        if (e & EPOLLOUT)
            events |= FDEVENT_OUT;
        if (e & EPOLLERR)
            events |= FDEVENT_ERR;
        if (e & EPOLLHUP)
            events |= FDEVENT_HUP;
        if (e & EPOLLPRI) //有紧急数据到达(带外数据)
            events |= FDEVENT_PRI;
        return e;
    }

    这个函数就做了一个转换。fdevent_get_handler和fdevent_get_context返回fd对应的fdnode中的handler和ctx。这几个函数都简单,这里不再列出源代码。
      最后,在switch语句中调用fd对应的handler函数处理事件。对于监听fd,调用的函数为:

    /**
     * 这个是监听socket的IO事件处理函数。
     * 只要的工作就是建立和客户端的socket连接。只处理读事件。在处理过程中,
     * 每次调用这个函数都试图一次建立100个连接,这样可以提高效率。
     */
    handler_t network_server_handle_fdevent(void *s, void *context, int revents)
    {
        server *srv = (server *) s;
        server_socket *srv_socket = (server_socket *) context;
        connection *con;
        int loops = 0;
        UNUSED(context);
        /*
         * 只有fd事件是FDEVENT_IN时,才进行事件处理。
         */
        if (revents != FDEVENT_IN)
        {
            log_error_write(srv, __FILE__, __LINE__, "sdd", "strange event for server socket", srv_socket->fd, revents);
            return HANDLER_ERROR;
        }
        /*
         * accept()s at most 100 connections directly we jump out after 100 to give the waiting connections a chance
         *一次监听fd的IO事件,表示有客户端请求连接,对其的处理就是建立连接。建立连接后并不急着退出函数,
         * 而是继续尝试建立新连接,直到已经建立了100次连接。这样可以提高效率。
         */
        for (loops = 0; loops < 100 && NULL != (con =connection_accept(srv, srv_socket)); loops++)
        {
            handler_t r;
            //根据当前状态,改变con的状态机,并做出相应的动作。
            connection_state_machine(srv, con);
            switch (r = plugins_call_handle_joblist(srv, con))
            {
            case HANDLER_FINISHED:
            case HANDLER_GO_ON:
                break;
            default:
                log_error_write(srv, __FILE__, __LINE__, "d", r);
                break;
            }
        }
        return HANDLER_GO_ON;
    }

    监听fd有IO事件,表示有客户端请求连接,对其的处理就是建立连接。在这个函数中,建立连接后并不急着退出,而是继续尝试建立新连接,直到已经建立了100次连接。这样可以提高效率。connection_accept()函数接受连接请求并返回一个connection结构体指针。接着对这个连接启动状态机(状态机可是很有意思的。。。)。然后把连接加到作业队列中。
      这里有一个问题,如果连接迟迟达不到100个,那么程序就会阻塞在这个函数中,这样对于已经建立的连接也就没法进行处理。这怎么办?读者不要忘了,在将监听fd注册到fdevent系统时,其被设置成了非阻塞的,因此,如果在调用accept()函数时没有连接请求,那么accept()函数会直接出错返回,这样connection_accept就返回一个NULL,退出了for循环。
    到这,fdevent系统对于监听fd的处理就完成了一个轮回。处理完IO事件以后fd接着在epoll中等待下一次事件。
      下一篇中回介绍一些fdevent系统对连接fd(accept函数的返回值)的处理,以及超时连接的处理。

    本文章由http://www.wifidog.pro/2015/04/21/wifidog%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E4%BD%BF%E7%94%A8-2.html 整理编辑,转载请注明出处

  • 相关阅读:
    FZU 2112 并查集、欧拉通路
    HDU 5686 斐波那契数列、Java求大数
    Codeforces 675C Money Transfers 思维题
    HDU 5687 字典树插入查找删除
    HDU 1532 最大流模板题
    HDU 5384 字典树、AC自动机
    山科第三届校赛总结
    HDU 2222 AC自动机模板题
    HDU 3911 线段树区间合并、异或取反操作
    CodeForces 615B Longtail Hedgehog
  • 原文地址:https://www.cnblogs.com/wifidog/p/4443357.html
Copyright © 2020-2023  润新知