zoukankan      html  css  js  c++  java
  • 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 整理编辑,转载请注明出处

  • 相关阅读:
    webpack4.x下babel的安装、配置及使用
    SQL Server中追踪器Trace的介绍和简单使用
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
  • 原文地址:https://www.cnblogs.com/wifidog/p/4443357.html
Copyright © 2011-2022 走看看