Nginx事件管理之概念描述

1. Nginx事件管理概述

首先,Nginx定义了一个核心模块ngx_events_module,这样在Nginx启动时会调用ngx_init_cycle方法解析配置项,一旦在
nginx.conf配置文件中找到ngx_events_module感兴趣的配置项“events{}”,ngx_events_module模块就开始工作了。
ngx_events_module模块定义了事件类型的模块,它的全部工作就是为所有的事件模块解析“events{}”中的配置项,同时管理
这些事件模块存储配置项的结构体。

其次,Nginx定义了一个非常重要的事件模块ngx_event_core_module,这个模块会决定使用哪种事件驱动机制,以及如何管
理事件。

最后,Nginx定义了一系列运行在不同操作系统、不同内核版本上的事件驱动模块,包括:

  • ngx_epoll_module
  • ngx_kqueue_module
  • ngx_pool_module
  • ngx_select_module
  • ngx_devpoll_module
  • ngx_eventport_module
  • ngx_aio_module
  • ngx_rtsig_module
  • 基于Windows的ngx_select_module

在ngx_event_core_module模块的初始化函数ngx_event_process_init中,会根据配置变量ecf->use记录的值,进而调用到
对应的事件处理模块的初始化函数,比如epoll模块的ngx_epoll_init函数。

事件模块通用接口:ngx_event_module_t
typedef struct {
    /* 事件模块的名称 */
    ngx_str_t              *name;

    /* 在解析配置项前,这个回调方法用于创建存储配置项参数的结构体 */
    void                 *(*create_conf)(ngx_cycle_t *cycle);
    /* 在解析配置项完成后,init_conf方法会被调用,用于综合处理当前事件模块感兴趣的全部配置项 */
    char                 *(*init_conf)(ngx_cycle_t *cycle, void *conf);

    /* 对于事件驱动领机制,每个事件模块需要实现的10个抽像方法 */
    ngx_event_actions_t     actions;
} ngx_event_module_t;

ngx_event_module_t 中的 actions 成员是定义事件驱动模块的核心方法:

