nginx源码阅读(九).事件及连接的定义、连接池

前言

本小节将分析事件模块中事件的定义以及连接池等相关定义,方便我们后面对事件模块中的模块进行分析。

事件的定义

每个事件由ngx_event_s表示:

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

    /* 这种结构体成员中冒号的作用是位压缩
     * 这是为了节约空间
     * unsigned         write:1代表该变量只占1位的空间
     * 而不是占unsigned int大小
     */

    /* 读事件标识位
     * 为1表示事件可写
     * 即此时可以发送数据
     */
    unsigned         write:1;

    /* 可建立新连接标识位 */
    unsigned         accept:1;

    /* used to detect the stale events in kqueue, rtsig, and epoll */
    /* 用于区分过期事件 */
    unsigned         instance:1;

    /*
     * the event was passed or would be passed to a kernel;
     * in aio mode - operation was posted.
     */
    /* 表示当前事件是否活跃 */
    unsigned         active:1;

    /* 为1表示禁用该事件,在epoll模块中无用 */
    unsigned         disabled:1;

    /* the ready event; in aio mode 0 means that no operation can be posted */
    /* 为1表示当前事件已经准备就绪
     * 即该事件可以被处理
     */
    unsigned         ready:1;

    /* 在epoll模块中无用 */
    unsigned         oneshot:1;

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

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

    /* 为1表示该事件已经超时 */
    unsigned         timedout:1;
    /* 为1表示该事件存在定时器中 */
    unsigned         timer_set:1;

    /* 为1表示该事件会被延迟处理 */
    unsigned         delayed:1;

    /* 该标识位目前未使用 */
    unsigned         read_discarded:1;

    /* 该标识位目前未使用 */
    unsigned         unexpected_eof:1;

    /* 为1表示延迟建立tcp连接
     * 即就算三次握手之后也不立即建立连接
     * 而是等到有数据包发送时才建立
     */
    unsigned         deferred_accept:1;

    /* the pending eof reported by kqueue or in aio chain operation */
    /* 在epoll模块无用 */
    unsigned         pending_eof:1;

#if !(NGX_THREADS)
    /* 为1表示在处理post事件时,当前事件已经准备就绪 */
    unsigned         posted_ready:1;
#endif

#if (NGX_WIN32)
    /* setsockopt(SO_UPDATE_ACCEPT_CONTEXT) was successful */
    unsigned         accept_context_updated:1;
#endif

#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
     *
     * iocp: TODO
     *
     * otherwise:
     *   accept:     1 if accept many, 0 otherwise
     */

#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
    int              available;
#else
    /* 为1表示在epoll事件驱动机制下,一次尽可能多建立tcp连接 */
    unsigned         available:1;
#endif

    /* 该事件发生时的处理方法 */
    ngx_event_handler_pt  handler;


#if (NGX_HAVE_AIO)

#if (NGX_HAVE_IOCP)
    ngx_event_ovlp_t ovlp;
#else
    /* linux aio机制中定义的结构体 */
    struct aiocb     aiocb;
#endif

#endif

    /* epoll模块中未用 */
    ngx_uint_t       index;

    /* 用于记录error_log日志的ngx_log_t对象 */
    ngx_log_t       *log;

    /* 定时器节点 */
    ngx_rbtree_node_t   timer;

    /* 为1表示当前事件已经关闭,epoll模块中未用 */
    unsigned         closed:1;

    /* to test on worker exit */
    /* 当前并没有使用 */
    unsigned         channel:1;
    unsigned         resolver:1;

/* 并没有使用多线程
 * 因此这几个标识位跳过
 */
#if (NGX_THREADS)

    unsigned         locked:1;

    unsigned         posted_ready:1;
    unsigned         posted_timedout:1;
    unsigned         posted_eof:1;

#if (NGX_HAVE_KQUEUE)
    /* the pending errno reported by kqueue */
    int              posted_errno;
#endif

#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
    int              posted_available;
#else
    unsigned         posted_available:1;
#endif

    ngx_atomic_t    *lock;
    ngx_atomic_t    *own_lock;

#endif

    /* the links of the posted queue */
    /* post事件会形成一个队列再一次进行处理
     * 这是一个双向链表
     * next指向下一个事件的地址
     * prev指向上一个事件的地址
     */
    ngx_event_t     *next;
    ngx_event_t    **prev;


#if 0

    /* the threads support */

    /*
     * the event thread context, we store it here
     * if $(CC) does not understand __thread declaration
     * and pthread_getspecific() is too costly
     */

    void            *thr_ctx;

#if (NGX_EVENT_T_PADDING)

    /* event should not cross cache line in SMP */

    uint32_t         padding[NGX_EVENT_T_PADDING];
#endif
#endif
};

以上便是关于事件的定义,nginx的事件不需要创建。

