对于web server来说,必须能够监听到客户端的连接才能与之通信,这篇文章就看一下nginx是如何实现连接的建立。监听到新的连接实际上就是监听socket上的读事件,此时监听socket的已完成连接队列是非空的,可以非阻塞的调用accpet获取新到的连接。在nginx中每个socket都会被封装成一个连接结构,就是ngx_connection_t类型。每个ngx_connection_t结构具有读写事件read和write,它们是ngx_event_t类型的,有一个handler回调函数指针,在发生读写事件时被调用。在监听socket初始化一文中介绍了socket的初始化,主要是获取监听地址、端口等信息。在事件模型中介绍了nginx事件模型的初始化,在ngx_event_process_init函数中为每个监听socket分配连接,并将这些连接的read的handler初始化为ngx_event_accept,也就是说说在监听到连接时会调用,用于初始化连接等,最后将其添加到事件循环中。
在事件循环中,已经介绍过为了防止惊群(新到的一个连接会唤醒所有阻塞的worker进程),只有获取accept锁的worker进程才能accept新的连接,接下来才会去调用ngx_event_accept函数处理。下面就看看具体过程。
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; }初始化event的available属性,表示事件发生。
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);ngx_event_t的data属性是该事件所在的连接,对于监听socket的连接,可以通过listening属性获取对应的监听socket(ngx_listening_t)。接下来的while循环用于迭代ev->available次数,获取对应的连接。在while循环一开始的部分就是调用accept获取连接socket。
ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;在介绍 事件模型时,提到过ngx_accept_disabled主要用于实现worker进程的简单的负载均衡,在一个worker进程的空闲连接的个数小于连接池大小的1/8时,该进程会放弃竞争accept锁,ngx_accept_disabled在ngx_process_events_and_timers函数中使用。
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; }s是连接socket的文件描述符,从连接池中获取一个连接并分配给该socket。
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 = ngx_palloc(c->pool, sizeof(ngx_log_t)); if (log == NULL) { ngx_close_accepted_connection(c); return; }连接的sockaddr存放客户端socket地址,这里为其分配空间。然后为连接的log结构分配空间。
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; } } }在使用aio时采用阻塞方式,其他模式(epoll、select等)使用非阻塞。
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->unexpected_eof = 1;对新建的连接的一些属性初始化,包括接收、发送的函数,日志、监听socket等。接下来,初始化连接的事件部分。
rev = c->read; wev = c->write; wev->ready = 1; 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; #if (NGX_HAVE_KQUEUE) rev->available = 1; #endif } rev->log = log; wev->log = log;设置读写事件的ready属性,该属性表示该事件已经就绪,可以被触发。
c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);ngx_connection_counter是nginx的连接计数器,防止共享内存中,初始化是在event module的module init回调函数中调用,具体就是ngx_event_module_init函数。连接的number字段很显然表示该连接的序号。
if (ls->addr_ntop) { c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len); if (c->addr_text.data == NULL) { ngx_close_accepted_connection(c); return; } c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->addr_text.data, ls->addr_text_max_len, 0); if (c->addr_text.len == 0) { ngx_close_accepted_connection(c); return; } }这段代码将二进制的地址转换为文本格式,并赋值给连接的addr_text属性。
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; } }调用ngx_add_conn将新建的连接加入nginx的事件循环。在使用epoll时,实际上会调用ngx_epoll_add_connection函数,最终调用epoll_ctl添加事件,这样后续就会监听到来自该socket的数据。
log->data = NULL; log->handler = NULL; /** * 调用监听socket的初始化函数ngx_http_init_connection(http/ngx_http_request.c),这个handler * 是在ngx_http_add_listening函数中赋值的 */ ls->handler(c);ls是监听socket(ngx_listening_t),handler在accept到新连接时调用,注释已经说的很清楚,就是调用ngx_http_init_connection完成连接的初始化,其中最主要的就是为读事件设置handler,下面看一下这个函数。
ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); if (ctx == NULL) { ngx_http_close_connection(c); return; } ctx->connection = c; ctx->request = NULL; ctx->current_request = NULL; c->log->connection = c->number; c->log->handler = ngx_http_log_error; c->log->data = ctx; c->log->action = "reading client request line"; c->log_error = NGX_ERROR_INFO;初始化log相关属性。
rev = c->read; rev->handler = ngx_http_init_request; c->write->handler = ngx_http_empty_handler;这段代码是最核心的,将读事件的handler设置为ngx_http_init_request,在客户端向服务器发送数据时会被调用,用于初始化并处理客户端请求。在后面介绍ngxin请求处理会详细介绍。
if (rev->ready) { /* the deferred accept(), rtsig, aio, iocp */ if (ngx_use_accept_mutex) { ngx_post_event(rev, &ngx_posted_events); return; } ngx_http_init_request(rev); return; }按照注释的描述这段代码在使用deferred accept、rtsig、aio和iocp时调用,在ngx_event_accept中只有在这几种情况下才会将读事件的ready置为1。
ngx_add_timer(rev, c->listening->post_accept_timeout); /* * 将新建的连接的描述符添加到事件循环 */ if (ngx_handle_read_event(rev, 0) != NGX_OK) { #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_reading, -1); #endif ngx_http_close_connection(c); return; }设置timer,最后将新建的socket描述符添加到事件循环,但是在使用epoll时添加到事件循环的工作已经在ngx_add_conn中完成,所以这个函数不会做任何操作。
上面就是nginx建立连接的处理过程,主要是初始化连接的属性,并设置了读事件的handler,用于处理请求,下面一篇介绍nginx的请求处理。