typedef struct {
    /* 将某描述符的某个事件(可读/可写)添加到多路复用监控里 */
    ngx_int_t  (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    /* 将某描述符的某个事件(可读/可写)从多路复用监控里删除 */
    ngx_int_t  (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

    /* 启用1个事件,目前事件驱动框架不会调用这个方法,大部分事件驱动模块对于该方法
     * 的实现都是与上面的add方法完全一致 */
    ngx_int_t  (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    /* 禁用1个事件,目前事件驱动框架不会调用这个方法,大部分事件驱动模块对于该方法
     * 的实现都是与上面的del方法完全一致 */
    ngx_int_t  (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

    /* 向事件驱动机制中添加一个新的连接,这意味着连接上的读写事件都添加到事件驱动机制中了 */
    ngx_int_t  (*add_conn)(ngx_connection_t *c);
    /* 从事件驱动机制中移除一个连接上的读写事件 */
    ngx_int_t  (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);

    ngx_int_t  (*notify)(ngx_event_handler_pt handler);

    /* 在正常工作循环中,将通过调用process_events方法来处理事件。这个方法仅在
     * ngx_process_events_and_timers方法中调用,它是处理、分发事件的核心 */
    ngx_int_t  (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags);

    /* 初始化事件驱动模块的方法 */
    ngx_int_t  (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
    /* 退出事件驱动模块前调用的方法 */
    void       (*done)(ngx_cycle_t *cycle);
}ngx_event_actions_t;

2. Nginx事件的定义

在Nginx中,每一个事件都由ngx_event_t结构体来表示。

struct ngx_event_s {
    /* 事件相关的对象。通常data都是指向ngx_connection_t连接对象。开启文件异步I/O时,它可能
     * 会指向ngx_event_aio_t结构体 */
    void            *data;

    /* 标志位,为1时表示事件是可写的。通常情况下,它表示对应的TCP连接目前状态是可写的,也就是
     * 连接处于可以发送网络包的状态 */
    unsigned         write:1;

    /* 标志位,为1时表示为此事件可以建立新的连接。通常情况下,在ngx_cycle_t中的listening动态
     * 数组中,每一个监听对象ngx_listening_t对应的读事件中的accept标志位才会是1 */
    unsigned         accept:1;

    /* 这个标志位用于区分前档事件是否是过期的,它仅仅是给事件驱动模块使用的,而事件消费模块可
     * 不用关心。为什么需要这个标志位呢?当开始处理一批事件时,处理前面的事件可能会关闭一些连
     * 接,而这些连接有可能会影响这批事件中还未处理到的后面的事件。这时,可通过instance标志位
     * 来避免处理后面已经过期的事件 */
    /* used to detect the stale events in kqueue and epoll */
    unsigned         instance:1;

    /*
     * the event was passed or would be passed to a kernel;
     * in aio mode - operation was posted.
     */
    /* 标志位,为1时表示当前事件是活跃的,为0时表示事件是不活跃的。这个状态对应着事件驱动模块
     * 处理方式的不通过。例如,在添加事件、删除事件和处理事件时,active标志位的不同都会对应着
     * 不同的处理方式。在使用事件时,一般不会直接改变active标志位 */
    unsigned         active:1;

    /* 标志位,为1时表示禁用事件,仅在kqueue或者rtsig事件驱动模块中有效,而对于epoll事件驱动
     * 模块则无意义 */
    unsigned         disabled:1;

    /* 标志位,为1时表示当前事件已经准备就绪,也就是说,允许这个事件的消费模块处理这个事件。在
     * HTTP框架中,经常会检查事件的ready标志位以确定是否可以接收请求或者发送响应 */
    /* the ready event; in aio mode 0 means that no operation can be posted */
    unsigned         ready:1;

    /* 该标志位仅对kqueue,eventport等模块有意义 */
    unsigned         oneshot:1;

    /* 该标志位用于异步AIO事件的处理 */
    /* aio operation is complete */
    unsigned         complete:1;

    /* 标志位,为1时表示当前处理的字符流已经结束 */
    unsigned         eof:1;
    /* 标志位,为1时表示事件在处理过程中出现错误 */
    unsigned         error:1;

    /* 标志位,为1时表示这个事件已经超时,用于提示事件的消费模块做超时处理 */
    unsigned         timedout:1;
    /* 标志位,为1时表示这个事件存在与定时器中 */
    unsigned         timer_set:1;

    /* 标志位,为1时表示需要延迟处理这个事件,它仅用于限速功能 */
    unsigned         delayed:1;

    /* 标志位,为1时表示延迟建立TCP连接,也就是说,经过TCP三次握手后并不建立连接,而是要等到
     * 真正收到数据包后才会建立TCP连接 */
    unsigned         deferred_accept:1;

    /* 标志位,为1时表示等待字符流结束,它只与kqueue和aio事件驱动机制有关 */
    /* the pending eof reported by kqueue, epoll or in aio chain operation */
    unsigned         pending_eof:1;

    unsigned         posted:1;

    unsigned         closed:1;

    /* to test on worker exit */
    unsigned         channel:1;
    unsigned         resolver:1;

    unsigned         cancelable:1;

#if (NGX_HAVE_KQUEUE)
    unsigned         kq_vnode:1;

    /* the pending errno reported by kqueue */
    int              kq_errno;
#endif

    /*
     * kqueue only:
     *   accept:     number of sockets that wait to be accepted
     *   read:       bytes to read when event is ready
     *               or lowat when event is set with NGX_LOWAT_EVENT flag
     *   write:      available space in buffer when event is ready
     *               or lowat when event is set with NGX_LOWAT_EVENT flag
     *
     * epoll with EPOLLRDHUP:
     *   accept:     1 if accept many, 0 otherwise
     *   read:       1 if there can be data to read, 0 otherwise
     *
     * iocp: TODO
     *
     * otherwise:
     *   accept:     1 if accept many, 0 otherwise
     */

#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
    int              available;
#else
    /* 标志位,在epoll事件驱动机制下表示一次尽可能多地建立TCP连接,它与multi_accept配置项对应 */
    unsigned         available:1;
#endif

    /* 这个事件发生时的处理方法,每个事件消费模块都会重新实现它 */
    ngx_event_handler_pt  handler;


#if (NGX_HAVE_IOCP)
    /* Windos系统下的一种事件驱动模型 */
    ngx_event_ovlp_t ovlp;
#endif

    ngx_uint_t       index;

    ngx_log_t       *log;

    /* 定时器节点,用于定时器红黑树中 */
    ngx_rbtree_node_t   timer;

    /* the posted queue */
    ngx_queue_t      queue;
};

事件是不需要创建的,因为Nginx在启动时已经在ngx_cycle_t的read_events和write_events成员中都预分配了所有的读写
事件。每一个连接将自动对应一个写事件和读事件,只要从连接池中获取一个空闲连接就可以拿到事件了。将事件添加到
epoll等事件驱动模块中推荐使用 ngx_handle_read_event 和 ngx_handle_write_event 方法,而不是直接使用
ngx_event_actions_t 结构体的 add 方法或者 del 方法.

ngx_handle_read_event()

/* 
 * @rev: 要操作的事件
 * @flags:指定事件的驱动方式。对于不同的事件驱动模块,flags的取值范围并不同,对于epoll来说,flags
 *         的取值返回可以是 0 或者 NGX_CLOSE_EVENT(NGX_CLOSE_EVENT仅在epoll的LT水平触发模式
 *         下有效),Nginx主要工作在ET模式下,一般可以忽略flags这个参数
 * 
 * 将读事件添加到事件驱动模块中,这样该事件对应的TCP连接上一旦出现可读事件(如接收到
 * TCP连接另一端发送来的字符流)就会回调该事件的handler方法 
 */
ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
{
    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) 
    {

        /* kqueue, epoll */

        if (!rev->active && !rev->ready) 
        {
            if (ngx_add_event(rev, NGX_READ_EVENT, NGX_CLEAR_EVENT)
                == NGX_ERROR)
            {
                return NGX_ERROR;
            }
        }

        return NGX_OK;

    } 
    else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) 
    {

        /* select, poll, /dev/poll */

        if (!rev->active && !rev->ready) 
        {
            if (ngx_add_event(rev, NGX_READ_EVENT, NGX_LEVEL_EVENT)
                == NGX_ERROR)
            {
                return NGX_ERROR;
            }

            return NGX_OK;
        }

        if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) 
        {
            if (ngx_del_event(rev, NGX_READ_EVENT, NGX_LEVEL_EVENT | flags)
                == NGX_ERROR)
            {
                return NGX_ERROR;
            }

            return NGX_OK;
        }

    } 
    else if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) 
    {

        /* event ports */

        if (!rev->active && !rev->ready) 
        {
            if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) 
            {
                return NGX_ERROR;
            }

            return NGX_OK;
        }

        if (rev->oneshot && !rev->ready) 
        {
            if (ngx_del_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR)
            {
                return NGX_ERROR;
            }

            return NGX_OK;
        }
    }

    /* iocp */

    return NGX_OK;
}

