事件模块(二)ngx_epoll_module详解

本文详细解析了Nginx中的epoll模块,包括其核心结构与配置选项、事件处理过程及如何通过epoll_ctl添加或删除事件。此外还介绍了如何处理accept事件并初始化新连接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ngx_epoll_module模块是事件驱动模块的一种,下面主要来分析一下这个模块的接口以及时如何与epoll结合起来的

首先来看一下模块的相关结构体

static ngx_str_t      epoll_name = ngx_string("epoll");

static ngx_command_t  ngx_epoll_commands[] = {

    { ngx_string("epoll_events"),//在调用epoll_wait时,将由第二和第三个参数告诉linux内核一次可以返回多少个事件,这个配置项表示调用一次最多//返回多少事件
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      0,
      offsetof(ngx_epoll_conf_t, events),
      NULL },

    { ngx_string("worker_aio_requests"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      0,
      offsetof(ngx_epoll_conf_t, aio_requests),
      NULL },

      ngx_null_command
};

ngx_event_module_t  ngx_epoll_module_ctx = {
    &epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */

    {                     //这个actions成员是定义事件驱动模块的核心方法
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */
        ngx_epoll_del_connection,        /* delete an connection */
        NULL,                            /* process the changes */
        ngx_epoll_process_events,        /* process the events */    //此函数在解析worker的文章中有过详解
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};

ngx_module_t  ngx_epoll_module = {
    NGX_MODULE_V1,
    &ngx_epoll_module_ctx,               /* module context */
    ngx_epoll_commands,                  /* module directives */
    NGX_EVENT_MODULE,                    /* module type */
    NULL,                                /* init master */
    NULL,                                /* init module */
    NULL,                                /* init process */
    NULL,                                /* init thread */
    NULL,                                /* exit thread */
    NULL,                                /* exit process */
    NULL,                                /* exit master */
    NGX_MODULE_V1_PADDING
};

下面是ngx_epoll_conf_t结构体

typedef struct {
    ngx_uint_t  events;
    ngx_uint_t  aio_requests;
} ngx_epoll_conf_t;
在调用ngx_event_core_module模块时会在启动时调用ngx_evennt_process_init函数,这个函数会检查配置项结构,得到使用哪一个事件驱动模块,并且调用相应模块的初始化函数。在ngx_epoll_module中,这个函数是ngx_epoll_init

下面来分析代码

主要做了两件事

1.调用epoll_create方法创建epoll对象

1.创建epoll_list数组,用于进行epoll_wait调用中接受事件参数

static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
    ngx_epoll_conf_t  *epcf;
    //取得epoll模块的配置结构
    epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);
    //ep是epoll模块定义的一个全局变量,初始化为-1
    if (ep == -1) {
        /*
        调用epoll_create 在内核中创建epoll对象。
        */
        ep = epoll_create(cycle->connection_n / 2);  //创一个epoll对象,容量为总连接数的一半(具体数目并无影响)

        if (ep == -1) {
            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
                          "epoll_create() failed");
            return NGX_ERROR;
        }

    }

    if (nevents < epcf->events) {
        if (event_list) {
            ngx_free(event_list);
        }
        //event_list存储产生事件的数组
        event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
                               cycle->log);
        if (event_list == NULL) {
            return NGX_ERROR;
        }
    }

    nevents = epcf->events;//最大一次可以返回的事件数目

    ngx_io = ngx_os_io;

    ngx_event_actions = ngx_epoll_module_ctx.actions;//设置ngx_event_actions借口

#if (NGX_HAVE_CLEAR_EVENT)
    ngx_event_flags = NGX_USE_CLEAR_EVENT  //epoll添加这个标志,主要为了实现边缘触发
#else
    ngx_event_flags = NGX_USE_LEVEL_EVENT  //水平触发
#endif
                      |NGX_USE_GREEDY_EVENT //io的时候,知道EAGAIN为止
                      |NGX_USE_EPOLL_EVENT; //epoll标志

    return NGX_OK;
}
下面分析ngx_epoll_add_event()函数,了解他是如何调用epoll_ctl向epoll中添加或者删除事件的


static ngx_int_t
ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
{
    int                  op;
    uint32_t             events, prev;
    ngx_event_t         *e;
    ngx_connection_t    *c;
    struct epoll_event   ee;

    c = ev->data;//每个事件的data成员都存放着ngx_connection_t连接

    events = (uint32_t) event;//根据当前事件是读事件还是写事件来决定参数是EPOLLIN还是EPOLLOUT

    if (event == NGX_READ_EVENT) {
        e = c->write;
        prev = EPOLLOUT;
#if (NGX_READ_EVENT != EPOLLIN)
        events = EPOLLIN;
#endif

    } else {
        e = c->read;
        prev = EPOLLIN;
#if (NGX_WRITE_EVENT != EPOLLOUT)
        events = EPOLLOUT;
#endif
    }

    if (e->active) {//根据判断当前事件是否活跃决定是修改还是添加事件
        op = EPOLL_CTL_MOD;
        events |= prev;

    } else {
        op = EPOLL_CTL_ADD;
    }

    ee.events = events | (uint32_t) flags;
    ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);

    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
                   "epoll add event: fd:%d op:%d ev:%08XD",
                   c->fd, op, ee.events);

    if (epoll_ctl(ep, op, c->fd, &ee) == -1) {//调用此函数将事件添加或者修改
        ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
                      "epoll_ctl(%d, %d) failed", op, c->fd);
        return NGX_ERROR;
    }

    ev->active = 1;//将当前事件设置为活跃


    return NGX_OK;
}


