Nginx事件模块学习之连接

        本节主要对最近学习的nginx事件模块做一下总结。通过对nginx整体的学习,觉得nginx的事件模块最主要的两个结构 ngx_connection_t,ngx_event_t,ngx_connection_t结构主要存储tcp连接结构, 对应ngx_event_t结构则存储对应位置上连接的读写事件,nginx的事件模块可以配置,linux下默认用内核提供的epoll机制,本节暂不细讨论epoll机制原理。

        对上述两个结构进行了简化,如下:

struct ngx_cycle_s {
	……
    ngx_connection_t         *free_connections;
    ngx_uint_t                free_connection_n;

    ngx_connection_t         *connections;
    ngx_event_t              *read_events;
    ngx_event_t              *write_events;
	……
}

struct ngx_connection_s {
    void               *data; // 空连接指向 next ;非空不同模块指的结构不同
    ngx_event_t        *read;
    ngx_event_t        *write;

    ngx_socket_t        fd;

    ngx_recv_pt         recv;
    ngx_send_pt         send;
	……

    ngx_listening_t    *listening;
	……
};

struct ngx_event_s {
    // 执行该事件所属的连接结构
    void            *data;
	……
};

        结构体之间的大致关系如下图:

Nginx事件模块学习之连接_第1张图片

        每次调用 ngx_get_connection 获取获取新连接时,该连接对应的读、写事件的data指针指向该连接。主要代码如下:

ngx_connection_t * ngx_get_connection(ngx_socket_t s, ngx_log_t *log)
{
	……
    c = ngx_cycle->free_connections;
	
	……
    rev = c->read;
    wev = c->write;

	……
	// 该链接的读、写事件的data指针指向该连接
    rev->data = c;
    wev->data = c;

    return c;
}

        ngx_event_process_init函数主要代码如下:

static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
{
    ……
	// 申请连接对象存储
    cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
    ……

	// 申请连接对象中读事件存储
    cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);
	

	// 申请连接对象中写事件存储
    cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);
    ……

	// 将上述申请的三个变量关联
    do {
        i--;
		
		// 连接数组中的每个(void *)data指针指向数组后一个ngx_connection_t结构的地址
        c[i].data = next;
		
		// 连接数组中的每个(ngx_event_t *)read、write指针指向 cycle->read_events、 cycle->write_events 数组对应位置的结构
        c[i].read = &cycle->read_events[i];
        c[i].write = &cycle->write_events[i];
		
        c[i].fd = (ngx_socket_t) -1;

        next = &c[i];
    } while (i);

	// 由于刚开始申请的连接全部还未被用,都为free状态
    cycle->free_connections = next;
    cycle->free_connection_n = cycle->connection_n;

    // 遍历全部的监听socket
    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) {

		……
        // 为其获取connection连接
		c = ngx_get_connection(ls[i].fd, cycle->log);
		
		……
		rev = c->read;
		
		……
        // 并为其读事件添加回调函数, tcp 为 ngx_event_accept
        rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
                                                : ngx_event_recvmsg;

		……
        // 将监听套接字的读事件调用事件添加函数,添加至epoll中
        if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
            return NGX_ERROR;
        }
}

        上述ngx_event_process_init函数调用栈为:main() -> ngx_master_process_cycle() -> ngx_start_worker_processes() -> ngx_worker_process_cycle() -> ngx_worker_process_init();在ngx_worker_process_init函数中会调用所有模块的 init_process 函数指针,主要代码如下:

ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker)
{
	……
	for (i = 0; cycle->modules[i]; i++)
	{
		if (cycle->modules[i]->init_process)
		{
			if (cycle->modules[i]->init_process(cycle) == NGX_ERROR)
			{
				/* fatal */
				exit(2);
			}
		}
	}
	……
}

        上述了解到监听套接字的连接(ngx_connection_t)处理及回调函数(ngx_event_accept)的设置。

        当进程启动进程后,客户端的http请求nginx监听的端口时会触发监听套接字连接(ngx_connection_t)的读事件,进而调用读事件的handler处理函数(ngx_event_accept函数)去处理。函数主要代码如下:

void ngx_event_accept(ngx_event_t *ev)
{
	……
    lc = ev->data;
    ls = lc->listening;
    ……

    do {
        // 调用accept接收新连接
        s = accept(lc->fd, &sa.sockaddr, &socklen);

		// 新连接申请connection结构
        c = ngx_get_connection(s, ev->log);

        ……
        // 定义处理接收、发送数据的回调函数
        c->recv = ngx_recv;
        c->send = ngx_send;
        ……
		// 调用连接结构connection中listening结构的handler回调方法
        ls->handler(c);
		……
    } while (ev->available);
}

        由上述代码简单可知,当 ngx_event_accept 收到新连接时,最终会调用监听connection中listening结构的handler回调函数。该回调函数其实是在解析nginx配置文件时已被初始化;函数调用栈为: main() -> ngx_init_cycle() -> ngx_conf_parse() -> ngx_conf_handler();主要代码如下:

static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
    ……
	// 调用对应命令的set函数
    rv = cmd->set(cf, cmd, conf);
	
	……
}

        当解析到“http”关键字时,会调用对应的set回调函数:ngx_http_block。后续的函数栈调用为:ngx_http_block() -> ngx_http_optimize_servers() -> ngx_http_init_listening() -> ngx_http_add_listening。主要代码如下:

static ngx_listening_t * ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
{
	……
	
    ls = ngx_create_listening(cf, addr->opt.sockaddr, addr->opt.socklen);
    ……
	
	// 初始化 listen 结构的 handler 指针
    ls->handler = ngx_http_init_connection;

	……
}

       因此,当有客户端的新连接时会调用listen结构handler函数指针指向的函数: ngx_http_init_connection,该函数主要代码如下:

void ngx_http_init_connection(ngx_connection_t *c)
{
	// 申请 ngx_http_connection_t 结构
    hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
    c->data = hc;

    ……
	// 设置读、写事件的回调函数
    rev = c->read;
    rev->handler = ngx_http_wait_request_handler;
    c->write->handler = ngx_http_empty_handler;
	……
}

       ngx_http_init_connection() 函数主要设置了连接的读写事件的回调函数,当在该连接上接收或者发送数据时会调用回调。该函数主要代码如下:

static void ngx_http_wait_request_handler(ngx_event_t *rev)
{
	……
    c = rev->data;
	
	……
  
	// 调用c连接的revc回调函数接收数据,c->recv函数指针在ngx_event_accept函数值已被初始化
	// 根据系统不同调用不同函数,linux系统会调用ngx_unix_recv函数(封装了recv函数)
    n = c->recv(c, b->last, size);

	// 后续可以处理接收到的数据
    ……
}

        当一个客户端连接成功后,发送请求数据时,nginx会调用ngx_http_wait_request_handler() 函数接收数据。       

        而epoll机制是当内核发现有事件发生时调用对应事件的handler回调函数;主要实现函数ngx_epoll_process_events 的代码如下:

static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
    ……
    events = epoll_wait(ep, event_list, (int) nevents, timer);

	……
	// 遍历触发的事件
    for (i = 0; i < events; i++) {
	
        c = event_list[i].data.ptr;

		……
        rev = c->read;


        if ((revents & EPOLLIN) && rev->active) {
			……
			
			// 调用事件的handler回调函数
            rev->handler(rev);
			……
        }

     ……
	 }
}

        总结:nginx的ngx_connection_t*连接结构里面有两类连接,一类是:监听套接字的连接,该连接的回调函数为ngx_event_accept(); 另一类是:处理客户端数据的连接,该连接的回调函数为:ngx_http_wait_request_handler()。linux系统epoll机制监听所有的连接;当连接上有事件触发时调用该事件对应的回调函数。

你可能感兴趣的:(Nginx,nginx,事件模块)