ngx_handle_write_event()

/*
 * @wev: 要操作的写事件
 * @lowat: 表示当连接对应的套接字缓冲区中必须有lowat大小的可用空间时,事件收集器(如select或者
 *         epoll_wait调用)才能处理这个可写事件(lowat为0表示不考虑可写缓冲区的大小)
 */
ngx_int_t ngx_handle_write_event(ngx_event_t *wev, size_t lowat)
{
    ngx_connection_t  *c;

    if (lowat) 
    {
        c = wev->data;

        if (ngx_send_lowat(c, lowat) == NGX_ERROR) 
        {
            return NGX_ERROR;
        }
    }

    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) 
    {

        /* kqueue, epoll */

        if (!wev->active && !wev->ready) 
        {
            if (ngx_add_event(wev, NGX_WRITE_EVENT,
                              NGX_CLEAR_EVENT | (lowat ? NGX_LOWAT_EVENT : 0))
                == NGX_ERROR)
            {
                return NGX_ERROR;
            }
        }

        return NGX_OK;

    } 
    else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) 
    {

        /* select, poll, /dev/poll */

        if (!wev->active && !wev->ready) 
        {
            if (ngx_add_event(wev, NGX_WRITE_EVENT, NGX_LEVEL_EVENT)
                == NGX_ERROR)
            {
                return NGX_ERROR;
            }

            return NGX_OK;
        }

        if (wev->active && wev->ready) 
        {
            if (ngx_del_event(wev, NGX_WRITE_EVENT, NGX_LEVEL_EVENT)
                == NGX_ERROR)
            {
                return NGX_ERROR;
            }

            return NGX_OK;
        }

    } 
    else if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) 
    {

        /* event ports */

        if (!wev->active && !wev->ready) 
        {
            if (ngx_add_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR)
            {
                return NGX_ERROR;
            }

            return NGX_OK;
        }

        if (wev->oneshot && wev->ready) 
        {
            if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) 
            {
                return NGX_ERROR;
            }

            return NGX_OK;
        }
    }

    /* iocp */

    return NGX_OK;
}