ngx_epoll_process_events在之前曾经分析过,在调用ngx_process_timers_and_events时我们会调用ngx_epoll_process_events函数来收集已经就绪的事件,并且在使用accept_mutex锁时将事件分为两个连链表,accept事件链表和post事件链表,在后面将优先依次调用accept事件链表中的事件的回调函数(ngx_event_accept),然后释放锁再依次调用post事件链表中事件的回调函数

下面分析一下函数ngx_event_accept

//这个函数被调用是当listen 句柄有可读事件之后才被调用
void
ngx_event_accept(ngx_event_t *ev)
{
    socklen_t          socklen;
    ngx_err_t          err;
    ngx_log_t         *log;
    ngx_socket_t       s;
    ngx_event_t       *rev, *wev;
    ngx_listening_t   *ls;
    ngx_connection_t  *c, *lc;
    ngx_event_conf_t  *ecf;
    u_char             sa[NGX_SOCKADDRLEN];

    ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);

    if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
        ev->available = 1;

    } else if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {
        ev->available = ecf->multi_accept;
    }

    lc = ev->data;
    ls = lc->listening;
    ev->ready = 0;

    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
                   "accept on %V, ready: %d", &ls->addr_text, ev->available);

    do {
        socklen = NGX_SOCKADDRLEN;
//开始accept句柄
#if (NGX_HAVE_ACCEPT4)
        if (use_accept4) {
            s = accept4(lc->fd, (struct sockaddr *) sa, &socklen,
                        SOCK_NONBLOCK);
        } else {
            s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
        }
#else
        s = accept(lc->fd, (struct sockaddr *) sa, &socklen);  // accept一个新的连接
#endif

        if (s == -1) {  //连接的错误处理
            err = ngx_socket_errno;

            if (err == NGX_EAGAIN) {
                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err,
                               "accept() not ready");
                return;
            }


        //accept到一个新的连接以后,就重新计算ngx_accept_disabled的值。它主要用来做负载均衡使用
        ngx_accept_disabled = ngx_cycle->connection_n / 8
                              - ngx_cycle->free_connection_n;

        //从连接池取得连接,然后创建连接里面包含的数据结构
        c = ngx_get_connection(s, ev->log);

        if (c == NULL) {
            if (ngx_close_socket(s) == -1) {
                ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
                              ngx_close_socket_n " failed");
            }

            return;
        }

        //创建内存池
        c->pool = ngx_create_pool(ls->pool_size, ev->log);
        if (c->pool == NULL) {
            ngx_close_accepted_connection(c);
            return;
        }
        
        //分配客户端地址
        c->sockaddr = ngx_palloc(c->pool, socklen);
        if (c->sockaddr == NULL) {
            ngx_close_accepted_connection(c);
            return;
        }

        ngx_memcpy(c->sockaddr, sa, socklen);

        //分配log
        log = ngx_palloc(c->pool, sizeof(ngx_log_t));
        if (log == NULL) {
            ngx_close_accepted_connection(c);
            return;
        }

        /* set a blocking mode for aio and non-blocking mode for others */

        if (ngx_inherited_nonblocking) {
            if (ngx_event_flags & NGX_USE_AIO_EVENT) {
                if (ngx_blocking(s) == -1) {
                    ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
                                  ngx_blocking_n " failed");
                    ngx_close_accepted_connection(c);
                    return;
                }
            }

        } else {
            if (!(ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT))) {
                if (ngx_nonblocking(s) == -1) {
                    ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
                                  ngx_nonblocking_n " failed");
                    ngx_close_accepted_connection(c);
                    return;
                }
            }
        }

        *log = ls->log;

        //设置读取的回调,这里依赖于操作系统
        c->recv = ngx_recv;
        c->send = ngx_send;
        c->recv_chain = ngx_recv_chain;
        c->send_chain = ngx_send_chain;

        c->log = log;
        c->pool->log = log;

        //设置client的ip地址
        c->socklen = socklen;
        c->listening = ls;
        c->local_sockaddr = ls->sockaddr;

        c->unexpected_eof = 1;


        //准备设置读写的结构
        rev = c->read;
        wev = c->write;

        wev->ready = 1;
        //这里使用的epoll模型,在这里设置连接为nonblocking
        if (ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT)) {
            /* rtsig, aio, iocp */
            rev->ready = 1;
        }

        if (ev->deferred_accept) {
            rev->ready = 1;
        }

        //设置log
        rev->log = log;
        wev->log = log;

        c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);


    
        if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
            if (ngx_add_conn(c) == NGX_ERROR) {
                ngx_close_accepted_connection(c);
                return;
            }
        }

        log->data = NULL;
        log->handler = NULL;

		//它将完成新连接的最后初始化工作,同时将accept到的新连接放入epoll中,挂在handler上的函数就是ngx_http_init_connection
        ls->handler(c);// 被初始化为 ngx_http_init_connection

        if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
            ev->available--;
        }

    } while (ev->available);
}
此函数执行流程大致如下:

1.调用accept方法建立新连接,如果没有准备好的新连接,ngx_event_accept方法直接放回

2.设置负载均衡阀值ngx_accept_disabled,这个阀值是进程允许的总连接数的1/8减去空闲连接数

3.在连接池中获取一个ngx_connection_t结构体

4.为ngx_connection_t中的pool指针建立内存池,在这个连接释放到控线连接池时释放pool内存池

5,将这个新连接的对应读事件添加到epoll中,连接上如果收到用户请求epoll_wait就会调用相应函数(ngx_http_init_connection_t)
6.调用监听对象ngx_listening_t中的handler回调方法。ngx_listening_t结构体的handler回调方法就是当新的TCP连接刚刚建立完成时就在这里调用







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值