nginx 事件模块(5) - 接受连接

2018-07-27 16:52:51

在前面文章有介绍过 ngx_event_process_init() 函数将监听端口的读事件加入了 epoll 监控,并设置了读事件的回调函数,当有新连接到来时,tcp/udp 分别会使用 ngx_event_accept 或 ngx_event_recvmsg来处理。

// 设置接受连接的回调函数为ngx_event_accept
// 监听端口上收到连接请求时的回调函数,即事件handler
// 从cycle的连接池里获取连接
// 关键操作 ls->handler(c);调用其他模块的业务handler
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
                                                : ngx_event_recvmsg;

这两个函数很像,所以我们只讨论 ngx_event_accept()。

监听端口的事件比较特殊,服务器必须持续监听,所以即使事件超时也不能认为出错,而是要再次加入事件监控。

void
ngx_event_accept(ngx_event_t *ev)
{
    socklen_t          socklen;
    ngx_err_t          err;
    ngx_log_t         *log;
    ngx_uint_t         level;
    ngx_socket_t       s;
    ngx_event_t       *rev, *wev;
    ngx_sockaddr_t     sa;
    ngx_listening_t   *ls;
    ngx_connection_t  *c, *lc;
    ngx_event_conf_t  *ecf;

    static ngx_uint_t  use_accept4 = 1;

    // 事件已经超时
    if (ev->timedout) {
        // 遍历监听端口列表,重新加入epoll连接事件
        if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) {
            return;
        }

        // 清除超时标
        ev->timedout = 0;
    }

    // 得到event core模块的配置,检查是否接受多个连接
    ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);

    // rtsig在nginx 1.9.x已经删除
    if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {
        // epoll是否允许尽可能接受多个请求
        // 这里的ev->available仅使用1个bit的内存空间
        ev->available = ecf->multi_accept;
    }

    // 从事件里获取连接对象
    lc = ev->data;

    // 从连接里获取监听对象
    ls = lc->listening;

    // 开始处理,清除ready标志
    ev->ready = 0;

在 linux 系统下,nginx 使用效率更高的 accept4(),直接获得一个非阻塞的 socket,减少了一次 ioctl() 系统调用。

do {
    socklen = sizeof(ngx_sockaddr_t);

    // 调用accept接受连接,返回socket对象
    // accept4是linux的扩展功能,可以直接把连接的socket设置为非阻塞
    s = accept4(lc->fd, &sa.sockaddr, &socklen, SOCK_NONBLOCK);
        
    // ngx_accept_disabled是总连接数的1/8-空闲连接数
    // 也就是说空闲连接数小于总数的1/8,那么就暂时停止接受连接
    ngx_accept_disabled = ngx_cycle->connection_n / 8
                          - ngx_cycle->free_connection_n;

    // 从全局变量ngx_cycle里获取空闲链接,即free_connections链表
    c = ngx_get_connection(s, ev->log);

    // 1.10连接对象里新的字段,表示连接类型是tcp
    c->type = SOCK_STREAM;

    // 创建连接使用的内存池
    c->pool = ngx_create_pool(ls->pool_size, ev->log);

    // 拷贝客户端sockaddr
    c->sockaddr = ngx_palloc(c->pool, socklen);

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

    log = ngx_palloc(c->pool, sizeof(ngx_log_t));
    *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;
    c->socklen = socklen;
    c->listening = ls;
    c->local_sockaddr = ls->sockaddr;
    c->local_socklen = ls->socklen;

    // 连接相关的读写事件
    rev = c->read;
    wev = c->write;

    // 建立连接后是可写的
    wev->ready = 1;

    // 连接计数器增加,用来标记用的序号
    c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
        // 接受连接,收到请求的回调函数
        // 在http模块里是http.c:ngx_http_init_connection
        ls->handler(c);
    } while (ev->available);
}

现在这个连接对象就代表了与客户端的连接,所以可以调用监听端口的回调函数,正式开始与客户端的通信。对于 http 模块,执行的是 ngx_http_init_connection,对于 stream 模块,执行的是 ngx_stream_init_connection(),分别进入 http 或 stream 框架处理。

如果启用了 multi_accept,那么 ev->available = 1,则会一直循环全部 accept 进来。ngx_accept_disabled 是用来控制负载均衡的。

流程图大致如下


备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/df


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。