3. Nginx连接的定义

3.1 被动连接

struct ngx_connection_s {
    /* 连接未使用时,data成员用于充当连接池中空闲连接表中的next指针。当连接被使用时,data的意义
     * 由使用它的Nginx模块而定,如在HTTP框架中,data指向ngx_http_request_t请求 */
    void               *data;
    
    /* 连接对应的读事件 */
    ngx_event_t        *read;
    /* 连接对应的写事件 */
    ngx_event_t        *write;

    /* 套接字句柄 */
    ngx_socket_t        fd;

    /* 直接接收网络字符流的方法 */
    ngx_recv_pt         recv;
    /* 直接发送网络字符流的方法 */
    ngx_send_pt         send;
    /* 以ngx_chain_t链表为参数来接收网络字符流的方法 */
    ngx_recv_chain_pt   recv_chain;
    /* 以ngx_chain_t链表为参数来发送网络字符流的方法 */
    ngx_send_chain_pt   send_chain;

    /* 这个连接对应的ngx_listening_t监听对象,此连接由listening监听端口的事件建立 */
    ngx_listening_t    *listening;

    /* 这个连接已经发送出去的字节数 */
    off_t               sent;

    ngx_log_t          *log;

    /* 内存池。一般在accept一个新连接时,会创建一个内存池,而在这个连接结束时会销毁内存池。注意,
     * 这里所说的连接是指成功建立的TCP连接,所有的ngx_connection_t结构体都是预分配的。这个内存池
     * 的大小将由上面的listening监听对象中的pool_size成员决定 */
    ngx_pool_t         *pool;

    int                 type;

    /* 连接客户端的sockaddr结构体 */
    struct sockaddr    *sockaddr;
    socklen_t           socklen;
    /* 连接客户端字符串形式的IP地址 */
    ngx_str_t           addr_text;

    ngx_str_t           proxy_protocol_addr;
    in_port_t           proxy_protocol_port;

#if (NGX_SSL || NGX_COMPAT)
    ngx_ssl_connection_t  *ssl;
#endif

    /* 本机的监听端口对应的sockaddr结构体,也就是listening监听对象中的sockaddr成员 */
    struct sockaddr    *local_sockaddr;
    socklen_t           local_socklen;