在初始化过程中会读取nginx.conf文件中”events {}”中的worker_connections配置(我默认的nginx.conf中worker_connections的值是1024),即指读/写事件的最大值为1024(同时也代表每个worker进程的最大连接数是1024),然后在ngx_event_process_init中申请读事件和写事件的空间,并初始化读/写事件,完成预分配。

每一个读和写事件都对应了一个连接,每个连接也是预分配好的,还记得ngx_cycle_t中的*connections*read_events*write_events吗,connections即存储所有连接的数组,read_events即存储读事件的数组,write_events即存储写事件的数组。
main起始的调用的流程图:
这里写图片描述
当需要向I/O多路复用机制中注册事件的时候,我们只需要从连接池中取一个空闲的连接出来,该连接对应了读和写事件,而ngx_cycle_t中的*free_connections指向连接池中空闲连接的起始处。将读/写事件添加到I/O多路复用机制中采用的方法是:
ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
ngx_int_t ngx_handler_write_event(ngx_event_t *wev, size_t lowat)
rev参数代表需要进行操作的事件,flags参数指定事件的驱动方式;

wev参数代表需要进行操作的事件,lowat表示只有当连接对应的套接字缓冲区必须至少有lowat大小的可用空间时,该事件才能被epoll处理(lowat为0时不考虑可写缓冲区的大小)

连接的定义

接下来,介绍一下nginx中关于连接的定义。nginx中的连接主要分别主动连接和被动连接两种,被动连接是指客户端发起,而服务端被动接受的连接;主动连接则是指nginx主动发起连接,而其他服务器被动接受的连接。

定义被动连接的结构体在core/ngx_connection.h
- 被动连接:

typedef struct ngx_connection_s   ngx_connection_t;

struct ngx_connection_s {
    //当连接未使用时,data成员用于充当连接池中空闲连接链表中的next指针
    //当该连接被使用时,具体的作用视具体的模块而定
    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对象
    ngx_log_t          *log;

    //内存池,每一个连接在建立之后,都会创建一个内存池(这里的建立是指成功建立tcp连接之后,而不是预分配的时候就建立内存池)
    //在该连接结束时,该内存池会被销毁
    //内存池的大小由listening成员监听对象中的pool_size决定
    ngx_pool_t         *pool;

    //连接客户端的sockaddr结构体
    struct sockaddr    *sockaddr;
    //sockaddr结构体的长度
    socklen_t           socklen;
    //连接客户端的ip地址(以字符串的形式表示)
    ngx_str_t           addr_text;

#if (NGX_SSL)
    ngx_ssl_connection_t  *ssl;
#endif

    //本机上监听端口对应的sockaddr结构体
    //其实也就是指listening监听对象中的sockaddr成员
    struct sockaddr    *local_sockaddr;

    //接受字符流的缓存
    ngx_buf_t          *buffer;

    //该成员将当前连接以双向链表的节点的形式添加到ngx_cycle_t核心结构体的reusable_connections_queue双向链表中
    //表示该连接可重用
    ngx_queue_t         queue;

    //该连接使用的次数(不论是被动连接还是主动连接,只要使用了该连接,number都会加1)
    ngx_atomic_uint_t   number;

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

    //缓存中的业务类型。这里目前略过。
    unsigned            buffered:8;

    /* 表示该连接记录日志时的级别
     * 它只占用了3位,目前所支持的级别如下:
     * typedef enum {
     *      NGX_ERROR_ALERT = 0,
     *      NGX_ERROR_ERR,
     *      NGX_ERROR_INFO,
     *      NGX_ERROR_IGNORE_ECONNRESET,
     *      NGX_ERROR_IGNORE_EINVAL
     * } ngx_connections_log_error_e;
     */
    unsigned            log_error:3;     /* ngx_connection_log_error_e */

    //为1时表示为独立的连接,比如从客户端发生的连接
    unsigned            single_connection:1;
    //为1表示不期待字符流结束,目前无意义
    unsigned            unexpected_eof:1;
    //为1表示连接超时
    unsigned            timedout:1;
    //为1表示连接处理中发生错误
    unsigned            error:1;
    //为1表示对应的tcp连接已经销毁
    unsigned            destroyed:1;

    //为1表示连接处于空闲状态
    unsigned            idle:1;
    //为1表示连接可重用
    unsigned            reusable:1;
    //为1表示连接关闭
    unsigned            close:1;

    //为1表示正在把文件中的数据发往另一端
    unsigned            sendfile:1;
    //为1表示只有当连接套接字对应的发送缓冲区满足最低设置的大小阀值时,该事件才可能被触发
    unsigned            sndlowat:1;
    //表示如何使用tcp的nodelay特性
    unsigned            tcp_nodelay:2;   /* ngx_connection_tcp_nodelay_e */
    //表示如何使用tcp的nopush特性
    unsigned            tcp_nopush:2;    /* ngx_connection_tcp_nopush_e */

#if (NGX_HAVE_IOCP)
    unsigned            accept_context_updated:1;
#endif

#if (NGX_HAVE_AIO_SENDFILE)
    //为1时表示使用异步I/O的方式将磁盘上的文件发送给连接的另一端
    unsigned            aio_sendfile:1;
    //使用异步I/O方式发送的文件,该缓冲区保存待发送文件的信息
    ngx_buf_t          *busy_sendfile;
#endif

#if (NGX_THREADS)
    ngx_atomic_t        lock;
#endif
};