    /* 用于接收、缓存客户端发来的字符流,每个事件消费模块可自由决定从连接池中分配多大的空间给
     * buffer这个接收缓存字段。例如,在HTTP模块中,它的大小决定于client_header_buffer_size配置项 */
    ngx_buf_t          *buffer;

    /* 该字段用来将当前连接以双向链表元素的形式添加到ngx_cycle_t核心结构体的
     * reuseable_connections_queue双向链表中,表示可重用的连接 */
    ngx_queue_t         queue;

    /* 连接使用次数。ngx_connection_t结构体每次建立一条来自客户端的连接,或者用于主动向后端服务器
     * 发送连接时(ngx_peer_connection_t也使用它),number都会加1 */
    ngx_atomic_uint_t   number;

    /* 处理的请求次数 */
    ngx_uint_t          requests;

    /*
     * 缓存中的业务类型。任何事件消费模块都可以自定义需要的标志位。这个buffered字段有8位,最多可以同时表示
     * 8个不同的业务。第三方模块在自定义buffered标志位时注意不要与可能使用的模块定义的标志位冲突。
     * openssl模块定义了一个标志位:
     * #define NGX_SSL_BUFFERED                   0x01
     * HTTP官方模块定义了以下标志位:
     * #define NGX_HTTP_LOWLEVEL_BUFFERED         0xf0
     * #define NGX_HTTP_WRITE_BUFFERED            0x10
     * #define NGX_HTTP_GZIP_BUFFERED             0x20
     * #define NGX_HTTP_SSI_BUFFERED              0x01
     * #define NGX_HTTP_SUB_BUFFERED              0x02
     * #define NGX_HTTP_COPY_BUFFERED             0x04
     * 对于HTTP模块而言,buffered的低4位要慎用,在实际发送响应的ngx_http_write_filter_module
     * 过滤模块中,低4位标志位为1则意味着Nginx会一直认为有HTTP模块还需要处理这个请求,必须等待
     * HTTP模块将低4位全置为0才会正常结束请求。检查低4位的宏如下:
     * #define NGX_HTTP_LOWLEVEL_BUFFERED         0xf0
     */
    unsigned            buffered:8;

    /* 本连接记录日志时的级别,它占用3位,取值范围是0~7,但实际上目前只定义了5个值,由
     * ngx_connection_log_error_e枚举表示,如下:
     * typedef enum {
     * NGX_ERROR_ALERT = 0,
     * NGX_ERROR_ERR,
     * NGX_ERROR_INFO,
     * NGX_ERROR_IGNORE_ECONNRESET,
     * NGX_ERROR_IGNORE_EINVAL
     * } ngx_connection_log_error_e;
     */
    unsigned            log_error:3;     /* ngx_connection_log_error_e */

    /* 标志位,为1时表示连接已经超时 */
    unsigned            timedout:1;
    /* 标志位,为1时表示连接处理过程中出现错误 */
    unsigned            error:1;
    /* 标志位,为1时表示连接已经销毁。这里的连接指的是TCP连接,而不是ngx_connection_t结构体。
     * 当destroyed为1时,ngx_connection_t结构体仍然存在,但其对应的套接字、内存池等已经不可用 */
    unsigned            destroyed:1;

    /* 标志位,为1时表示连接处于空闲状态,如keepalive请求中两次请求之间的状态 */
    unsigned            idle:1;
    /* 标志位,为1时表示连接可重用,它与上面的queue字段是对应使用的 */
    unsigned            reusable:1;
    /* 标志位,为1时表示连接关闭 */
    unsigned            close:1;
    unsigned            shared:1;

    /* 标志位,为1时表示正在将文件中的数据发往连接的另一端 */
    unsigned            sendfile:1;
    /* 标志位,如果为1,则表示只有在链接套接字对应的发送缓冲区必须满足最低设置的大小阈值时,事件
     * 驱动模块才会分发该事件。这与ngx_handle_write_event方法中的lowat参数是对应的 */
    unsigned            sndlowat:1;
    /* 标志位,表示如何使用TCP的nodelay特性。它的取值范围是下面这个枚举类型
     * ngx_connection_tcp_nodelay_e:
     * typedef enum {
     * NGX_TCP_NODELAY_UNSET = 0,
     * NGX_TCP_NODELAY_SET,
     * NGX_TCP_NODELAY_DISABLED
     * } ngx_connection_tcp_nodelay_e;
     */
    unsigned            tcp_nodelay:2;   /* ngx_connection_tcp_nodelay_e */
    /* 标志位,表示如何使用TCP的nopush特性。它的取值范围是下面的这个枚举类型
     * ngx_connection_tcp_nopush_e:
     * typedef enum {
     * NGX_TCP_NOPUSH_UNSET = 0,
     * NGX_TCP_NOPUSH_SET,
     * NGX_TCP_NOPUSH_DISABLED
     * } ngx_connection_tcp_nopush_e;
     */
    unsigned            tcp_nopush:2;    /* ngx_connection_tcp_nopush_e */

    unsigned            need_last_buf:1;

#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT)
    unsigned            busy_count:2;
#endif

#if (NGX_THREADS || NGX_COMPAT)
    ngx_thread_task_t  *sendfile_task;
#endif
};

3.2 主动连接

/* 当使用长连接与上游服务器通信时,可通过该方法由连接池中获取一个新连接 */
typedef ngx_int_t (*ngx_event_get_peer_pt)(ngx_peer_connection_t *pc,
    void *data);
/* 当使用长连接与上游服务器通信时,通过该方法将使用完毕的连接释放给连接池 */
typedef void (*ngx_event_free_peer_pt)(ngx_peer_connection_t *pc, void *data,
    ngx_uint_t state);
    
    
struct ngx_peer_connection_s {
    /* 一个主动连接实际上也需要ngx_connection_t结构体中的大部分成员 */
    ngx_connection_t                *connection;

    /* 远端服务器的sockaddr地址 */
    struct sockaddr                 *sockaddr;
    socklen_t                        socklen;
    /* 远端服务器的名称 */
    ngx_str_t                       *name;

    /* 表示在连接一个远端服务器时,当前连接出现异常失败后可以重试的次数,也就是允许的最多失败次数 */
    ngx_uint_t                       tries;
    ngx_msec_t                       start_time;

    /* 获取连接的方法,如果使用长连接构成的连接池,那么必须要实现get方法 */
    ngx_event_get_peer_pt            get;
    /* 与get方法对应的释放连接的方法 */
    ngx_event_free_peer_pt           free;
    ngx_event_notify_peer_pt         notify;
    /* 这个data指针仅用于和上面的get、free方法配合传递参数,它的具体含义与实现get方法、
     * free方法的模块相关 */
    void                            *data;

#if (NGX_SSL || NGX_COMPAT)
    ngx_event_set_peer_session_pt    set_session;
    ngx_event_save_peer_session_pt   save_session;
#endif

    /* 本机地址信息 */
    ngx_addr_t                      *local;

    int                              type;
    /* 套接字的接收缓冲区大小 */
    int                              rcvbuf;

    ngx_log_t                       *log;

    /* 标志位,为1时表示上面的connection连接已经缓存 */
    unsigned                         cached:1;
    unsigned                         transparent:1;

                                     /* ngx_connection_log_error_e */
    unsigned                         log_error:2;

    NGX_COMPAT_BEGIN(2)
    NGX_COMPAT_END
};

3.3 ngx_connection_t 连接池

Nginx 在接受客户端的连接时,所使用的 ngx_connection_t 结构体都是在启动阶段就预分配好的,使用时从连接池中获
取即可。