以上便是被动连接的表示,接下来是主动连接(event/ngx_event_connec.h):
- 主动连接

typedef struct ngx_peer_connection_s  ngx_peer_connection_t;

struct ngx_peer_connection_s {
    //主动连接也需要被动连接ngx_connection_t结构体的大部分成员
    ngx_connection_t                *connection;

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

    //连接异常时,允许的重新尝试连接的次数
    ngx_uint_t                       tries;

    //获取连接的方法
    ngx_event_get_peer_pt            get;
    //与get对应的释放连接的方法
    ngx_event_free_peer_pt           free;
    //配合get还有free传递参数
    void                            *data;

#if (NGX_SSL)
    ngx_event_set_peer_session_pt    set_session;
    ngx_event_save_peer_session_pt   save_session;
#endif
#if (NGX_THREADS)
    ngx_atomic_t                    *lock;
#endif

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

    //接受缓冲区大小
    int                              rcvbuf;

    //记录日志的ngx_log_t对象
    ngx_log_t                       *log;

    //为1表示connection连接已经缓存
    unsigned                         cached:1;

                                     /* ngx_connection_log_error_e */
    unsigned                         log_error:2;
};
关于连接池

前面提到过,nginx会预先分配好connectionsread_eventswrite_events这几个数组,并且会预先初始化好。对于连接池来说,当需要连接的时候,分配出去即可,剩下的则是空闲连接,因此ngx_cycle_t核心结构体中有一个free_connections成员专门指向空闲连接的首部,在连接是空闲的时候,使用ngx_connection_t里的data成员将空闲连接连接起来。

下面根据代码来说明(event/ngx_event.c):

......
//给connections数组申请空间
cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
if(cycle->connections == NULL) {
  return NGX_ERROR;
}

c = cycle->connections;

//给读事件数组申请空间
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);
if (cycle->read_events == NULL) {
    return NGX_ERROR;
}

//初始化读事件
rev = cycle->read_events;
for (i = 0; i < cycle->connection_n; i++) {
    rev[i].closed = 1;
    rev[i].instance = 1;
#if (NGX_THREADS)
    rev[i].lock = &c[i].lock;
    rev[i].own_lock = &c[i].lock;
#endif
}

//给写事件数组申请空间
cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
                                cycle->log);
if (cycle->write_events == NULL) {
    return NGX_ERROR;
}

//初始化写事件
wev = cycle->write_events;
for (i = 0; i < cycle->connection_n; i++) {
    wev[i].closed = 1;
#if (NGX_THREADS)
    wev[i].lock = &c[i].lock;
    wev[i].own_lock = &c[i].lock;
#endif
}

i = cycle->connection_n;
next = NULL;

/* 初始化connections数组 */
do {
    i--;

    /* 在初始化阶段,连接数组中所有连接都是空闲的
     * 因此使用data将所有空闲连接连接起来
     */
    c[i].data = next;
    /* read对应read_events中的其中一个读事件 */
    c[i].read = &cycle->read_events[i];
    /* write对应write_events中的其中一个写事件 */
    c[i].write = &cycle->write_events[i];
    //将fd置为-1
    c[i].fd = (ngx_socket_t) -1;

    //由于遍历的顺序是从后往前,因此将next指向当前的connection元素
    next = &c[i];

#if (NGX_THREADS)
    c[i].lock = 0;
#endif
} while (i);

//free_connections指向第一个空闲连接
cycle->free_connections = next;
//free_connection_n即空闲连接的总数
cycle->free_connection_n = cycle->connection_n;

如何将连接、读事件、写事件一一对应起来,这点其实在代码中就已经表达的很清楚了,即通过简单的下标就可以完成一一对应。
这里引用一下《深入理解Nginx》书中的图帮助理解:
这里写图片描述

小结

nginx中关于事件还有连接的定义在本小节中预览了一下,它们有很多状态位,不可能一次就全部记住,在后面的代码分析中再进行理解学习。nginx的连接都是预分配好了的,需要连接时,直接从connections数组中获取即可,用完了之后再归还就行了。并且每个连接都对应了读、写这两种事件,取得连接之后,相应的读、写事件也会被注册到I/O多路复用机制中。

关于connections的元素个数,是由nginx.conf中的connections配置项决定的,我的配置文件中是1024。

下一小节,我们就可以正式进入到ngx_event_core_module模块中了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值