从下图可以看出,在ngx_cycle_t中的connections和free_connections这两个成员构成了一个连接池,其中connections
指向整个连接池数组的首部,而free_connections则指向第一个ngx_connection_t空闲连接。所有的空闲连接
ngx_connection_t都以 data 成员作为 next 指针串联成一个单链表,如此,一旦有用户发起连接时就从
free_connections指向的链表头获取一个空闲的连接,同时free_connections再指向下一个空闲连接。而归还连接时只需
把该连接插入到free_connections链表表头即可。

ngx_connection_t连接池示意图

3.3.1 ngx_get_connection:从连接池中获取一个空闲连接

/*
 * 参数含义:
 * - s:是这条连接的套接字句柄
 * - log:记录日志的对象
 *
 * 执行意义:
 * 从连接池中获取一个ngx_connection_t结构体,同时获取相应的读/写事件
 */
ngx_connection_t *ngx_get_connection(ngx_socket_t s, ngx_log_t *log)
{
    ngx_uint_t         instance;
    ngx_event_t       *rev, *wev;
    ngx_connection_t  *c;

    /* disable warning: Win32 SOCKET is u_int while UNIX socket is int */

    if (ngx_cycle->files && (ngx_uint_t) s >= ngx_cycle->files_n) 
    {
        ngx_log_error(NGX_LOG_ALERT, log, 0,
                      "the new socket has number %d, "
                      "but only %ui files are available",
                      s, ngx_cycle->files_n);
        return NULL;
    }

    c = ngx_cycle->free_connections;

    if (c == NULL) 
    {
        /* 从可复用连接队列尾部取出一个连接 */
        ngx_drain_connections((ngx_cycle_t *) ngx_cycle);
        c = ngx_cycle->free_connections;
    }

    if (c == NULL) 
    {
        ngx_log_error(NGX_LOG_ALERT, log, 0,
                      "%ui worker_connections are not enough",
                      ngx_cycle->connection_n);

        return NULL;
    }

    /* 指向下一个空闲连接 */
    ngx_cycle->free_connections = c->data;
    /* 空闲连接数减1 */
    ngx_cycle->free_connection_n--;

    if (ngx_cycle->files && ngx_cycle->files[s] == NULL) 
    {
        ngx_cycle->files[s] = c;
    }

    /* 获取该空闲连接已经预分配好的读/写事件 */
    rev = c->read;
    wev = c->write;

    ngx_memzero(c, sizeof(ngx_connection_t));

    /* 初始化该连接 */
    c->read  = rev;
    c->write = wev;
    c->fd    = s;
    c->log   = log;

    /* 先暂存该值 */
    instance = rev->instance;

    /* 将rev、wev清空 */
    ngx_memzero(rev, sizeof(ngx_event_t));
    ngx_memzero(wev, sizeof(ngx_event_t));

    /* 将instance标志位置为原来的相反值,以便当该连接超时时做出检测 */
    rev->instance = !instance;
    wev->instance = !instance;

    rev->index = NGX_INVALID_INDEX;
    wev->index = NGX_INVALID_INDEX;

    rev->data = c;
    wev->data = c;

    /* 置1,表示wev事件是可写的 */
    wev->write = 1;

    return c;
}

3.3.2 ngx_free_connection: 将该连接池归还给连接池

/*
 * 参数含义:
 * - c:是需要回收的连接
 *
 * 执行意义:
 * 将这个连接回收到连接池中
 */
void ngx_free_connection(ngx_connection_t *c)
{
    /* 将该连接插入到空闲连接链表的表头 */
    c->data = ngx_cycle->free_connections;
    ngx_cycle->free_connections = c;
    ngx_cycle->free_connection_n++;

    if (ngx_cycle->files && ngx_cycle->files[c->fd] == c)
    {
        ngx_cycle->files[c->fd] = NULL;
    }
}
posted @ 2018-04-25 17:34  季末的天堂  阅读(3813)  评论(0编辑  收藏  举报