【Nginx】架构原理分析

Nginx架构原理分析

文章目录


一、Nginx架构分析


Nginx目录结构

在这里插入图片描述

  • Nginx的源码主要分布在src/目录下,而src/目录下主要包含三部分比较重要的模块。

  • core:包含了Nginx的最基础的库和框架。包括了内存池、链表、hashmap、String等常用的数据结构。

  • event:事件模块。Nginx自己实现了事件模型。而我们所熟悉的Memcached是使用了Libevent的事件库。自己实现event会性能和效率方便更加高效。

  • http:实现HTTP的模块。实现了HTTP的具体协议的各种模块,该部分内容量比较大。


Nginx进程结构

Nginx是一款多进程的软件。Nginx启动后,会产生一个master进程和N个工作进程。

  • 其中nginx.conf中可以配置工作进程的个数:
worker_processes  1;

多进程模块有一个非常大的好处,就是不需要太多考虑并发锁的问题。

我们常见的软件Memcached就和Nginx相反,就是典型的多线程模型的c语言软件。

在这里插入图片描述


Nginx架构图

在这里插入图片描述


Nginx模块设计

在这里插入图片描述


二、Nginx基础数据结构


1、内存池ngx_pool

  • Nginx的内存管理是通过内存池来实现的。
  • Nginx的内存池的设计非常的精巧,很多场景下,我们可以将Nginx的内存池实现抽象出来改造成我们开发中的内存池。

一般我们使用malloc/alloc/free等函数来分配和释放内存。
但是直接使用这些函数会有一些弊端:

虽然系统自带的ptmalloc内存分配管理器,也有自己的内存优化管理方案(申请内存块以及将内存交还给系统都有自己的优化方案,具体可以研究一下ptmalloc的源码),但是直接使用malloc/alloc/free,仍然会导致内存分配的性能比较低。

频繁使用这些函数分配和释放内存,会导致内存碎片,不容易让系统直接回收内存。典型的例子就是大并发频繁分配和回收内存,会导致进程的内存产生碎片,并且不会立马被系统回收。容易产生内存泄露。

使用内存池分配内存有几点好处:

提升内存分配效率。不需要每次分配内存都执行malloc/alloc等函数。
让内存的管理变得更加简单。内存的分配都会在一块大的内存上,回收的时候只需要回收大块内存就能将所有的内存回收,防止了内存管理混乱和内存泄露问题。


数据结构定义


ngx_pool_s 内存池主结构
  • 数据区域、
  • 最大可分配内存、
  • 内存池地址指针、
  • 缓冲区链表、
  • 存储大数据的链表、
  • 可自定义的清除内存分配的内存回调函数
/**
 * Nginx 内存池数据结构
 */
struct ngx_pool_s {
    ngx_pool_data_t       d; 		/* 内存池的数据区域*/
    size_t                max; 		/* 最大每次可分配内存 */
    ngx_pool_t           *current;  /* 指向当前的内存池指针地址。ngx_pool_t链表上最后一个缓存池结构*/
    ngx_chain_t          *chain;	/* 缓冲区链表 */
    ngx_pool_large_t     *large;    /* 存储大数据的链表 */
    ngx_pool_cleanup_t   *cleanup;  /* 可自定义回调函数,清除内存块分配的内存 */
    ngx_log_t            *log;      /* 日志 */
};
ngx_pool_data_t 数据区域结构
  • 未使用内存的头节点地址
  • 内存池结束地址
  • 下一个内存池的地址(链表)
  • 失败次数
typedef struct {
    u_char               *last;  /* 内存池中未使用内存的开始节点地址 */
    u_char               *end;   /* 内存池的结束地址 */
    ngx_pool_t           *next;  /* 指向下一个内存池 */
    ngx_uint_t            failed;/* 失败次数 */
} ngx_pool_data_t;
ngx_pool_large_s 大数据块结构
  • 指向下一个大数据存储地址
  • 数据块指针地址
typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;   /* 指向下一个大数据存储地址 通过这个地址可以知道当前块长度 */
    void                 *alloc;  /* 数据块指针地址 */
};
ngx_pool_cleanup_s 自定义清理回调的数据结构
struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;  /* 清理的回调函数 */
    void                 *data; 	/* 指向存储的数据地址 */
    ngx_pool_cleanup_t   *next; 	/* 下一个ngx_pool_cleanup_t */
};

数据结构图

  • 说明:

  • Nginx的内存池会放在ngx_pool_t的数据结构上(ngx_pool_data_t用于记录内存块block的可用地址空间和内存块尾部)。当初始化分配的内存块大小不能满足需求的时候,Nginx就会调用ngx_palloc_block函数来分配一个新的内存块,通过链表的形式连接起来。

  • 当申请的内存大于pool->max的值的时候,Nginx就会单独分配一块large的内存块,会放置在pool->large的链表结构上。

  • pool->cleanup的链表结构主要存放需要通过回调函数清理的内存数据。(例如文件描述符)
    在这里插入图片描述


具体函数实现


内存分配 ngx_alloc和ngx_calloc
  • ngx_alloc和ngx_calloc 主要封装了Nginx的内存分配函数malloc。
/**
 * 封装了malloc函数,并且添加了日志
 */
void *
ngx_alloc(size_t size, ngx_log_t *log)
{
    void  *p;
    //分配一块内存
    p = malloc(size);
    if (p == NULL) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "malloc(%uz) failed", size);
    }
 
    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
 
    return p;
}
 
/**
 * 调用ngx_alloc方法,如果分配成,则调用ngx_memzero方法,将内存块设置为0
 * #define ngx_memzero(buf, n)  (void) memset(buf, 0, n)
 */
void *
ngx_calloc(size_t size, ngx_log_t *log)
{
    void  *p;
 
    //调用内存分配函数
    p = ngx_alloc(size, log);
 
    if (p) {
    	//将内存块全部设置为0
        ngx_memzero(p, size);
    }
 
    return p;
}



创建内存池ngx_create_pool
  • 调用ngx_create_pool这个方法就可以创建一个内存池。
/**
 * 创建一个内存池
 */
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log) {
	ngx_pool_t *p;
 
	/**
	 * 相当于分配一块内存 ngx_alloc(size, log)
	 */
	p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
	if (p == NULL) {
		return NULL;
	}
 
	/**
	 * Nginx会分配一块大内存,其中内存头部存放ngx_pool_t本身内存池的数据结构
	 * ngx_pool_data_t	p->d 存放内存池的数据部分(适合小于p->max的内存块存储)
	 * p->large 存放大内存块列表
	 * p->cleanup 存放可以被回调函数清理的内存块(该内存块不一定会在内存池上面分配)
	 */
	p->d.last = (u_char *) p + sizeof(ngx_pool_t); //内存开始地址,指向ngx_pool_t结构体之后数据取起始位置
	p->d.end = (u_char *) p + size; //内存结束地址
	p->d.next = NULL; //下一个ngx_pool_t 内存池地址
	p->d.failed = 0; //失败次数
 
	size = size - sizeof(ngx_pool_t);
	p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
 
	/* 只有缓存池的父节点,才会用到下面的这些  ,子节点只挂载在p->d.next,并且只负责p->d的数据内容*/
	p->current = p;
	p->chain = NULL;
	p->large = NULL;
	p->cleanup = NULL;
	p->log = log;
 
	return p;
}



销毁内存池ngx_destroy_pool
  • 即链表内存的挨个释放
/**
 * 销毁内存池。
 */
void ngx_destroy_pool(ngx_pool_t *pool) {
	ngx_pool_t *p, *n;
	ngx_pool_large_t *l;
	ngx_pool_cleanup_t *c;
 
	/* 首先清理pool->cleanup链表 */
	for (c = pool->cleanup; c; c = c->next) {
		/* handler 为一个清理的回调函数 */
		if (c->handler) {
			ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
					"run cleanup: %p", c);
			c->handler(c->data);
		}
	}
 
	/* 清理pool->large链表(pool->large为单独的大数据内存块)  */
	for (l = pool->large; l; l = l->next) {
 
		ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
 
		if (l->alloc) {
			ngx_free(l->alloc);
		}
	}
 
#if (NGX_DEBUG)
 
	/*
	 * we could allocate the pool->log from this pool
	 * so we cannot use this log while free()ing the pool
	 */
 
	for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
		ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
				"free: %p, unused: %uz", p, p->d.end - p->d.last);
 
		if (n == NULL) {
			break;
		}
	}
 
#endif
 
	/* 对内存池的data数据区域进行释放 */
	for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
		ngx_free(p);
 
		if (n == NULL) {
			break;
		}
	}
}



重设内存池ngx_reset_pool
  • **对于内存池数据区域:**不抹去内存数据,相当于直接在后原来的数据后面新建
  • 对于大数据区域:挨个进行free,等同于清空
/**
 * 重设内存池
 */
void ngx_reset_pool(ngx_pool_t *pool) {
	ngx_pool_t *p;
	ngx_pool_large_t *l;
 
	/* 清理pool->large链表(pool->large为单独的大数据内存块)  */
	for (l = pool->large; l; l = l->next) {
		if (l->alloc) {
			ngx_free(l->alloc);
		}
	}
 
	pool->large = NULL;
 
	/* 循环重新设置内存池data区域的 p->d.last;data区域数据并不擦除*/
	for (p = pool; p; p = p->d.next) {
		p->d.last = (u_char *) p + sizeof(ngx_pool_t);
	}
}



使用内存池分配一块内存ngx_palloc和ngx_pnalloc
  • 内存分配逻辑:

  • 分配一块内存,如果分配的内存size小于内存池的pool->max的限制,则属于小内存块分配,走小内存块分配逻辑;否则走大内存分配逻辑;

  • 块内存的对齐和非对齐操作,一些硬件读取结构体的时候只能挨个读取,非对齐的内存块会影响读取性能。

  • 小内存分配逻辑:循环读取pool->d上的内存块,是否有足够的空间容纳需要分配的size,如果可以容纳,则直接分配内存;否则内存池需要申请新的内存块,调用ngx_palloc_block。

  • 大内存分配逻辑:当分配的内存size大于内存池的pool->max的限制,则会直接调用ngx_palloc_large方法申请一块独立的内存块,并且将内存块挂载到pool->large的链表上进行统一管理。

/**
 * 内存池分配一块内存,返回void类型指针
 */
void *
ngx_palloc(ngx_pool_t *pool, size_t size) {
	u_char *m;
	ngx_pool_t *p;
 
	/* 判断每次分配的内存大小,如果超出pool->max的限制,则需要走大数据内存分配策略 */
	if (size <= pool->max) {
 
		p = pool->current;
 
		/*
		 * 循环读取缓存池链p->d.next的各个的ngx_pool_t节点,
		 * 如果剩余的空间可以容纳size,则返回指针地址
		 *
		 * 这边的循环,实际上最多只有4次,具体可以看ngx_palloc_block函数
		 * */
		do {
			/* 对齐操作,会损失内存,但是提高内存使用速度 */
			m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
 
			if ((size_t)(p->d.end - m) >= size) {
				p->d.last = m + size;
 
				return m;
			}
 
			p = p->d.next;
 
		} while (p);
 
		/* 如果没有缓存池空间没有可以容纳大小为size的内存块,则需要重新申请一个缓存池pool节点 */
		return ngx_palloc_block(pool, size);
	}
 
	/* 走大数据分配策略 ,在pool->large链表上分配 */
	return ngx_palloc_large(pool, size);
}
 
/**
 * 内存池分配一块内存,返回void类型指针
 * 不考虑对齐情况
 */
void *
ngx_pnalloc(ngx_pool_t *pool, size_t size) {
	u_char *m;
	ngx_pool_t *p;
 
	/* 判断每次分配的内存大小,如果超出pool->max的限制,则需要走大数据内存分配策略 */
	if (size <= pool->max) {
 
		p = pool->current;
 
		/* 循环读取数据区域的各个ngx_pool_t缓存池链,如果剩余的空间可以容纳size,则返回指针地址*/
		do {
			m = p->d.last; //分配的内存块的地址
 
			if ((size_t)(p->d.end - m) >= size) {
				p->d.last = m + size;
 
				return m;
			}
 
			p = p->d.next;
 
		} while (p);
 
		/* 如果没有缓存池空间没有可以容纳大小为size的内存块,则需要重新申请一个缓存池*/
		return ngx_palloc_block(pool, size);
	}
 
	/* 走大数据分配策略 */
	return ngx_palloc_large(pool, size);
}



内存池扩容ngx_palloc_block
标准数据池扩容
/**
 * 申请一个新的缓存池 ngx_pool_t
 * 新的缓存池会挂载在主缓存池的 数据区域 (pool->d->next)
 */
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size) {
	u_char *m;
	size_t psize;
	ngx_pool_t *p, *new, *current;
 
	psize = (size_t)(pool->d.end - (u_char *) pool);
 
	/* 申请新的块 */
	m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
	if (m == NULL) {
		return NULL;
	}
 
	new = (ngx_pool_t *) m;
 
	new->d.end = m + psize;
	new->d.next = NULL;
	new->d.failed = 0;
 
	/* 分配size大小的内存块,返回m指针地址 */
	m += sizeof(ngx_pool_data_t);
	m = ngx_align_ptr(m, NGX_ALIGNMENT);
	new->d.last = m + size;
 
	current = pool->current;
 
	/**
	 * 缓存池的pool数据结构会挂载子节点的ngx_pool_t数据结构
	 * 子节点的ngx_pool_t数据结构中只用到pool->d的结构,只保存数据
	 * 每添加一个子节点,p->d.failed就会+1,当添加超过4个子节点的时候,
	 * pool->current会指向到最新的子节点地址
	 *
	 * 这个逻辑主要是为了防止pool上的子节点过多,导致每次ngx_palloc循环pool->d.next链表
	 * 将pool->current设置成最新的子节点之后,每次最大循环4次,不会去遍历整个缓存池链表
	 */
	for (p = current; p->d.next; p = p->d.next) {
		if (p->d.failed++ > 4) {
			current = p->d.next;
		}
	}
 
	p->d.next = new;
 
	/* 最终这个还是没变 */
	pool->current = current ? current : new;
 
	return m;
}


大数据块扩容
  • 分配一块大内存,挂载到pool->large链表上ngx_palloc_large:
/**
 * 当分配的内存块大小超出pool->max限制的时候,需要分配在pool->large上
 */
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size) {
	void *p;
	ngx_uint_t n;
	ngx_pool_large_t *large;
 
	/* 分配一块新的大内存块 */
	p = ngx_alloc(size, pool->log);
	if (p == NULL) {
		return NULL;
	}
 
	n = 0;
 
	/* 去pool->large链表上查询是否有NULL的,只在链表上往下查询3次,主要判断大数据块是否有被释放的,如果没有则只能跳出*/
	for (large = pool->large; large; large = large->next) {
		if (large->alloc == NULL) {
			large->alloc = p;
			return p;
		}
 
		if (n++ > 3) {
			break;
		}
	}
 
	/* 分配一个ngx_pool_large_t 数据结构 */
	large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
	if (large == NULL) {
		ngx_free(p); //如果分配失败,删除内存块
		return NULL;
	}
 
	large->alloc = p;
	large->next = pool->large;
	pool->large = large;
 
	return p;
}


大内存块的释放ngx_pfree
  • 内存池释放需要走ngx_destroy_pool,独立大内存块在reset的时候要单独释放,可以走ngx_pfree方法。
/**
 * 大内存块释放  pool->large
 */
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p) {
	ngx_pool_large_t *l;
 
	/* 在pool->large链上循环搜索,并且只释放内容区域,不释放ngx_pool_large_t数据结构*/
	for (l = pool->large; l; l = l->next) {
		if (p == l->alloc) {
			ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
					"free: %p", l->alloc);
			ngx_free(l->alloc);
			l->alloc = NULL;
 
			return NGX_OK;
		}
	}
 
	return NGX_DECLINED;
}


回调函数内存清理cleanup机制

Nginx的内存池cleanup机制,设计的非常巧妙。pool->cleanup本身是一个链表,每个ngx_pool_cleanup_t的数据结构上,保存着内存数据的本身cleanup->data和回调清理函数cleanup->handler

通过cleanup的机制,我们就可以在内存池上保存例如文件句柄fd的资源。当我们调用ngx_destroy_pool方法销毁内存池的时候,首先会来清理pool->cleanup,并且都会执行c->handler(c->data)回调函数,用于清理资源。

Nginx的这个机制,最显著的就是让文件描述符和需要自定义清理的数据的管理变得更加简单。

分配一个cleanup结构:

/**
 * 分配一个可以用于回调函数清理内存块的内存
 * 内存块仍旧在p->d或p->large上
 *
 * ngx_pool_t中的cleanup字段管理着一个特殊的链表,该链表的每一项都记录着一个特殊的需要释放的资源。
 * 对于这个链表中每个节点所包含的资源如何去释放,是自说明的。这也就提供了非常大的灵活性。
 * 意味着,ngx_pool_t不仅仅可以管理内存,通过这个机制,也可以管理任何需要释放的资源,
 * 例如,关闭文件,或者删除文件等等的。下面我们看一下这个链表每个节点的类型
 *
 * 一般分两种情况:
 * 1. 文件描述符
 * 2. 外部自定义回调函数可以来清理内存
 */
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size) {
	ngx_pool_cleanup_t *c;
 
	/* 分配一个ngx_pool_cleanup_t */
	c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
	if (c == NULL) {
		return NULL;
	}
 
	/* 如果size !=0 从pool->d或pool->large分配一个内存块 */
	if (size) {
		/*  */
		c->data = ngx_palloc(p, size);
		if (c->data == NULL) {
			return NULL;
		}
 
	} else {
		c->data = NULL;
	}
 
	/* handler为回调函数 */
	c->handler = NULL;
	c->next = p->cleanup;
 
	p->cleanup = c;
 
	ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
 
	return c;
}


手动清理 p->cleanup链表上的数据:(内存池销毁函数ngx_destroy_pool也会清理p->cleanup)

/**
 * 清除 p->cleanup链表上的内存块(主要是文件描述符)
 * 回调函数:ngx_pool_cleanup_file
 */
void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd) {
	ngx_pool_cleanup_t *c;
	ngx_pool_cleanup_file_t *cf;
 
	for (c = p->cleanup; c; c = c->next) {
		if (c->handler == ngx_pool_cleanup_file) {
 
			cf = c->data;
 
			if (cf->fd == fd) {
				c->handler(cf); /* 调用回调函数 */
				c->handler = NULL;
				return;
			}
		}
	}
}


关闭文件的回调函数和删除文件的回调函数。这个是文件句柄通用的回调函数,可以放置在p->cleanup->handler上。

/**
 * 关闭文件回调函数
 * ngx_pool_run_cleanup_file方法执行的时候,用了此函数作为回调函数的,都会被清理
 */
void ngx_pool_cleanup_file(void *data) {
	ngx_pool_cleanup_file_t *c = data;
 
	ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
			c->fd);
 
	if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
		ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
				ngx_close_file_n " \"%s\" failed", c->name);
	}
}


/**
 * 删除文件回调函数
 */
void ngx_pool_delete_file(void *data) {
	ngx_pool_cleanup_file_t *c = data;
 
	ngx_err_t err;
 
	ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
			c->fd, c->name);
 
	if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
		err = ngx_errno;
 
		if (err != NGX_ENOENT) {
			ngx_log_error(NGX_LOG_CRIT, c->log, err,
					ngx_delete_file_n " \"%s\" failed", c->name);
		}
	}
 
	if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
		ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
				ngx_close_file_n " \"%s\" failed", c->name);
	}
}



2、数组结构ngx_array

  • Nginx的内存管理都是围绕内存池来实现的,包括array数组类型也是基于Nginx的pool来实现数据结构。
  • Nginx的Array结构设计得非常小巧,主要用于存储小块内存。Nginx的数组每个元素的大小是固定的。

数据结构定义


ngx_array_t 数组的基础数据结构
/* 数组Array数据结构 */
typedef struct {
    void        *elts; 		/* 指向数组第一个元素指针*/
    ngx_uint_t   nelts; 	/* 未使用元素的索引*/
    size_t       size; 		/* 每个元素的大小,元素大小固定*/
    ngx_uint_t   nalloc;	/* 分配多少个元素 */
    ngx_pool_t  *pool;  	/* 内存池*/
} ngx_array_t;


  • *elts:指向数组的第一个元素的指针地址
  • nelts:未使用的元素的计数器
  • size:每个元素的大小,元素大小是固定的。
  • nalloc:一共分配了多少个元素。如果元素不够用,Nginx会数组会进行自动扩容
  • *pool:数组的数据结构ngx_array_t和元素所需要的内存都会分配在pool内存池上。

数据结构图


  • Nginx的数组只存储比较小的数据
  • 数组的元素长度在创建数组的时候就固定死了。但是数组个数,会自动扩容。
  • 数组的数据结构和元素内存都会分配在Nginx的pool内存池上。
  • 数组回收会去检查pool内存池,看是否可以将数组内存交还给内存池。
    在这里插入图片描述

具体函数实现


创建数组和初始化 ngx_array_create

可以定义创建多少个数组元素,并且可以定义每个元素的size,初始化数组。

/**
 * 初始化一个数组
 * p:内存池容器
 * n:支持多少个数组元素
 * size:每个元素的大小
 */
ngx_array_t *
ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size)
{
    ngx_array_t *a;
 
    /* 在内存池 pool上面 分配一段内存给 ngx_array数据结构*/
    a = ngx_palloc(p, sizeof(ngx_array_t));
    if (a == NULL) {
        return NULL;
    }
 
    /**
     *  数组初始化,并且分配内存空间给数组元素
     *  PS:这边数组的数据结构和数组元素的存储分成了两次在pool上分配,笔者认为可以一次进行分配
     *  但是Nginx是多进程的,程序执行流程是线性的,所以分两次分配也无伤大雅。
     */
    if (ngx_array_init(a, p, n, size) != NGX_OK) {
        return NULL;
    }
 
    return a;
}


初始化

static ngx_inline ngx_int_t /*typeof 前的 inline long*/
ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
    /*
     * set "array->nelts" before "array->elts", otherwise MSVC thinks
     * that "array->nelts" may be used without having been initialized
     */

    array->nelts = 0;
    array->size = size;
    array->nalloc = n;
    array->pool = pool;

    array->elts = ngx_palloc(pool, n * size);
    if (array->elts == NULL) {
        return NGX_ERROR;
    }

    return NGX_OK;
}
数组销毁 ngx_array_destroy
  • 首先判断如果数组的数据的末尾地址在可用内存池的开始,那么直接将内存池的last指针指向数据的首地址
  • 然后判断如果数组的数据结构的末尾地址在可用内存池的开始,那么直接将内存池的last指针指向数据结构的首地址
/**
 * 数组销毁
 * 数组销毁设计的也挺讲究的,会去帮助清除内存池上的内存
 */
void
ngx_array_destroy(ngx_array_t *a)
{
    ngx_pool_t  *p;
 
    p = a->pool;
 
    /**
     * PS:你估计比较奇怪,为何数组的内存空间一定会分配在内存池(pool->d存储小内存)上面
     * 如果比较大的内存块不是会存储在内存池的pool->large上面吗?
     * 当我们全局搜索Nginx代码中ngx_array_create方法的时候发现,Nginx的数组都是比较小的,存储的数据量也
     * 并不是很大。所以ngx_array_t适合存储小块的内存。
     */
 
    /**
     * 如果数组元素的末尾地址和内存池pool的可用开始的地址相同
     * 则将内存池pool->d.last移动到数组元素的开始地址,相当于清除当前数组的内容
     */
    if ((u_char *) a->elts + a->size * a->nalloc == p->d.last) {
        p->d.last -= a->size * a->nalloc;
    }
 
    /**
     * 如果数组的数据结构ngx_array_t的末尾地址和内存池pool的可用开始地址相同
     * 则将内存池pool->d.last移动到数组元素的开始地址,相当于清除当前数组的内容
     */
    if ((u_char *) a + sizeof(ngx_array_t) == p->d.last) {
        p->d.last = (u_char *) a;
    }
}


添加一个元素 ngx_array_push
/**
 * 添加一个元素
 */
void *
ngx_array_push(ngx_array_t *a)
{
    void        *elt, *new;
    size_t       size;
    ngx_pool_t  *p;
 
    /* 如果数组中元素都用完了 ,则需要对数组进行扩容 */
    if (a->nelts == a->nalloc) {
 
        /* the array is full */
 
        size = a->size * a->nalloc;
 
        p = a->pool;
 
        /**
         * 扩容有两种方式
         * 1.如果数组元素的末尾和内存池pool的可用开始的地址相同,
         * 并且内存池剩余的空间支持数组扩容,则在当前内存池上扩容
         * 2. 如果扩容的大小超出了当前内存池剩余的容量或者数组元素的末尾和内存池pool的可用开始的地址不相同,
         * 则需要重新分配一个新的内存块存储数组,并且将原数组拷贝到新的地址上
         */
        if ((u_char *) a->elts + size == p->d.last
            && p->d.last + a->size <= p->d.end)
        {
            /*
             * the array allocation is the last in the pool
             * and there is space for new allocation
             */
 
            p->d.last += a->size;
            a->nalloc++;
 
        } else {
            /* allocate a new array */
 
        	/* 重新分配一个 2*size的内存块 */
            new = ngx_palloc(p, 2 * size);
            if (new == NULL) {
                return NULL;
            }
 
            /* 内存块拷贝,将老的内存块拷贝到新的new内存块上面  */
            ngx_memcpy(new, a->elts, size);
            /*
            #define ngx_memcpy(dst, src, n)   (void) memcpy(dst, src, n)
            */
            a->elts = new; /* 内存块指针地址改变 */
            a->nalloc *= 2; /* 分配的个数*2 */
 
            // size 不变(最大容量,即单个元素大小 x 最大元素数)
            // pool 不变(因为分配新的内存块的时候,会去循环读取pool->d.next链表上的缓存池,
            // 并且比较剩余空间大小,是否可以容乃新的内存块存储)
            // nelts 已用元素数量不变
        }
    }
 
    /* 最新的元素指针 地址 */
    elt = (u_char *) a->elts + a->size * a->nelts;
    a->nelts++; //只分配一个元素,所以元素数量+1
 
    return elt;
}


添加多个元素 ngx_array_push_n
/**
 * 这个方法同上,只不过要传unit元素个数、计算地址的时候考虑成多个元素
 */
void *
ngx_array_push_n(ngx_array_t *a, ngx_uint_t n)
{
    void        *elt, *new;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *p;
 
    size = n * a->size;
 
    if (a->nelts + n > a->nalloc) {
 
        /* the array is full */
 
        p = a->pool;
 
        if ((u_char *) a->elts + a->size * a->nalloc == p->d.last
            && p->d.last + size <= p->d.end)
        {
            /*
             * the array allocation is the last in the pool
             * and there is space for new allocation
             */
 
            p->d.last += size;
            a->nalloc += n;
 
        } else {
            /* allocate a new array */
 
            nalloc = 2 * ((n >= a->nalloc) ? n : a->nalloc);
 
            new = ngx_palloc(p, nalloc * a->size);
            if (new == NULL) {
                return NULL;
            }
 
            ngx_memcpy(new, a->elts, a->nelts * a->size);
            a->elts = new;
            a->nalloc = nalloc;
        }
    }
 
    elt = (u_char *) a->elts + a->size * a->nelts;
    a->nelts += n;
 
    return elt;
}



3、缓冲区结构ngx_buf


  • Nginx的buf缓冲区数据结构,主要用来存储非常大块的内存。ngx_buf_t数据结构也贯穿了整个Nginx。

Nginx的缓冲区设计是比较灵活的。

可以自定义管理业务层面的缓冲区链表;
也可以将空闲的缓冲区链表交还给内存池pool->chain结构。
缓冲区ngx_buf_t是nginx处理大数据的关键数据结构,它既应用于内存数据也应用于磁盘数据。


数据结构定义


缓冲区内存块的数据结构 ngx_buf_t

从这个数据结构中,可以看到ngx_buf_t结构,即可以处理内存,也可以处理文件。

  • Nginx使用了位域的方法,节省存储空间。
  • 每个buf都记录了开始和结束点以及未处理的开始和结束点,因为缓冲区的内存申请了之后,是可以被复用的。
  • 所有缓冲区需要的数据结构以及缓冲区的buf内存块都会被分配到内存池上面。
typedef struct ngx_buf_s  ngx_buf_t;
/**
 * Nginx缓冲区
 */
struct ngx_buf_s {
    u_char          *pos;           /* 待处理数据的开始标记  */
    u_char          *last;          /* 待处理数据的结尾标记 */
    off_t            file_pos;		/* 处理文件时,待处理的文件开始标记  */
    off_t            file_last;		/* 处理文件时,待处理的文件结尾标记  */
 
    u_char          *start;         /* 缓冲区开始的指针地址 */
    u_char          *end;           /* 缓冲区结尾的指针地址 */
    ngx_buf_tag_t    tag;			/* 缓冲区标记地址,是一个void类型的指针。 */
    ngx_file_t      *file;			/* 引用的文件 */
    ngx_buf_t       *shadow;
 
 
    /* the buf's content could be changed */
 
    unsigned         temporary:1;	 /* 标志位,为1时,内存可修改 */
 
    /*
     * the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     */
    unsigned         memory:1;   	/* 标志位,为1时,内存只读 */
 
    /* the buf's content is mmap()ed and must not be changed */
    unsigned         mmap:1;		/* 标志位,为1时,mmap映射过来的内存,不可修改 */
 
    unsigned         recycled:1;	/* 标志位,为1时,可回收 */
    unsigned         in_file:1;		/* 标志位,为1时,表示处理的是文件 */
    unsigned         flush:1;		/* 标志位,为1时,表示需要进行flush操作 */
    unsigned         sync:1;		/* 标志位,为1时,表示可以进行同步操作,容易引起堵塞 */
    unsigned         last_buf:1;	/* 标志位,为1时,表示为缓冲区链表ngx_chain_t上的最后一块待处理缓冲区 */
    unsigned         last_in_chain:1;/* 标志位,为1时,表示为缓冲区链表ngx_chain_t上的最后一块缓冲区 */
 
    unsigned         last_shadow:1;	/* 标志位,为1时,表示是否是最后一个影子缓冲区 */
    unsigned         temp_file:1;	/* 标志位,为1时,表示当前缓冲区是否属于临时文件 */
 
    /* STUB */ int   num;
};



缓冲区链表结构 ngx_chain_t
  • 是否还记得内存池结构中,有一个数据结构pool->chain就是保存空闲的缓冲区链表的。
    Nginx的缓冲区ngx_buf_t,通过ngx_chain_t链表结构进行关联和管理。
  • 通过链表的方式实现buf有一个非常大的好处:如果一次需要缓冲区的内存很大,那么并不需要分配一块完整的内存,只需要将缓冲区串起来就可以了。
typedef struct ngx_chain_s       ngx_chain_t;
/**
 * 缓冲区链表结构,放在pool内存池上面
 */
struct ngx_chain_s {
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
};

数据结构图


在这里插入图片描述

  • Nginx的缓冲区数据结构主要包含链表数据结构ngx_chain_t和buf数据结构ngx_buf_t
    Nginx可以在自定义的业务层面管理繁忙busy和空闲free的缓冲区链表结构。通过后边的函数,可以对缓冲区的链表结构和buf结构进行管理。
  • 如果缓冲区链表需要被回收,则会放到Nginx内存池的pool->chain链表上。
  • 缓冲区是Nginx用的非常多的一种数据结构,主要用于接收和输出HTTP的数据信息。所以对Nginx的缓冲区的数据结构深入理解非常有必要。

具体函数实现


创建一个缓冲区buf ngx_create_temp_buf
  • ngx_create_temp_buf直接从pool上创建一个缓冲区的buf,buf大小可以自定义。buf的数据结构和buf内存块都会被创建到pool内存池上。
/**
 * 创建一个缓冲区。需要传入pool和buf的大小
 */
ngx_buf_t *
ngx_create_temp_buf(ngx_pool_t *pool, size_t size)
{
	ngx_buf_t *b;
 
	/* 最终调用的是内存池pool,开辟一段内存用作缓冲区,主要放置ngx_buf_t结构体 */
	b = ngx_calloc_buf(pool);
	if (b == NULL) {
		return NULL;
	}
 
	/* 分配缓冲区内存;  pool为内存池,size为buf的大小*/
	b->start = ngx_palloc(pool, size);
	if (b->start == NULL) {
		return NULL;
	}
 
	/*
	 * set by ngx_calloc_buf():
	 *
	 *     b->file_pos = 0;
	 *     b->file_last = 0;
	 *     b->file = NULL;
	 *     b->shadow = NULL;
	 *     b->tag = 0;
	 *     and flags
	 */
 
	b->pos = b->start;  //待处理数据的标记指针
	b->last = b->start; //待处理数据的结尾标记指针
	b->end = b->last + size; //缓冲区结尾地址
	b->temporary = 1;
 
	return b;
}



创建一个缓冲区的链表结构 ngx_alloc_chain_link
  • 单独创建缓冲区ngx_buf_t是没法形成回收和管理机制的。所以需要创建ngx_chain_t缓冲区链表结构,用来管理整个缓冲区。
/**
 * 创建一个缓冲区的链表结构
 */
ngx_chain_t *
ngx_alloc_chain_link(ngx_pool_t *pool)
{
	ngx_chain_t *cl;
	/*
	 * 首先从内存池中去取ngx_chain_t,
	 * 被清空的ngx_chain_t结构都会放在pool->chain 缓冲链上
	 */
	cl = pool->chain;
 
	if (cl) {
		pool->chain = cl->next;
		return cl;
	}
	/* 如果取不到,则从内存池pool上分配一个数据结构  */
	cl = ngx_palloc(pool, sizeof(ngx_chain_t));
	if (cl == NULL) {
		return NULL;
	}
 
	return cl;
}


批量创建多个缓冲区buf ngx_create_chain_of_bufs
  • 批量创建多个buf,并且用链表串起来。当我们需要的缓冲区非常大的时候,可以通过此方法,分配一个缓冲区链表,用于缓冲区的数据管理。
/**
 * 批量创建多个buf,并且用链表串起来
 */
ngx_chain_t *
ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs)
{
	u_char *p;
	ngx_int_t i;
	ngx_buf_t *b;
	ngx_chain_t *chain, *cl, **ll;
 
	/* 在内存池pool上分配bufs->num个 buf缓冲区 ,每个大小为bufs->size */
	p = ngx_palloc(pool, bufs->num * bufs->size);
	if (p == NULL) {
		return NULL;
	}
 
	ll = &chain;
 
	/* 循环创建BUF,并且将ngx_buf_t挂载到ngx_chain_t链表上,并且返回链表*/
	for (i = 0; i < bufs->num; i++) {
 
		/* 最终调用的是内存池pool,开辟一段内存用作缓冲区,主要放置ngx_buf_t结构体 */
		b = ngx_calloc_buf(pool);
		if (b == NULL) {
			return NULL;
		}
 
		/*
		 * set by ngx_calloc_buf():
		 *
		 *     b->file_pos = 0;
		 *     b->file_last = 0;
		 *     b->file = NULL;
		 *     b->shadow = NULL;
		 *     b->tag = 0;
		 *     and flags
		 *
		 */
 
		b->pos = p;
		b->last = p;
		b->temporary = 1;
 
		b->start = p;
		p += bufs->size; //p往前增
		b->end = p;
 
		/* 分配一个ngx_chain_t */
		cl = ngx_alloc_chain_link(pool);
		if (cl == NULL) {
			return NULL;
		}
 
		/* 将buf,都挂载到ngx_chain_t链表上,最终返回ngx_chain_t链表 */
		cl->buf = b;
		*ll = cl;
		ll = &cl->next;
	}
 
	*ll = NULL;
 
	/* 最终得到一个分配了bufs->num的缓冲区链表  */
	return chain;
}


拷贝缓冲区链表 ngx_chain_add_copy
  • 将其它缓冲区链表放到已有缓冲区链表结构的尾部。
/**
 * 将其它缓冲区链表放到已有缓冲区链表结构的尾部
 */
ngx_int_t ngx_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain,
		ngx_chain_t *in)
{
	ngx_chain_t *cl, **ll;
 
	ll = chain; //chain 指向指针的指针,很绕
 
	/* 找到缓冲区链表结尾部分,cl->next== NULL;cl = *chain既为指针链表地址*/
	for (cl = *chain; cl; cl = cl->next) {
		ll = &cl->next;
	}
 
	/* 遍历in */
	while (in) {
		cl = ngx_alloc_chain_link(pool);
		if (cl == NULL) {
			return NGX_ERROR;
		}
 
		cl->buf = in->buf; //in上的buf拷贝到cl上面
		*ll = cl; //并且放到chain链表上
		ll = &cl->next; //链表往下走
		in = in->next; //遍历,直到NULL
	}
 
	*ll = NULL;
 
	return NGX_OK;
}


获取一个空闲的buf链表结构 ngx_chain_get_free_buf
/**
 * 冲空闲的buf链表上,获取一个未使用的buf链表
 */
ngx_chain_t *
ngx_chain_get_free_buf(ngx_pool_t *p, ngx_chain_t **free)
{
	ngx_chain_t *cl;
 
	/* 空闲列表中有数据,则直接返回 */
	if (*free) {
		cl = *free;
		*free = cl->next;
		cl->next = NULL;
		return cl;
	}
 
	/* 否则分配一个新的buf */
	cl = ngx_alloc_chain_link(p);
	if (cl == NULL) {
		return NULL;
	}
 
	cl->buf = ngx_calloc_buf(p);
	if (cl->buf == NULL) {
		return NULL;
	}
 
	cl->next = NULL;
 
	return cl;
}


释放缓冲区链表 ngx_free_chain和ngx_chain_update_chains
  • ngx_free_chain:直接交还给Nginx内存池的pool->chain空闲buf链表
  • ngx_chain_update_chains:可以交还给自定义的空闲链表上。
//直接交还给缓存池
#define ngx_free_chain(pool, cl)                                             \
    cl->next = pool->chain;                                                  \
    pool->chain = cl
 
 
/**
 * 释放BUF
 * 1. 如果buf不为空,则不释放
 * 2. 如果cl->buf->tag标记不一样,则直接还给Nginx的pool->chain链表
 * 3. 如果buf为空,并且需要释放,则直接释放buf,并且放到free的空闲列表上
 */
void ngx_chain_update_chains(ngx_pool_t *p, ngx_chain_t **free,
		ngx_chain_t **busy, ngx_chain_t **out, ngx_buf_tag_t tag)
{
	ngx_chain_t *cl;
 
	/* *busy 指向OUT,将已经输出的out放到busy链表上 */
	if (*busy == NULL) {
		*busy = *out;
 
	} else {
		for (cl = *busy; cl->next; cl = cl->next) { /* void */
		}
 
		cl->next = *out;
	}
 
	*out = NULL;
 
	/* 遍历 busy链表 */
	while (*busy) {
		cl = *busy;
 
		/* 如果buf不为空,则继续遍历 */
		if (ngx_buf_size(cl->buf) != 0) {
			break;
		}
 
		/* 如果标识一样,则释放这个BUF */
		if (cl->buf->tag != tag) {
			*busy = cl->next;
			ngx_free_chain(p, cl); //还给Nginx pool->chain
			continue;
		}
 
		/* 直接将buf使用的部分回归到 起点指针地址 */
		cl->buf->pos = cl->buf->start;
		cl->buf->last = cl->buf->start;
 
		*busy = cl->next; //继续往后遍历
 
		/* 并且将cl放到free列表上 */
		cl->next = *free;
		*free = cl;
	}
}



4、双向链表结构ngx_queue


数据结构定义


链表数据结构 ngx_queue_t
typedef struct ngx_queue_s ngx_queue_t;
 
/**
 * 链表的数据结构非常简单,ngx_queue_s会挂载到实体
 * 结构上。然后通过ngx_queue_s来做成链表
 */
struct ngx_queue_s {
	ngx_queue_t *prev;
	ngx_queue_t *next;
};

一般情况下,会在业务的数据结构中,放置一个ngx_queue_t的数据结构。通过这个数据结构进行双向链表的连接。

例如下面的数据结构:

/**
 * 该结构体用于描述一个网络连接
 */
struct ngx_connection_s {
	void *data; //连接未使用时,data用于充当连接池中空闲链表中的next指针。连接使用时由模块而定,HTTP中,data指向ngx_http_request_t
	ngx_event_t *read; //连接对应的读事件
	ngx_event_t *write; //连接对应的写事件
 
	ngx_socket_t fd; //套接字句柄
 
	ngx_recv_pt recv; //直接接受网络字节流
	ngx_send_pt send; //直接发送网络字节流
	ngx_recv_chain_pt recv_chain; //网络字节流接收链表
	ngx_send_chain_pt send_chain; //网络字节流发送链表
 
	/*用来将当前连接以双向链表元素的形式添加到ngx_cycle_t核心结构体
	 * 的reuseable_connection_queue双向链表中,表示可以重用的连接*/
	ngx_queue_t queue;
 
	/* 省去部分 */
};


数据结构图


双向链表就是主体业务数据结构,然后子业务数据的链表结构就指向主体业务链表,这样就可以找到在主体业务的位置

在这里插入图片描述

整个链表实现的是双向链表。
遍历链表后,获取链表的主体业务数据结构(上图为ngx_connection_s)使用ngx_queue_data方法
链表和业务数据结构之间进行了解耦,使用更加灵活和方便。


具体函数实现


链表常用操作方法

Nginx的链表用宏定义的方式定义了很多常用的双向链表操作方法。

/**
 * 初始化一个Q
 */
#define ngx_queue_init(q)                                                     \
    (q)->prev = q;                                                            \
    (q)->next = q
 
/**
 * 判断是否是空Q
 */
#define ngx_queue_empty(h)                                                    \
    (h == (h)->prev)
 
/**
 * 向链表H后面插入一个x的Q,支持中间插入
 */
#define ngx_queue_insert_head(h, x)                                           \
    (x)->next = (h)->next;                                                    \
    (x)->next->prev = x;                                                      \
    (x)->prev = h;                                                            \
    (h)->next = x
 
#define ngx_queue_insert_after   ngx_queue_insert_head
 
/**
 * 向链表H前面插入一个x的Q,支持中间插入
 */
#define ngx_queue_insert_tail(h, x)                                           \
    (x)->prev = (h)->prev;                                                    \
    (x)->prev->next = x;                                                      \
    (x)->next = h;                                                            \
    (h)->prev = x
 
/**
 * h是尾部,链表的第一个元素
 */
#define ngx_queue_head(h)                                                     \
    (h)->next
 
// h 是头,h 的上一个就是尾
#define ngx_queue_last(h)                                                     \
    (h)->prev
 
#define ngx_queue_sentinel(h)                                                 \
    (h)
 
/**
 * 返回节点Q的下一个元素
 */
#define ngx_queue_next(q)                                                     \
    (q)->next
 
/**
 * 返回节点Q的上一个元素
 */
#define ngx_queue_prev(q)                                                     \
    (q)->prev
 
#if (NGX_DEBUG)
 
/**
 * 移除某一个节点
 */
#define ngx_queue_remove(x)                                                   \
    (x)->next->prev = (x)->prev;                                              \
    (x)->prev->next = (x)->next;                                              \
    (x)->prev = NULL;                                                         \
    (x)->next = NULL
 
#else
 
#define ngx_queue_remove(x)                                                   \
    (x)->next->prev = (x)->prev;                                              \
    (x)->prev->next = (x)->next
 
#endif
 
//分割一个链表
#define ngx_queue_split(h, q, n)                                              \
    (n)->prev = (h)->prev;                                                    \
    (n)->prev->next = n;                                                      \
    (n)->next = q;                                                            \
    (h)->prev = (q)->prev;                                                    \
    (h)->prev->next = h;                                                      \
    (q)->prev = n;
 
 
#define ngx_queue_add(h, n)                                                   \
    (h)->prev->next = (n)->next;                                              \
    (n)->next->prev = (h)->prev;                                              \
    (h)->prev = (n)->prev;                                                    \
    (h)->prev->next = h;


获取链表的主体结构 ngx_queue_data

前面我们已经看到,Nginx的ngx_queue_t数据结构是挂载在具体的业务数据结构上面的。通过ngx_queue_data,我们可以通过业务结构体中ngx_queue_t数据结构的偏移量来得到业务主体的数据结构的指针地址。

/**
 * 通过链表可以找到结构体所在的指针
 * typedef struct {
 * 		ngx_queue_s queue;
 * 		char * x;
 * 		....
 * } TYPE
 * 例如:ngx_queue_data(&type->queue, TYPE, queue)
 */
#define ngx_queue_data(q, type, link)                                         \
    (type *) ((u_char *) q - offsetof(type, link))


5、单向链表结构 ngx_list

  • Nginx的list单向链表的结构和Nginx的数组结构Array有点类似(也就是把栈变成了链表),总体来说,数据结构也是非常简单清晰的。

  • Nginx的单向链表也是固定了每个元素的大小,并且用单向链表的方式连接。

数据结构定义

链表节点定义ngx_list_part_t

跟Array类似

typedef struct ngx_list_part_s  ngx_list_part_t;
 
/**
 * 链表节点  每个节点大小 = size * nelts
 * 节点元素用完后,每次就会分配一个新的节点
 */
struct ngx_list_part_s {
    void             *elts;  	/* 节点的内存起始位置 */
    ngx_uint_t        nelts; 	/* 已经使用的元素*/
    ngx_list_part_t  *next;  	/* 指向下一个链表节点*/
};


链表结构ngx_list_t
/**
 * 链表结构
 */
typedef struct {
    ngx_list_part_t  *last;		/* 指向最新的链表节点*/
    ngx_list_part_t   part;		/* 第一个链表节点*/
    size_t            size;		/* 这个链表默认的每个元素大小 */
    ngx_uint_t        nalloc;	        /* 每个节点part 可以支持多少个元素*/
    ngx_pool_t       *pool;		/* 线程池*/
} ngx_list_t;



数据结构图

在这里插入图片描述


具体函数实现

创建一个list ngx_list_create
  • 通过调用ngx_list_create创建一个list,并且可以指定每个元素的大小以及每个节点元素个数;然后初始化。
/**
 * 创建链表
 */
ngx_list_t *
ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
    ngx_list_t  *list;
 
    /* 从内存池上面分配一块内存,存储ngx_list_t数据结构 */
    list = ngx_palloc(pool, sizeof(ngx_list_t));
    if (list == NULL) {
        return NULL;
    }
 
    /* 分配一个链表节点的内存块。内存大小  n * size*/
    list->part.elts = ngx_palloc(pool, n * size);
    if (list->part.elts == NULL) {
        return NULL;
    }
 
    list->part.nelts = 0; 		/* 使用的元素个数	*/
    list->part.next = NULL; 	/* 下一个节点		*/
    list->last = &list->part; 	/* 最后一个节点地址 	*/
    list->size = size;  		/* 每个元素的大小	*/
    list->nalloc = n;			/* 分配多少个 		*/
    list->pool = pool;			/* 线程池			*/
 
    return list;
}


使用一个list元素 ngx_list_push
  • 使用ngx_list_push方法可以push一个元素,并且返回元素的指针地址。如果节点元素已经用完,则会创建一个新的
void *
ngx_list_push(ngx_list_t *l)
{
    void             *elt;
    ngx_list_part_t  *last;
 
    last = l->last;
 
    /* 如果最后一个链表节点的元素已经用完,则需要创建一个新的链表*/
    if (last->nelts == l->nalloc) {
 
        /* the last part is full, allocate a new list part */
 
    	/* 分配一块内存,存储ngx_list_part_t数据结构 */
        last = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
        if (last == NULL) {
            return NULL;
        }
 
        /* 分配一个链表节点的内存块。内存大小  n * size*/
        last->elts = ngx_palloc(l->pool, l->nalloc * l->size);
        if (last->elts == NULL) {
            return NULL;
        }
 
        last->nelts = 0;
        last->next = NULL;
 
        l->last->next = last;
        l->last = last;
    }
 
 
    /* 返回元素指针 */
    elt = (char *) last->elts + l->size * last->nelts;
    last->nelts++;
 
    return elt;
}



6、hash表结构 ngx_hash

  • Nginx的hash模块包含了对内存利用最大化、CPU利用最大化的很多设计细节,非常值得推荐和学习。

  • Nginx的hash表结构主要几个特点:

  • 静态只读。当初始化生成hash表结构后,是不能动态修改这个hash表结构的内容。

  • 将内存利用最大化。Nginx的hash表,将内存利用率发挥到了极致,并且很多设计上面都是可以供我们学习和参考的。

  • 查询速度快。Nginx的hash表做了内存对齐等优化
    主要解析配置数据


数据结构定义


hash表的元素结构ngx_hash_elt_t
/**
 * 存储hash的元素
 */
typedef struct {
    void             *value; 	/* 指向value的指针 */
    u_short           len;   	/* key的长度 */
    u_char            name[1]; 	/* 指向key的第一个地址,key长度为变长(设计上的亮点)*/
} ngx_hash_elt_t;

hash表结构 ngx_hash_t
/**
 * Hash的桶
 */
typedef struct {
    ngx_hash_elt_t  **buckets; 	/* hash表的桶指针地址值 */
    ngx_uint_t        size; 	/* hash表的桶的个数*/
} ngx_hash_t;


hash表初始化结构 ngx_hash_init_t

同样,hash表也是基于内存池的

/**
 * hash表主体结构
 */
typedef struct {
    ngx_hash_t       *hash;	/* 指向hash数组结构 */
    ngx_hash_key_pt   key;  /* 计算key散列的方法 */
 
    ngx_uint_t        max_size; 	/* 最大多少个 */
    ngx_uint_t        bucket_size; 	/* 桶的存储空间大小 */
 
    char             *name; /* hash表名称 */
    ngx_pool_t       *pool; /* 内存池 */
    ngx_pool_t       *temp_pool; /* 临时内存池*/
} ngx_hash_init_t;



数据结构图


在这里插入图片描述

  • Nginx的hash表主要存放在ngx_hash_t数据结构上。ngx_hash_t主要存放桶的指针值和桶的个数
  • Nginx的hash表中桶的个数会在初始化的时候进行“探测”,会探测出合适的桶的个数。
  • Nginx的hash表在初始化的时候就决定了hash表的桶的个数以及元素个数和大小,所以所有元素都会被分配到一个大的连续的内存池上。
  • 每个bucket的长度会根据元素个数的实际长度决定,并且每个bucket之间通过NULL指针进行分割。
  • 每个桶都保存了桶的第一个元素ngx_hash_elt_t的指针值。
  • NULL指针会在查找元素的时候用到,具体看下面的源码阅读。
  • ngx_hash_elt_t存储每个元素的数据结构,并且key的长度是非定长的。

具体函数实现


查找一个元素 ngx_hash_find
/**
 * 从hash表中读取一个元素
 */
void *
ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
{
	ngx_uint_t i;
	ngx_hash_elt_t *elt;
 
#if 0
	ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "hf:\"%*s\"", len, name);
#endif
 
	/* 获取对应的桶 */
	elt = hash->buckets[key % hash->size];
 
	if (elt == NULL) {
		return NULL;
	}
 
	/* 在桶的链表上,查找具体的值;elt元素最后一个elt->value==NULL */
	while (elt->value) {
		if (len != (size_t) elt->len) {
			goto next;
		}
 
		for (i = 0; i < len; i++) {
			if (name[i] != elt->name[i]) {
				goto next;
			}
		}
 
		return elt->value;
 
		next:
 
		/* 因为在内存池上申请内存,并且是自己处理整块内存,为了CPU读取速度更快,进行了内存对齐 */
		elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
				sizeof(void *));
		continue;
	}
 
	return NULL;
}


创建一个hash表 ngx_hash_init
  • 获取元素大小的宏定义。
/**
 * 获取元素的大小
 * 元素大小主要是ngx_hash_elt_t结构,包括:
 * 1. name的长度	(name)->key.len
 * 2. len的长度	其中的"+2"是要加上该结构中len字段(u_short类型)的大小
 * 3. value指针的长度	"sizeof(void *)"相当于 value的长度
 */
#define NGX_HASH_ELT_SIZE(name)                                               \
    (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))


ngx_hash_init初始化一个hash表

  • Nginx的hash表是只读的,所以在初始化的时候就会生成固定的hash表。
  • 初始化过程中,先会根据实际key的大小来进行“探测”,得出一个合适的桶的个数。
  • 然后根据元素的大小,来确定每个桶具体的大小,并且分配完整的元素大内存块。
  • 然后将元素切割成ngx_hash_elt_t的结构,装入每一个bucket桶上。
  • 每个bucket的结尾都会有一个NULL空指针作为标识符号,该标识符号会强制换成ngx_hash_elt_t结构,并且设置value=NULL,在查询的时候用于判断桶的结尾部分。
/**
 * 初始化一个hash表
 */
ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
		ngx_uint_t nelts)
{
	u_char *elts;
	size_t len;
	u_short *test;
	ngx_uint_t i, n, key, size, start, bucket_size;
	ngx_hash_elt_t *elt, **buckets;
 
	/**
	 * 先检查每个元素是否会超过bucket_size的限制
	 * 如果超过限制,则说明需要重新处理
	 * hash表的每一个bucket桶中的元素elt都是被分配到一块完整的内存块上的,
	 * 每个bucket的内存块结尾会有一个void *的空指针作为表示符号用于分隔bucket
	 */
	for (n = 0; n < nelts; n++) {
		if (hinit->bucket_size
				< NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *)) {
			ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
					"could not build the %s, you should "
							"increase %s_bucket_size: %i", hinit->name,
					hinit->name, hinit->bucket_size);
			return NGX_ERROR;
		}
	}
 
	/*
	 * test是用来做探测用的,探测的目标是在当前bucket的数量下,冲突发生的是否频繁。
	 * 过于频繁则需要调整桶的个数。
	 * 检查是否频繁的标准是:判断元素总长度和bucket桶的容量bucket_size做比较
	 */
	test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);
	if (test == NULL) {
		return NGX_ERROR;
	}
 
	/**
	 * 每个桶的元素实际所能容纳的空间大小
	 * 需要减去尾部的NULL指针结尾符号
	 */
	bucket_size = hinit->bucket_size - sizeof(void *);
 
	/**
	 * 通过一定的小算法,计算得到从哪个桶开始test(探测)
	 */
	start = nelts / (bucket_size / (2 * sizeof(void *)));
	start = start ? start : 1;
 
	if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) {
		start = hinit->max_size - 1000;
	}
 
	/**
	 * 这边就是真正的探测逻辑
	 * 探测会遍历所有的元素,并且计算落到同一个bucket上元素长度的总和和bucket_size比较
	 * 如果超过了bucket_size,则说明需要调整
	 * 最终会探测出比较合适的桶的个数 :size
	 */
	for (size = start; size < hinit->max_size; size++) {
 
		ngx_memzero(test, size * sizeof(u_short));
 
		for (n = 0; n < nelts; n++) {
			if (names[n].key.data == NULL) {
				continue;
			}
 
			key = names[n].key_hash % size;
			test[key] = (u_short)(test[key] + NGX_HASH_ELT_SIZE(&names[n]));
 
#if 0
			ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
					"%ui: %ui %ui \"%V\"",
					size, key, test[key], &names[n].key);
#endif
 
			/* 比较bucket_size和落到该bucket上的元素长度总和*/
			if (test[key] > (u_short) bucket_size) {
				goto next;
			}
		}
 
		goto found;
 
		next:
 
		continue;
	}
 
	ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
			"could not build the %s, you should increase "
					"either %s_max_size: %i or %s_bucket_size: %i", hinit->name,
			hinit->name, hinit->max_size, hinit->name, hinit->bucket_size);
 
	ngx_free(test);
 
	return NGX_ERROR;
 
	/**
	 * 探测成功,则size为bucket桶的个数
	 */
	found:
 
	/**
	 * 为了确定bucket的实际长度,初始化每个桶的长度计数器,初始值为一个NULL空指针长度
	 * 前面说过,每个bucket的内存块之间,使用一个NULL空指针进行分割,所以长度需要加上去
	 */
	for (i = 0; i < size; i++) {
		test[i] = sizeof(void *);
	}
 
	/**
	 * 通过遍历,计算每个桶的大小。并且将每个桶的大小存储在test[n]数组上
	 */
	for (n = 0; n < nelts; n++) {
		if (names[n].key.data == NULL) {
			continue;
		}
 
		key = names[n].key_hash % size;
		test[key] = (u_short)(test[key] + NGX_HASH_ELT_SIZE(&names[n]));
	}
 
	len = 0;
 
	/**
	 * 获取所有元素需要分配的内存的总大小
	 * len = 总的内存大小,所有的桶都会放在一块内存上,并且做了手工内存对齐
	 */
	for (i = 0; i < size; i++) {
		if (test[i] == sizeof(void *)) {
			continue;
		}
 
		/* 总内存大小,需要通过内存对齐函数 */
		test[i] = (u_short)(ngx_align(test[i], ngx_cacheline_size));
 
		len += test[i];
	}
 
	/**
	 * 分配一块内存空间,存储:ngx_hash_t *hash和ngx_hash_elt_t *
	 * ngx_hash_elt_t用于存储桶。指针指向元素地址
	 */
	if (hinit->hash == NULL) {
		hinit->hash = ngx_pcalloc(hinit->pool,
				sizeof(ngx_hash_wildcard_t) + size * sizeof(ngx_hash_elt_t *));
		if (hinit->hash == NULL) {
			ngx_free(test);
			return NGX_ERROR;
		}
 
		buckets = (ngx_hash_elt_t **) ((u_char *) hinit->hash
				+ sizeof(ngx_hash_wildcard_t));
 
	} else {
		buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));
		if (buckets == NULL) {
			ngx_free(test);
			return NGX_ERROR;
		}
	}
 
	/**
	 * 分配一个桶,用于存储所有元素数据
	 */
	elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);
	if (elts == NULL) {
		ngx_free(test);
		return NGX_ERROR;
	}
 
	elts = ngx_align_ptr(elts, ngx_cacheline_size); //内存对齐
 
	/**
	 * 将elts元素的内存,分割到buckets桶上
	 */
	for (i = 0; i < size; i++) {
		if (test[i] == sizeof(void *)) {
			continue;
		}
 
		buckets[i] = (ngx_hash_elt_t *) elts;
		elts += test[i];
 
	}
 
	/**
	 * 将test清空,利用test于元素填充计数器
	 */
	for (i = 0; i < size; i++) {
		test[i] = 0;
	}
 
	/**
	 * 往bucket的元素位上填充数据
	 */
	for (n = 0; n < nelts; n++) {
		if (names[n].key.data == NULL) {
			continue;
		}
 
		/* 计算在哪个桶上 */
		key = names[n].key_hash % size;
		elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);
 
		elt->value = names[n].value;
		elt->len = (u_short) names[n].key.len;
 
		/* 拷贝key数据,并且小写 */
		ngx_strlow(elt->name, names[n].key.data, names[n].key.len);
 
		/* test计数器计算新元素需要存放的位置 */
		test[key] = (u_short)(test[key] + NGX_HASH_ELT_SIZE(&names[n]));
	}
 
	/**
	 *  设置bucket桶上最后一个元素设置为value为NULL
	 */
	for (i = 0; i < size; i++) {
		if (buckets[i] == NULL) {
			continue;
		}
		/**
		 * 这边的设计 Nice!!!
		 * test[i] 其实是bucket的元素块的结束位置
		 * 由于前面bucket的处理中多留出了一个指针的空间,而此时的test[i]是bucket中实际数据的共长度,
		 * 所以bucket[i] + test[i]正好指向了末尾null指针所在的位置。处理的时候,把它当成一个ngx_hash_elt_t结构看,
		 * 在该结构中的第一个元素,正好是一个void指针,我们只处理它,别的都不去碰,所以没有越界的问题。
		 */
		elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]);
 
		elt->value = NULL;
	}
 
	ngx_free(test);
 
	hinit->hash->buckets = buckets;
	hinit->hash->size = size;
 
	return NGX_OK;
}



7、字符串结构 ngx_string


数据结构定义


字符串结构ngx_str_t
/**
 * 字符串结构
 */
typedef struct {
    size_t      len; //字符串长度
    u_char     *data; //具体的指针地址
} ngx_str_t;
字符串K V结构ngx_keyval_t
/**
 * 字符串的K V结构
 */
typedef struct {
    ngx_str_t   key;
    ngx_str_t   value;
} ngx_keyval_t;

数据结构图

在这里插入图片描述


具体函数实现


初始化一个字符串 ngx_string
//初始化一个字符串
#define ngx_string(str)     { sizeof(str) - 1, (u_char *) str }
设置字符串
//将一个字符串设置为NULL
#define ngx_null_string     { 0, NULL }
//设置一个字符串
#define ngx_str_set(str, text)                                               \
    (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
#define ngx_str_null(str)   (str)->len = 0; (str)->data = NULL

三、Nginx主流程


  • 前几节主要介绍了Nginx比较常用的一些基础数据结构,例如pool,buf,array,list等。通过对Nginx基础数据结构的理解,能更好的帮助我们读懂整个Nginx的源代码。

  • Nginx的主流程的实现函数在./src/core/nginx.c文件中。通过main()函数,我们可以窥探整个Nginx启动的流程。


1、Nginx的启动过程

在这里插入图片描述

重要流程分析

ngx_get_options 解析外部参数
  • ngx_get_options方法主要用于解析命令行外部参数。例如:./nginx -s stop|start|restart
    /* 解析外部参数 */
    if (ngx_get_options(argc, argv) != NGX_OK) {
        return 1;
    }
/**
 * 解析启动命令行中的参数
 * ./nginx -s stop|start|restart
 */
static ngx_int_t
ngx_get_options(int argc, char *const *argv)
{
    u_char     *p;
    ngx_int_t   i;
 
    for (i = 1; i < argc; i++) {
 
        p = (u_char *) argv[i];
 
        if (*p++ != '-') {
            ngx_log_stderr(0, "invalid option: \"%s\"", argv[i]);
            return NGX_ERROR;
        }
 
        while (*p) {
 
            switch (*p++) {
 
            case '?':
            case 'h':
                ngx_show_version = 1;
                ngx_show_help = 1;
                break;
 
            case 'v':
                ngx_show_version = 1;
                break;
 
            case 'V':
                ngx_show_version = 1;
                ngx_show_configure = 1;
                break;
 
            case 't':
                ngx_test_config = 1;
                break;
 
            case 'T':
                ngx_test_config = 1;
                ngx_dump_config = 1;
                break;
 
            case 'q':
                ngx_quiet_mode = 1;
                break;
 
            case 'p':
                if (*p) {
                    ngx_prefix = p;
                    goto next;
                }
 
                if (argv[++i]) {
                    ngx_prefix = (u_char *) argv[i];
                    goto next;
                }
 
                ngx_log_stderr(0, "option \"-p\" requires directory name");
                return NGX_ERROR;
 
            case 'c':
                if (*p) {
                    ngx_conf_file = p;
                    goto next;
                }
 
                if (argv[++i]) {
                    ngx_conf_file = (u_char *) argv[i];
                    goto next;
                }
 
                ngx_log_stderr(0, "option \"-c\" requires file name");
                return NGX_ERROR;
 
            case 'g':
                if (*p) {
                    ngx_conf_params = p;
                    goto next;
                }
 
                if (argv[++i]) {
                    ngx_conf_params = (u_char *) argv[i];
                    goto next;
                }
 
                ngx_log_stderr(0, "option \"-g\" requires parameter");
                return NGX_ERROR;
 
            case 's':
                if (*p) {
                    ngx_signal = (char *) p;
 
                } else if (argv[++i]) {
                    ngx_signal = argv[i];
 
                } else {
                    ngx_log_stderr(0, "option \"-s\" requires parameter");
                    return NGX_ERROR;
                }
 
                if (ngx_strcmp(ngx_signal, "stop") == 0
                    || ngx_strcmp(ngx_signal, "quit") == 0
                    || ngx_strcmp(ngx_signal, "reopen") == 0
                    || ngx_strcmp(ngx_signal, "reload") == 0)
                {
                    ngx_process = NGX_PROCESS_SIGNALLER;
                    goto next;
                }
 
                ngx_log_stderr(0, "invalid option: \"-s %s\"", ngx_signal);
                return NGX_ERROR;
 
            default:
                ngx_log_stderr(0, "invalid option: \"%c\"", *(p - 1));
                return NGX_ERROR;
            }
        }
 
    next:
 
        continue;
    }
 
    return NGX_OK;
}


init_cycle 初始化全局变量
  • 初始化init_cycle Nginx的全局变量。在内存池上创建一个默认大小1024的全局变量。这里只是最简单的初始化一个变量。
    /*
     * init_cycle->log is required for signal handlers and
     * ngx_process_options()
     */
    /* 初始化Nginx的init_cycle */
    ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
    init_cycle.log = log;
    ngx_cycle = &init_cycle;
 
    /* 创建内存池 默认大小1024*/
    init_cycle.pool = ngx_create_pool(1024, log);
    if (init_cycle.pool == NULL) {
        return 1;
    }


  • 真正的初始化在ngx_init_cycle这个函数中。ngx_init_cycle包含了Nginx的全局变量的全部初始化过程,后面会单独开一节文章讲解。
    /* 完成cycle的初始化工作 */
    cycle = ngx_init_cycle(&init_cycle);
    if (cycle == NULL) {
        if (ngx_test_config) {
            ngx_log_stderr(0, "configuration file %s test failed",
                           init_cycle.conf_file.data);
        }
 
        return 1;
    }
ngx_save_argv和ngx_process_options 变量保存方法
  • ngx_save_argv:保存Nginx命令行中的参数和变量,放到全局变量ngx_argv
  • ngx_process_options:将ngx_get_options中获得这些参数取值赋值到ngx_cycle中。prefix, conf_prefix, conf_file, conf_param等字段。
    /* 保存Nginx命令行中的参数和变量,放到全局变量ngx_argv */
    if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {
        return 1;
    }
 
    /* 将ngx_get_options中获得这些参数取值赋值到ngx_cycle中 */
    if (ngx_process_options(&init_cycle) != NGX_OK) {
        return 1;
    }

头部的全局变量定义

/**
 * 定义全局变量参数
 */
static ngx_uint_t   ngx_show_help; //是否显示帮助信息
static ngx_uint_t   ngx_show_version; //是否显示版本号
static ngx_uint_t   ngx_show_configure; //是否显示配置信息
static u_char      *ngx_prefix; //Nginx的工作目录
static u_char      *ngx_conf_file; //全局配置文件目录地址
static u_char      *ngx_conf_params; //配置参数
static char        *ngx_signal; //信号
int              ngx_argc; //命令行参数个数
char           **ngx_argv; //命令行参数
char           **ngx_os_argv;
/**
 * 保存Nginx命令行中的参数和变量
 * 放到全局变量ngx_argv
 */
static ngx_int_t
ngx_save_argv(ngx_cycle_t *cycle, int argc, char *const *argv)
{
#if (NGX_FREEBSD)
 
    ngx_os_argv = (char **) argv;
    ngx_argc = argc;
    ngx_argv = (char **) argv;
 
#else
    size_t     len;
    ngx_int_t  i;
 
    ngx_os_argv = (char **) argv;
    ngx_argc = argc;
 
    ngx_argv = ngx_alloc((argc + 1) * sizeof(char *), cycle->log);
    if (ngx_argv == NULL) {
        return NGX_ERROR;
    }
 
    for (i = 0; i < argc; i++) {
        len = ngx_strlen(argv[i]) + 1;
 
        ngx_argv[i] = ngx_alloc(len, cycle->log);
        if (ngx_argv[i] == NULL) {
            return NGX_ERROR;
        }
 
        (void) ngx_cpystrn((u_char *) ngx_argv[i], (u_char *) argv[i], len);
    }
 
    ngx_argv[i] = NULL;
 
#endif
 
    ngx_os_environ = environ;
 
    return NGX_OK;
}


/**
 * 将ngx_get_options中获得这些参数取值赋值到ngx_cycle中
 */
static ngx_int_t
ngx_process_options(ngx_cycle_t *cycle)
{
    u_char  *p;
    size_t   len;
 
    /* Nginx工作目录 */
    if (ngx_prefix) {
        len = ngx_strlen(ngx_prefix);
        p = ngx_prefix;
 
        if (len && !ngx_path_separator(p[len - 1])) {
            p = ngx_pnalloc(cycle->pool, len + 1);
            if (p == NULL) {
                return NGX_ERROR;
            }
 
            ngx_memcpy(p, ngx_prefix, len);
            p[len++] = '/';
        }
 
        cycle->conf_prefix.len = len;
        cycle->conf_prefix.data = p;
        cycle->prefix.len = len;
        cycle->prefix.data = p;
 
    } else {
 
#ifndef NGX_PREFIX
 
        p = ngx_pnalloc(cycle->pool, NGX_MAX_PATH);
        if (p == NULL) {
            return NGX_ERROR;
        }
 
        if (ngx_getcwd(p, NGX_MAX_PATH) == 0) {
            ngx_log_stderr(ngx_errno, "[emerg]: " ngx_getcwd_n " failed");
            return NGX_ERROR;
        }
 
        len = ngx_strlen(p);
 
        p[len++] = '/';
 
        cycle->conf_prefix.len = len;
        cycle->conf_prefix.data = p;
        cycle->prefix.len = len;
        cycle->prefix.data = p;
 
#else
 
#ifdef NGX_CONF_PREFIX
        ngx_str_set(&cycle->conf_prefix, NGX_CONF_PREFIX);
#else
        ngx_str_set(&cycle->conf_prefix, NGX_PREFIX);
#endif
        ngx_str_set(&cycle->prefix, NGX_PREFIX);
 
#endif
    }
 
    /* 配置文件目录 */
    if (ngx_conf_file) {
        cycle->conf_file.len = ngx_strlen(ngx_conf_file);
        cycle->conf_file.data = ngx_conf_file;
 
    } else {
        ngx_str_set(&cycle->conf_file, NGX_CONF_PATH);
    }
 
    if (ngx_conf_full_name(cycle, &cycle->conf_file, 0) != NGX_OK) {
        return NGX_ERROR;
    }
 
    for (p = cycle->conf_file.data + cycle->conf_file.len - 1;
         p > cycle->conf_file.data;
         p--)
    {
        if (ngx_path_separator(*p)) {
            cycle->conf_prefix.len = p - ngx_cycle->conf_file.data + 1;
            cycle->conf_prefix.data = ngx_cycle->conf_file.data;
            break;
        }
    }
 
    /* 配置参数 */
    if (ngx_conf_params) {
        cycle->conf_param.len = ngx_strlen(ngx_conf_params);
        cycle->conf_param.data = ngx_conf_params;
    }
 
    if (ngx_test_config) {
        cycle->log->log_level = NGX_LOG_INFO;
    }
 
    return NGX_OK;
}


ngx_preinit_modules给模块打标
  • ngx_preinit_modules方法主要初始化所有模块;
    并对所有模块进行编号处理;
    /* 初始化所有模块;并对所有模块进行编号处理;
     * ngx_modules数却是在自动编译的时候生成的,位于objs/ngx_modules.c文件中   */
    if (ngx_preinit_modules() != NGX_OK) {
        return 1;
    }

ngx_module.c

/**
 * 初始化所有模块;并对所有模块进行编号处理;
 */
ngx_int_t
ngx_preinit_modules(void)
{
    ngx_uint_t  i;
 
    for (i = 0; ngx_modules[i]; i++) {
        ngx_modules[i]->index = i;
        ngx_modules[i]->name = ngx_module_names[i];
    }
 
    ngx_modules_n = i;
    ngx_max_module = ngx_modules_n + NGX_MAX_DYNAMIC_MODULES;
 
    return NGX_OK;
}
ngx_create_pidfile创建PID文件
    /* 创建PID文件 */
    if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) {
        return 1;
    }

ngx_cycle.c

/**
 * 创建PID的文件
 */
ngx_int_t
ngx_create_pidfile(ngx_str_t *name, ngx_log_t *log)
{
    size_t      len;
    ngx_uint_t  create;
    ngx_file_t  file;
    u_char      pid[NGX_INT64_LEN + 2];
 
    if (ngx_process > NGX_PROCESS_MASTER) {
        return NGX_OK;
    }
 
    ngx_memzero(&file, sizeof(ngx_file_t));
 
    file.name = *name;
    file.log = log;
 
    create = ngx_test_config ? NGX_FILE_CREATE_OR_OPEN : NGX_FILE_TRUNCATE;
 
    file.fd = ngx_open_file(file.name.data, NGX_FILE_RDWR,
                            create, NGX_FILE_DEFAULT_ACCESS);
 
    if (file.fd == NGX_INVALID_FILE) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      ngx_open_file_n " \"%s\" failed", file.name.data);
        return NGX_ERROR;
    }
 
    if (!ngx_test_config) {
        len = ngx_snprintf(pid, NGX_INT64_LEN + 2, "%P%N", ngx_pid) - pid;
 
        if (ngx_write_file(&file, pid, len, 0) == NGX_ERROR) {
            return NGX_ERROR;
        }
    }
 
    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", file.name.data);
    }
 
    return NGX_OK;
}

2、平滑重启和信号控制


ngx_add_inherited_sockets 平滑重启热切换

  • 我们分析了Nginx的启动流程。其中ngx_add_inherited_sockets主要用于继承Socket文件句柄。

  • Nginx有平滑重启的功能,通过平滑重启,可以让用户无感知并且不中断。

#平滑重启  
sudo kill -HUP `cat /usr/local/nginx-1.4.7/nginx.pid`  
  • Nginx支持热切换,为了保证切换之后的套接字不丢失,就需要Nginx继承原先的socket(用户正在连接Nginx的时候的文件句柄)。

ngx_add_inherited_sockets 正是做了这个工作

  • 套接字会放在“NGINX”的全局环境变量中。

  • 继承后的socket文件句柄放到ngx_cycle.listening数组中。

    /* 初始化socket端口监听,例如打开80端口监听;
     * Nginx支持热切换,为了保证切换之后的套接字不丢失,
     * 所以需要采用这一步添加继承的Socket套接字,套接字会放在NGINX的全局环境变量中*/
    if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {
        return 1;
    }

NGINX宏变量的值,值为客户端的socket fd句柄:

NGINX="16000:16500:16600;"
/**
 * Nginx支持热切换,为了保证切换之后的套接字不丢失,所以需要采用这一步添加继承的Socket套接字,套接字会放在NGINX的全局环境变量中
 * 初始化继承的sockets
 * 函数通过环境变量NGINX完成socket的继承,继承来的socket将会放到init_cycle的listening数组中。
 * 在NGINX环境变量中,每个socket中间用冒号或分号隔开。完成继承同时设置全局变量ngx_inherited为1。
 */
static ngx_int_t
ngx_add_inherited_sockets(ngx_cycle_t *cycle)
{
    u_char           *p, *v, *inherited;
    ngx_int_t         s;
    ngx_listening_t  *ls;
 
    /* 获取宏环境变量NGINX的值    例如:# export NGINX="16000:16500:16600;" */
    inherited = (u_char *) getenv(NGINX_VAR);
 
    if (inherited == NULL) {
        return NGX_OK;
    }
 
    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
                  "using inherited sockets from \"%s\"", inherited);
 
    /* 初始化ngx_cycle.listening数组,并且数组中包含10个元素 */
    if (ngx_array_init(&cycle->listening, cycle->pool, 10,
                       sizeof(ngx_listening_t))
        != NGX_OK)
    {
        return NGX_ERROR;
    }
 
    for (p = inherited, v = p; *p; p++) {
        if (*p == ':' || *p == ';') {
            s = ngx_atoi(v, p - v);
            if (s == NGX_ERROR) {
                ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
                              "invalid socket number \"%s\" in " NGINX_VAR
                              " environment variable, ignoring the rest"
                              " of the variable", v);
                break;
            }
 
            v = p + 1;
 
            ls = ngx_array_push(&cycle->listening);
            if (ls == NULL) {
                return NGX_ERROR;
            }
 
            ngx_memzero(ls, sizeof(ngx_listening_t));
 
            /* 将fd保存到ngx_listening_t结构数组上 */
            ls->fd = (ngx_socket_t) s;
        }
    }
 
    if (v != p) {
        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
                      "invalid socket number \"%s\" in " NGINX_VAR
                      " environment variable, ignoring", v);
    }
 
    /* 已经初始化要继承的socket */
    ngx_inherited = 1;
 
    return ngx_set_inherited_sockets(cycle);
}

ngx_signal信号处理

  • 当我们使用./nginx -s stop|reload|quit 类似这样的命令的时候,就是给Nginx发送了一个信号。
  • 信号可以控制Nginx进程的重启、停止、退出等操作。通过获取已经启动的Nginx的进程PID,对进程发送信号操作。
  • Nginx的进程ID pid一般会放置到/usr/local/nginx-1.4.7/nginx.pid
    /* 处理信号;例如./nginx -s stop,则处理Nginx的停止信号 */
    if (ngx_signal) {
        return ngx_signal_process(cycle, ngx_signal);
    }

ngx_signal变量是在ngx_get_options方法中。ngx_signal主要用于标识是否是属于信号操作。例如:./nginx -s stop|reload|quit

                /* ngx_signal 是否有信号  ./nginx -s stop|reload|quit */
            case 's':
                if (*p) {
                    ngx_signal = (char *) p;
 
                } else if (argv[++i]) {
                    ngx_signal = argv[i];
 
                } else {
                    ngx_log_stderr(0, "option \"-s\" requires parameter");
                    return NGX_ERROR;
                }
 
                if (ngx_strcmp(ngx_signal, "stop") == 0
                    || ngx_strcmp(ngx_signal, "quit") == 0
                    || ngx_strcmp(ngx_signal, "reopen") == 0
                    || ngx_strcmp(ngx_signal, "reload") == 0)
                {
                    ngx_process = NGX_PROCESS_SIGNALLER;
                    goto next;
                }
 
                ngx_log_stderr(0, "invalid option: \"-s %s\"", ngx_signal);
                return NGX_ERROR;


然后再看下ngx_signal_process的具体操作:

/**
 * 处理信号;
 * 例如./nginx -s stop,则处理Nginx的停止信号
 */
ngx_int_t
ngx_signal_process(ngx_cycle_t *cycle, char *sig)
{
    ssize_t           n;
    ngx_pid_t         pid;
    ngx_file_t        file;
    ngx_core_conf_t  *ccf;
    u_char            buf[NGX_INT64_LEN + 2];
 
    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "signal process started");
 
    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
 
    ngx_memzero(&file, sizeof(ngx_file_t));
 
    file.name = ccf->pid;
    file.log = cycle->log;
 
    /* 通过/usr/local/nginx/logs/nginx.pid 获取进行ID号 */
    file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,
                            NGX_FILE_OPEN, NGX_FILE_DEFAULT_ACCESS);
 
    if (file.fd == NGX_INVALID_FILE) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
                      ngx_open_file_n " \"%s\" failed", file.name.data);
        return 1;
    }
 
    n = ngx_read_file(&file, buf, NGX_INT64_LEN + 2, 0);
 
    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", file.name.data);
    }
 
    if (n == NGX_ERROR) {
        return 1;
    }
 
    while (n-- && (buf[n] == CR || buf[n] == LF)) { /* void */ }
 
    /* 最终得到PID */
    pid = ngx_atoi(buf, ++n);
 
    if (pid == (ngx_pid_t) NGX_ERROR) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
                      "invalid PID number \"%*s\" in \"%s\"",
                      n, buf, file.name.data);
        return 1;
    }
 
    /* 调用ngx_os_signal_process方法,处理真正的信号 */
    return ngx_os_signal_process(cycle, sig, pid);
 
}


/**
 * 处理信号
 */
ngx_int_t
ngx_os_signal_process(ngx_cycle_t *cycle, char *name, ngx_pid_t pid)
{
    ngx_signal_t  *sig;
 
    for (sig = signals; sig->signo != 0; sig++) {
        if (ngx_strcmp(name, sig->name) == 0) {
 
        	/* 通过系统调用向该进程发送信号 */
            if (kill(pid, sig->signo) != -1) {
                return 0;
            }
 
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "kill(%P, %d) failed", pid, sig->signo);
        }
    }
 
    return 1;
}

3、全局变量cycle初始化

  • Nginx的大部分初始化工作主要围绕一个类型为ngx_cycle_t类型的全局变量(cycle)展开。本文主要讲解cycle的数据结构以及初始化过程中干了什么事情。

  • cycle的初始化过程在/src/core/cycle.c文件中。

数据结构

ngx_cycle_t全局变量数据结构

cycle是Nginx贯穿全局的一个全局变量。阅读Nginx的源码必须得先搞清楚cycle全局变量的初始化过程。

/**
 * Nginx全局变量cycle
 */
struct ngx_cycle_s {
    void                  ****conf_ctx; /* 配置文件 上下文的数组,每个模块的配置信息*/
    ngx_pool_t               *pool;	/* 内存池地址 */
 
    ngx_log_t                *log;	/* 日志 */
    ngx_log_t                 new_log;
 
    ngx_uint_t                log_use_stderr;  /* unsigned  log_use_stderr:1; */
 
    ngx_connection_t        **files;	/* 连接文件句柄 */
    ngx_connection_t         *free_connections;	/* 空闲连接 */
    ngx_uint_t                free_connection_n;/* 空闲连接个数 */
 
    ngx_module_t            **modules;	/* 模块数组 */
    ngx_uint_t                modules_n;	/* 模块个数 */
    ngx_uint_t                modules_used;    /* unsigned  modules_used:1; */
 
    ngx_queue_t               reusable_connections_queue;
 
    ngx_array_t               listening;	/* 监听数组 */
    ngx_array_t               paths;		/* 路径数组 */
    ngx_array_t               config_dump;
    ngx_list_t                open_files;	/* 打开的文件 */
    ngx_list_t                shared_memory;/* 共享内存链表*/
 
    ngx_uint_t                connection_n;	/* 连接的个数*/
    ngx_uint_t                files_n;	/* 打开文件的个数 */
 
    ngx_connection_t         *connections;	/* 连接事件*/
    ngx_event_t              *read_events;	/* 读取事件*/
    ngx_event_t              *write_events; /* 写入事件*/
 
    ngx_cycle_t              *old_cycle;
 
    ngx_str_t                 conf_file;	/* 配置文件 */
    ngx_str_t                 conf_param;	/* 配置参数 */
    ngx_str_t                 conf_prefix;	/* 配置文件前缀*/
    ngx_str_t                 prefix;	/* 前缀*/
    ngx_str_t                 lock_file;	/* 锁文件*/
    ngx_str_t                 hostname;	/* 主机名称*/
};


配置文件ngx_core_conf_t的数据结构

ngx_code_conf_t的数据结构主要用于装在Nginx的nginx.conf的核心配置文件的参数。后面我们会具体讲解配置文件nginx.conf的解析过程以及和模块。

/**
 * 核心配置文件信息
 * 对应nginx.conf的
 * #user  nobody;
	worker_processes  1;
	#error_log  logs/error.log;
	#error_log  logs/error.log  notice;
	#error_log  logs/error.log  info;
	#pid        logs/nginx.pid;
 */
typedef struct {
    ngx_flag_t                daemon;
    ngx_flag_t                master;
 
    ngx_msec_t                timer_resolution;
 
    ngx_int_t                 worker_processes;
    ngx_int_t                 debug_points;
 
    ngx_int_t                 rlimit_nofile;
    off_t                     rlimit_core;
 
    int                       priority;
 
    ngx_uint_t                cpu_affinity_auto;
    ngx_uint_t                cpu_affinity_n;
    ngx_cpuset_t             *cpu_affinity;
 
    char                     *username;
    ngx_uid_t                 user;
    ngx_gid_t                 group;
 
    ngx_str_t                 working_directory;
    ngx_str_t                 lock_file;
 
    ngx_str_t                 pid;
    ngx_str_t                 oldpid;
 
    ngx_array_t               env;
    char                    **environment;
} ngx_core_conf_t;

初始化过程

1. 创建一个内存池

后续所有的内存都会被分配到这个内存池上面。

    /* 创建一块内存 */
    pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
    if (pool == NULL) {
        return NULL;
    }
    pool->log = log;
 
    cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t));
    if (cycle == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
 
    cycle->pool = pool;
    cycle->log = log;
    cycle->old_cycle = old_cycle;
2. 拷贝配置文件的路径前缀(/usr/local/nginx)

主要拷贝到 cycle->conf_prefix

      /* 配置文件路径拷贝 /usr/local/nginx/ */
    cycle->conf_prefix.len = old_cycle->conf_prefix.len;
    cycle->conf_prefix.data = ngx_pstrdup(pool, &old_cycle->conf_prefix);
    if (cycle->conf_prefix.data == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
3. Nginx的路径前缀拷贝

拷贝到cycle->prefix

    /* Nginx路径拷贝 */
    cycle->prefix.len = old_cycle->prefix.len;
    cycle->prefix.data = ngx_pstrdup(pool, &old_cycle->prefix);
    if (cycle->prefix.data == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
4. 拷贝配置文件信息

/nginx/conf/nginx.conf 文件路径

    /* 配置文件 信息拷贝*/
    cycle->conf_file.len = old_cycle->conf_file.len;
    cycle->conf_file.data = ngx_pnalloc(pool, old_cycle->conf_file.len + 1);
    if (cycle->conf_file.data == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
    ngx_cpystrn(cycle->conf_file.data, old_cycle->conf_file.data,
                old_cycle->conf_file.len + 1);
5. 拷贝配置参数信息
    /* 配置参数 信息拷贝 */
    cycle->conf_param.len = old_cycle->conf_param.len;
    cycle->conf_param.data = ngx_pstrdup(pool, &old_cycle->conf_param);
    if (cycle->conf_param.data == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
6. 路径信息初始化
    /* 路径信息 */
    n = old_cycle->paths.nelts ? old_cycle->paths.nelts : 10;
 
    cycle->paths.elts = ngx_pcalloc(pool, n * sizeof(ngx_path_t *));
    if (cycle->paths.elts == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
 
    cycle->paths.nelts = 0;
    cycle->paths.size = sizeof(ngx_path_t *);
    cycle->paths.nalloc = n;
    cycle->paths.pool = pool;
 
 
    if (ngx_array_init(&cycle->config_dump, pool, 1, sizeof(ngx_conf_dump_t))
        != NGX_OK)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }
7. 初始化打开的文件句柄
    /* 初始化打开的文件句柄 */
    if (old_cycle->open_files.part.nelts) {
        n = old_cycle->open_files.part.nelts;
        for (part = old_cycle->open_files.part.next; part; part = part->next) {
            n += part->nelts;
        }
 
    } else {
        n = 20;
    }
 
    if (ngx_list_init(&cycle->open_files, pool, n, sizeof(ngx_open_file_t))
        != NGX_OK)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }
8. 初始化shared_memory链表
    /* 初始化shared_memory链表 */
    if (old_cycle->shared_memory.part.nelts) {
        n = old_cycle->shared_memory.part.nelts;
        for (part = old_cycle->shared_memory.part.next; part; part = part->next)
        {
            n += part->nelts;
        }
 
    } else {
        n = 1;
    }
 
    if (ngx_list_init(&cycle->shared_memory, pool, n, sizeof(ngx_shm_zone_t))
        != NGX_OK)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }
9. 初始化listening数组
    /* 初始化listening */
    n = old_cycle->listening.nelts ? old_cycle->listening.nelts : 10;
 
    cycle->listening.elts = ngx_pcalloc(pool, n * sizeof(ngx_listening_t));
    if (cycle->listening.elts == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
 
    cycle->listening.nelts = 0;
    cycle->listening.size = sizeof(ngx_listening_t);
    cycle->listening.nalloc = n;
    cycle->listening.pool = pool;
10. 模块创建和核心配置结构初始化ngx_core_conf_t
  • 主要初始化cycle->modules
  • 创建核心配置结构,Nginx的核心配置会放到ngx_core_conf_t *ccf数据结构上
  • 初始化核心配置结构ngx_core_conf_t *
 /* 创建模块以及创建模块的配置信息 */
    if (ngx_cycle_modules(cycle) != NGX_OK) {
        ngx_destroy_pool(pool);
        return NULL;
    }
 
    /*
     * 核心模块的配置文件创建
     * 配置创建调用nginx.c 中的 ngx_core_module_create_conf
     * */
    for (i = 0; cycle->modules[i]; i++) {
        if (cycle->modules[i]->type != NGX_CORE_MODULE) {
            continue;
        }
 
        module = cycle->modules[i]->ctx;
 
        if (module->create_conf) {
            rv = module->create_conf(cycle); //模块回调函数,创建模块的配置信息
            if (rv == NULL) {
                ngx_destroy_pool(pool);
                return NULL;
            }
            cycle->conf_ctx[cycle->modules[i]->index] = rv; //配置文件复制
        }
    }
 
    /* 获取核心配置文件的数据结构 ngx_core_conf_t */
    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
11. 配置文件nginx.conf解析
  /* 解析配置文件 */
    ngx_memzero(&conf, sizeof(ngx_conf_t));
    /* STUB: init array ? */
    conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));
    if (conf.args == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
 
    /* 创建一个临时的内存池,后面会清空掉;conf也主要用于解析配置文件的临时变量 */
    conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
    if (conf.temp_pool == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
 
 
    conf.ctx = cycle->conf_ctx;
    conf.cycle = cycle;
    conf.pool = pool;
    conf.log = log;
    conf.module_type = NGX_CORE_MODULE;
    conf.cmd_type = NGX_MAIN_CONF;
 
#if 0
    log->log_level = NGX_LOG_DEBUG_ALL;
#endif
 
    /* 解析命令行中的配置参数;例如:nginx -t -c /usr/local/nginx/conf/nginx.conf */
    if (ngx_conf_param(&conf) != NGX_CONF_OK) {
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }
 
    /* 解析配置文件/usr/local/nginx/conf/nginx.conf 信息 */
    if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }
 
    if (ngx_test_config && !ngx_quiet_mode) {
        ngx_log_stderr(0, "the configuration file %s syntax is ok",
                       cycle->conf_file.data);
    }
12. 创建PID文件
  • Nginx将PID写入文件内,/usr/local/nginx-1.4.7/nginx.pid,后续对Nginx进行重启、停止、信号操作就可以使用这个PID了。
 /* 创建pid文件,/usr/local/nginx-1.4.7/nginx.pid*/
    if (ngx_test_config) {
 
        if (ngx_create_pidfile(&ccf->pid, log) != NGX_OK) {
            goto failed;
        }
 
    } else if (!ngx_is_init_cycle(old_cycle)) {
 
        /*
         * we do not create the pid file in the first ngx_init_cycle() call
         * because we need to write the demonized process pid
         */
 
        old_ccf = (ngx_core_conf_t *) ngx_get_conf(old_cycle->conf_ctx,
                                                   ngx_core_module);
        if (ccf->pid.len != old_ccf->pid.len
            || ngx_strcmp(ccf->pid.data, old_ccf->pid.data) != 0)
        {
            /* new pid file name */
 
            if (ngx_create_pidfile(&ccf->pid, log) != NGX_OK) {
                goto failed;
            }
 
            ngx_delete_pidfile(old_cycle);
        }
    }
13. 遍历cycle->open_files链表中的文件,并且打开

cycle->open_file主要是在ngx_conf_open_file这个方法里面放入文件路径,然后下面去打开文件。

主要是日志文件和配置文件。

    /* 打开日志,并调用ngx_conf_open_file方法,会将打开的文件放到cycle->open_files链表中 主要是日志文件和配置文件*/
    if (ngx_log_open_default(cycle) != NGX_OK) {
        goto failed;
    }
    /* 遍历cycle->open_files链表中的文件,并打开 */
    part = &cycle->open_files.part;
    file = part->elts;
 
    for (i = 0; /* void */ ; i++) {
 
        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }
            part = part->next;
            file = part->elts;
            i = 0;
        }
 
        if (file[i].name.len == 0) {
            continue;
        }
 
        file[i].fd = ngx_open_file(file[i].name.data,
                                   NGX_FILE_APPEND,
                                   NGX_FILE_CREATE_OR_OPEN,
                                   NGX_FILE_DEFAULT_ACCESS);
 
        ngx_log_debug3(NGX_LOG_DEBUG_CORE, log, 0,
                       "log: %p %d \"%s\"",
                       &file[i], file[i].fd, file[i].name.data);
 
        if (file[i].fd == NGX_INVALID_FILE) {
            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                          ngx_open_file_n " \"%s\" failed",
                          file[i].name.data);
            goto failed;
        }
 
#if !(NGX_WIN32)
        if (fcntl(file[i].fd, F_SETFD, FD_CLOEXEC) == -1) {
            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                          "fcntl(FD_CLOEXEC) \"%s\" failed",
                          file[i].name.data);
            goto failed;
        }
#endif
    }
14. 创建共享内存并初始化

新旧shared_memory链表的比较,相同的共享内存保留,旧的不同的共享内存被释放,新的被创建

   /* 创建共享内存并初始化 */
    part = &cycle->shared_memory.part;
    shm_zone = part->elts;
 
    for (i = 0; /* void */ ; i++) {
 
        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }
            part = part->next;
            shm_zone = part->elts;
            i = 0;
        }
 
        if (shm_zone[i].shm.size == 0) {
            ngx_log_error(NGX_LOG_EMERG, log, 0,
                          "zero size shared memory zone \"%V\"",
                          &shm_zone[i].shm.name);
            goto failed;
        }
 
        shm_zone[i].shm.log = cycle->log;
 
        opart = &old_cycle->shared_memory.part;
        oshm_zone = opart->elts;
 
        for (n = 0; /* void */ ; n++) {
 
            if (n >= opart->nelts) {
                if (opart->next == NULL) {
                    break;
                }
                opart = opart->next;
                oshm_zone = opart->elts;
                n = 0;
            }
 
            if (shm_zone[i].shm.name.len != oshm_zone[n].shm.name.len) {
                continue;
            }
 
            if (ngx_strncmp(shm_zone[i].shm.name.data,
                            oshm_zone[n].shm.name.data,
                            shm_zone[i].shm.name.len)
                != 0)
            {
                continue;
            }
 
            if (shm_zone[i].tag == oshm_zone[n].tag
                && shm_zone[i].shm.size == oshm_zone[n].shm.size
                && !shm_zone[i].noreuse)
            {
                shm_zone[i].shm.addr = oshm_zone[n].shm.addr;
#if (NGX_WIN32)
                shm_zone[i].shm.handle = oshm_zone[n].shm.handle;
#endif
 
                if (shm_zone[i].init(&shm_zone[i], oshm_zone[n].data)
                    != NGX_OK)
                {
                    goto failed;
                }
 
                goto shm_zone_found;
            }
 
            ngx_shm_free(&oshm_zone[n].shm);
 
            break;
        }
15. 处理listening数组,并开始监听socket
  /* handle the listening sockets */
    /* 处理listening数组,并开始监听socket */
    if (old_cycle->listening.nelts) {
        ls = old_cycle->listening.elts;
        for (i = 0; i < old_cycle->listening.nelts; i++) {
            ls[i].remain = 0;
        }
 
        nls = cycle->listening.elts;
        for (n = 0; n < cycle->listening.nelts; n++) {
 
            for (i = 0; i < old_cycle->listening.nelts; i++) {
                if (ls[i].ignore) {
                    continue;
                }
 
                if (ls[i].remain) {
                    continue;
                }
 
                if (ls[i].type != nls[n].type) {
                    continue;
                }
 
                if (ngx_cmp_sockaddr(nls[n].sockaddr, nls[n].socklen,
                                     ls[i].sockaddr, ls[i].socklen, 1)
                    == NGX_OK)
                {
                    nls[n].fd = ls[i].fd;
                    nls[n].previous = &ls[i];
                    ls[i].remain = 1;
 
                    if (ls[i].backlog != nls[n].backlog) {
                        nls[n].listen = 1;
                    }
 
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
 
                    /*
                     * FreeBSD, except the most recent versions,
                     * could not remove accept filter
                     */
                    nls[n].deferred_accept = ls[i].deferred_accept;
 
                    if (ls[i].accept_filter && nls[n].accept_filter) {
                        if (ngx_strcmp(ls[i].accept_filter,
                                       nls[n].accept_filter)
                            != 0)
                        {
                            nls[n].delete_deferred = 1;
                            nls[n].add_deferred = 1;
                        }
 
                    } else if (ls[i].accept_filter) {
                        nls[n].delete_deferred = 1;
 
                    } else if (nls[n].accept_filter) {
                        nls[n].add_deferred = 1;
                    }
#endif
 
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
 
                    if (ls[i].deferred_accept && !nls[n].deferred_accept) {
                        nls[n].delete_deferred = 1;
 
                    } else if (ls[i].deferred_accept != nls[n].deferred_accept)
                    {
                        nls[n].add_deferred = 1;
                    }
#endif
 
#if (NGX_HAVE_REUSEPORT)
                    if (nls[n].reuseport && !ls[i].reuseport) {
                        nls[n].add_reuseport = 1;
                    }
#endif
 
                    break;
                }
            }
 
            if (nls[n].fd == (ngx_socket_t) -1) {
                nls[n].open = 1;
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
                if (nls[n].accept_filter) {
                    nls[n].add_deferred = 1;
                }
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
                if (nls[n].deferred_accept) {
                    nls[n].add_deferred = 1;
                }
#endif
            }
        }
 
    } else {
        ls = cycle->listening.elts;
        for (i = 0; i < cycle->listening.nelts; i++) {
            ls[i].open = 1;
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
            if (ls[i].accept_filter) {
                ls[i].add_deferred = 1;
            }
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
            if (ls[i].deferred_accept) {
                ls[i].add_deferred = 1;
            }
#endif
        }
    }
 
    if (ngx_open_listening_sockets(cycle) != NGX_OK) {
        goto failed;
    }
 
    if (!ngx_test_config) {
        ngx_configure_listening_sockets(cycle);
    }
 
16. 关闭或删除残留在old_cycle中的资源
  • 释放多余的共享内存;
  • 关闭多余的侦听sockets;
  • 关闭多余的打开文件
 /* close and delete stuff that lefts from an old cycle */
 
    /* free the unnecessary shared memory */
    /* 关闭或删除残留在old_cycle中的资源 释放多余的共享内存;关闭多余的侦听sockets;关闭多余的打开文件*/
    opart = &old_cycle->shared_memory.part;
    oshm_zone = opart->elts;
 
    for (i = 0; /* void */ ; i++) {
 
        if (i >= opart->nelts) {
            if (opart->next == NULL) {
                goto old_shm_zone_done;
            }
            opart = opart->next;
            oshm_zone = opart->elts;
            i = 0;
        }
 
        part = &cycle->shared_memory.part;
        shm_zone = part->elts;
 
        for (n = 0; /* void */ ; n++) {
 
            if (n >= part->nelts) {
                if (part->next == NULL) {
                    break;
                }
                part = part->next;
                shm_zone = part->elts;
                n = 0;
            }
 
            if (oshm_zone[i].shm.name.len == shm_zone[n].shm.name.len
                && ngx_strncmp(oshm_zone[i].shm.name.data,
                               shm_zone[n].shm.name.data,
                               oshm_zone[i].shm.name.len)
                == 0)
            {
                goto live_shm_zone;
            }
        }
 
        ngx_shm_free(&oshm_zone[i].shm);

4、 模块的初始化

Nginx是高度模块化的,各个功能都会封装在模块中。例如core模块、HTTP模块等。也可以自定义模块。

这一篇文章主要讲解模块的初始化。后续会有一篇文章教你如何编写Nginx的模块。

Nginx实现模块管理的代码主要在:/src/core/ngx_module.c文件中。

模块的数据结构

1. ngx_module_s 模块结构

结构体ngx_module_s主要用于管理每一个模块的详细信息。

Nginx的所有模块会放置在全局变量cycle的cycle->modules 模块数组。通过这个数组,我们就可以拿到每个模块的具体信息。

/**
 * 业务模块数据结构
 */
struct ngx_module_s {
    ngx_uint_t            ctx_index;
    ngx_uint_t            index; /* 模块的唯一标识符号 */
 
    char                 *name;  /* 模块名称 */
 
    ngx_uint_t            spare0;
    ngx_uint_t            spare1;
 
    ngx_uint_t            version;	/* 模块版本 */
    const char           *signature;
 
    void                 *ctx;	/* 模块上下文 */
    ngx_command_t        *commands; /* 模块支持的命令集 */
    ngx_uint_t            type;	/* 模块类型 */
 
    /* 回调函数 */
    ngx_int_t           (*init_master)(ngx_log_t *log); /* 主进程初始化的时候调用 */
 
    ngx_int_t           (*init_module)(ngx_cycle_t *cycle); /* 模块初始化的时候调用 */
 
    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);  /* 工作进程初始化时调用*/
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle); /* 线程初始化调用*/
    void                (*exit_thread)(ngx_cycle_t *cycle);	/* 线程退出调用*/
    void                (*exit_process)(ngx_cycle_t *cycle); /* 工作进程退出调用*/
 
    void                (*exit_master)(ngx_cycle_t *cycle);
 
    uintptr_t             spare_hook0;
    uintptr_t             spare_hook1;
    uintptr_t             spare_hook2;
    uintptr_t             spare_hook3;
    uintptr_t             spare_hook4;
    uintptr_t             spare_hook5;
    uintptr_t             spare_hook6;
    uintptr_t             spare_hook7;
};

说明:

  1. index 主要用于模块的标识。

cycle->conf_ctx主要存储的是各个模块的配置文件结构的指针地址。
cycle->conf_ctx中获取各个模块配置信息都是通过模块的标识来确定数组位置的。
例如核心模块是放在一个ngx_core_conf_t的数据结构上的。而ngx_core_conf_t这个指针就通过index索引值放在cycle->conf_ctx数组中。

获取核心模块的配置信息:

  ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
  1. commands主要用于配置模块的命令集。Nginx的配置文件都是通过commands命令集来逐个解析具体定义好的配置信息(每个模块不一样)。下一章会详解解读

  2. ctx 模块上下文。主要放置一个模块自定义的结构。例如核心模块就是ngx_core_module_t的结构。ngx_core_module_t中可以自定义一些方法或者参数。

  3. type。模块类型。

  4. init_module:初始化模块的时候会回调的函数。

2. ngx_core_module 核心模块
  • 核心模块在nginx.c的头部就定义了。定义了 ngx_core_module核心模块和核心模块的上下文ngx_core_module_ctx
  • ngx_core_module_t 为核心模块的上下文结构。主要用于核心模块的配置文件创建
  • ngx_core_module_create_conf和初始化ngx_core_module_init_conf。放置在ngx_module_s->ctx。
/**
 * 核心模块core数据结构
 * ngx_module_s->ctx 核心模块的上下文,主要定义了创建配置和初始化配置的结构
 */
typedef struct {
    ngx_str_t             name;
    void               *(*create_conf)(ngx_cycle_t *cycle);
    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;
/**
 * 核心模块配置文件
 * ngx_core_module_create_conf 核心模块创建配置文件
 * ngx_core_module_init_conf 核心模块初始化配置文件
 */
static ngx_core_module_t  ngx_core_module_ctx = {
    ngx_string("core"),
    ngx_core_module_create_conf,
    ngx_core_module_init_conf
};
 
/**
 * 核心模块
 */
ngx_module_t  ngx_core_module = {
    NGX_MODULE_V1,
    &ngx_core_module_ctx,                  /* module context */
    ngx_core_commands,                     /* module directives */
    NGX_CORE_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

模块的初始化

1. 模块编号处理ngx_preinit_modules

在nginx.c的main函数中,第一步对模块的处理就是进行编号处理。仅仅是编号,不做任何其他处理。
nginx.c:

    /* 初始化所有模块;并对所有模块进行编号处理;
     * ngx_modules数却是在自动编译的时候生成的,位于objs/ngx_modules.c文件中   */
    if (ngx_preinit_modules() != NGX_OK) {
        return 1;
    }

ngx_module.c 具体函数实现

/**
 * 初始化所有模块;并对所有模块进行编号处理;
 */
ngx_int_t
ngx_preinit_modules(void)
{
    ngx_uint_t  i;
 
    for (i = 0; ngx_modules[i]; i++) {
        ngx_modules[i]->index = i;
        ngx_modules[i]->name = ngx_module_names[i];
    }
 
    ngx_modules_n = i;
    ngx_max_module = ngx_modules_n + NGX_MAX_DYNAMIC_MODULES;
 
    return NGX_OK;
}

我们可以看到,模块的个数是通过ngx_modules的数组得到的。

ngx_modules是一个引用外部的变量。在ngx_modules.h中

/* 模块数组,所有的模块都会保存在此数组中   共有四种类型模块:"CORE","CONF","EVNT","HTTP" */
extern ngx_module_t  *ngx_modules[];

而ngx_modules的模块到底是什么时候确定的呢?

具体的模块可通过编译前的configure命令进行配置,即设置哪些模块需要编译,哪些不被编译。当编译的时候,会生成ngx_modules.c的文件,里面就包含模块数组。
新增模块或者减少模块可以在configure命令执行前 auto/modules文件里面修改。
生成的objs/ngx_modules.c文件如下:

00001:
00002: #include <ngx_config.h>
00003: #include <ngx_core.h>
00004:
00005:
00006:
00007: extern ngx_module_t ngx_core_module;
00008: extern ngx_module_t ngx_errlog_module;
00009: extern ngx_module_t ngx_conf_module;
00010: extern ngx_module_t ngx_events_module;
00011: extern ngx_module_t ngx_event_core_module;
00012: extern ngx_module_t ngx_epoll_module;
00013: extern ngx_module_t ngx_http_module;
00014: extern ngx_module_t ngx_http_core_module;
00015: extern ngx_module_t ngx_http_log_module;
00016: extern ngx_module_t ngx_http_upstream_module;
00017: extern ngx_module_t ngx_http_static_module;
00018: extern ngx_module_t ngx_http_autoindex_module;
00019: extern ngx_module_t ngx_http_index_module;
00020: extern ngx_module_t ngx_http_auth_basic_module;
00021: extern ngx_module_t ngx_http_access_module;
00022: extern ngx_module_t ngx_http_limit_zone_module;
00023: extern ngx_module_t ngx_http_limit_req_module;
00024: extern ngx_module_t ngx_http_geo_module;
00025: extern ngx_module_t ngx_http_map_module;
00026: extern ngx_module_t ngx_http_split_clients_module;
00027: extern ngx_module_t ngx_http_referer_module;
00028: extern ngx_module_t ngx_http_rewrite_module;
00029: extern ngx_module_t ngx_http_proxy_module;
00030: extern ngx_module_t ngx_http_fastcgi_module;
00031: extern ngx_module_t ngx_http_uwsgi_module;
00032: extern ngx_module_t ngx_http_scgi_module;
00033: extern ngx_module_t ngx_http_memcached_module;
00034: extern ngx_module_t ngx_http_empty_gif_module;
00035: extern ngx_module_t ngx_http_browser_module;
00036: extern ngx_module_t ngx_http_upstream_ip_hash_module;
00037: extern ngx_module_t ngx_http_stub_status_module;
00038: extern ngx_module_t ngx_http_write_filter_module;
00039: extern ngx_module_t ngx_http_header_filter_module;
00040: extern ngx_module_t ngx_http_chunked_filter_module;
00041: extern ngx_module_t ngx_http_range_header_filter_module;
00042: extern ngx_module_t ngx_http_gzip_filter_module;
00043: extern ngx_module_t ngx_http_postpone_filter_module;
00044: extern ngx_module_t ngx_http_ssi_filter_module;
00045: extern ngx_module_t ngx_http_charset_filter_module;
00046: extern ngx_module_t ngx_http_userid_filter_module;
00047: extern ngx_module_t ngx_http_headers_filter_module;
00048: extern ngx_module_t ngx_http_copy_filter_module;
00049: extern ngx_module_t ngx_http_range_body_filter_module;
00050: extern ngx_module_t ngx_http_not_modified_filter_module;
00051:
2. 初始化cycle->modules 分配内存

主要在cycle->modules上分配一块用于存放ngx_module_s数据结构的列表内存。
并且将原来的 ngx_modules拷贝到cycle->modules上。

ngx_cycle.c

    /* 创建模块以及创建模块的配置信息 */
    if (ngx_cycle_modules(cycle) != NGX_OK) {
        ngx_destroy_pool(pool);
        return NULL;
    }

ngx_modules.c

/**
 * ngx_init_cycle 在初始化cycle的时候,初始化模块
 * 创建一个列表,并将静态的模块拷贝到列表上
 */
ngx_int_t
ngx_cycle_modules(ngx_cycle_t *cycle)
{
    /*
     * create a list of modules to be used for this cycle,
     * copy static modules to it
     */
 
    cycle->modules = ngx_pcalloc(cycle->pool, (ngx_max_module + 1)
                                              * sizeof(ngx_module_t *));
    if (cycle->modules == NULL) {
        return NGX_ERROR;
    }
 
    ngx_memcpy(cycle->modules, ngx_modules,
               ngx_modules_n * sizeof(ngx_module_t *));
 
    cycle->modules_n = ngx_modules_n;
 
    return NGX_OK;
}
3. 每个模块进行初始化ngx_init_modules

ngx_module_s结构中定义了init_module的模块初始化回调函数。
ngx_init_modules主要用于每个模块的初始化工作。
在编写自定义模块的时候,可以定义init_module方法,主要用于这个模块的初始化工作。
ngx_cycle.c

    /* 调用每个模块的初始化函数 */
    if (ngx_init_modules(cycle) != NGX_OK) {
        /* fatal */
        exit(1);
    }
4. 统计有多少个模块ngx_count_modules
/**
 * 统计每个类型(type)下面总共多少个模块
 */
ngx_int_t
ngx_count_modules(ngx_cycle_t *cycle, ngx_uint_t type)
{
    ngx_uint_t     i, next, max;
    ngx_module_t  *module;
 
    next = 0;
    max = 0;
 
    /* count appropriate modules, set up their indices */
 
    for (i = 0; cycle->modules[i]; i++) {
        module = cycle->modules[i];
 
        if (module->type != type) {
            continue;
        }
 
        if (module->ctx_index != NGX_MODULE_UNSET_INDEX) {
 
            /* if ctx_index was assigned, preserve it */
 
            if (module->ctx_index > max) {
                max = module->ctx_index;
            }
 
            if (module->ctx_index == next) {
                next++;
            }
 
            continue;
        }
 
        /* search for some free index */
 
        module->ctx_index = ngx_module_ctx_index(cycle, type, next);
 
        if (module->ctx_index > max) {
            max = module->ctx_index;
        }
 
        next = module->ctx_index + 1;
    }
 
    /*
     * make sure the number returned is big enough for previous
     * cycle as well, else there will be problems if the number
     * will be stored in a global variable (as it's used to be)
     * and we'll have to roll back to the previous cycle
     */
 
    if (cycle->old_cycle && cycle->old_cycle->modules) {
 
        for (i = 0; cycle->old_cycle->modules[i]; i++) {
            module = cycle->old_cycle->modules[i];
 
            if (module->type != type) {
                continue;
            }
 
            if (module->ctx_index > max) {
                max = module->ctx_index;
            }
        }
    }
 
    /* prevent loading of additional modules */
 
    cycle->modules_used = 1;
 
    return max + 1;
}
5. 模块的工作进程初始化init_process
  • 在nginx_process_cycle.c文件的ngx_worker_process_init方法(Nginx是多进程模式的,此方法是每个进程进行自己的初始化工作)中包含模块的进程初始化。
  • 模块进程初始化,主要回调模块数据结构中的init_process回调函数。
	/* 对模块进程初始化 - 这边初始化的是所有的模块有init_process回调函数的进行初始化工作 */
	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);
			}
		}
	}

5、解析配置文件

Nginx源码中比较重要的一块就是配置文件的解析。一般是解析/usr/local/nginx/conf/nginx.conf文件中的配置信息。

前一篇文章,我们介绍了Nginx的模块化。
Nginx的功能模块都是通过cycle->modules 模块进行管理的。
而每个模块都会有自己的配置文件。

Nginx的配置文件nginx.conf

Nginx的配置文件每一行就是一条命令。
最外层的为核心模块的配置参数(类型:NGX_CORE_MODULE);内部嵌套的为各个子模块的配置。

  • events {} 为事件模块(类型:NGX_EVENT_MODULE)
  • http {} 为HTTP模块 (类型:NGX_HTTP_MODULE)
    http模块内还会嵌套多层
    多层嵌套会在后续的event模块中介绍。这里只介绍最顶层的core模块的解析。
#user  nobody;
worker_processes  1;
 
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
 
#pid        logs/nginx.pid;
 
 
events {
    worker_connections  1024;
}
 
 
http {
    include       mime.types;
    default_type  application/octet-stream;
 
    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';
 
    #access_log  logs/access.log  main;
 
    sendfile        on;
    #tcp_nopush     on;
 
    #keepalive_timeout  0;
    keepalive_timeout  65;
 
    #gzip  on;
 
    server {
        listen       80;
        server_name  localhost;
 
        #charset koi8-r;
 
        #access_log  logs/host.access.log  main;
 
        location / {
            root   html;
            index  index.html index.htm;
        }
 
        #error_page  404              /404.html;
 
        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
 
        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}
 
        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}
 
        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }
 
 
    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;
 
    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}
 
 
    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;
 
    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;
 
    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;
 
    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;
 
    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}
 
}

数据结构逻辑关系图

在这里插入图片描述

相关数据结构

1. ngx_cycle_s的conf_ctx和modules

cycle->conf_ctx :配置文件上下文数组。每个模块的配置文件数据结构的指针地址都会按照模块的index索引放置在cycle->conf_ctx数组中。

cycle->modules:模块数组。模块也会按照模块的索引index放在cycle->modules数组上。具体可以参考《Nginx源码分析 - 主流程篇 - 模块的初始化 》中ngx_cycle_modules方法。

/**
 * Nginx全局变量cycle
 */
struct ngx_cycle_s {
    void                  ****conf_ctx; /* 配置文件 上下文的数组,每个模块的配置信息*/
    ngx_pool_t               *pool;	/* 内存池地址 */
   
    ...
    ngx_module_t            **modules;	/* 模块数组 */
    ngx_uint_t                modules_n;	/* 模块个数 */
    ngx_uint_t                modules_used;    /* unsigned  modules_used:1; */
    ...
};
2. ngx_module_s的index和commands

index:是模块的索引值。

commands:模块支持的命令集。主要用于将配置信息设置到每个模块的配置文件数据结构上(例如核心模块的ngx_core_conf_t)。

/**
 * 业务模块数据结构
 */
struct ngx_module_s {
    ngx_uint_t            ctx_index;
    ngx_uint_t            index; /* 模块的唯一标识符号 */
 
    char                 *name;  /* 模块名称 */
    ...
    void                 *ctx;	/* 模块上下文 */
    ngx_command_t        *commands; /* 模块支持的命令集 */
    ngx_uint_t            type;	/* 模块类型 */
    ...
};
3. ngx_command_s 命令集的结构

set:为回调函数。最终设置值的时候,都会调用set的回调函数。

/**
 * 模块支持的命令集结构
 */
struct ngx_command_s {
    ngx_str_t             name; /* 命令名称 */
    ngx_uint_t            type; /* 命令类别 */
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); /* set回调函数 */
    ngx_uint_t            conf;
    ngx_uint_t            offset; /* 偏移量,命令长度 */
    void                 *post; /* 支持的回调方法;大多数情况为NULL*/
};

核心模块的定义

从上面的数据结构我们知道,每个模块都必须定义一个模块的数据结构ngx_module_s,主要用于管理每个模块的具体信息;每个模块也会定义一个ngx_command_t数组,主要用于存放需要解析的命令集的规则

而最终的配置文件信息,由于每个模块的配置结构不同,所以在cycle->conf_ctx只保存每个模块配置文件数据结构的指针地址

1. 核心模块在nginx.c的文件头部

核心模块

/**
 * 定义核心配置模块命令集ngx_command_t结构
 */
static ngx_command_t  ngx_core_commands[] = {
 
    { ngx_string("daemon"), /* 命令名称 */
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, /* 类型 */
      ngx_conf_set_flag_slot, /* 回调方法 */
      0,
      offsetof(ngx_core_conf_t, daemon), /* 偏移量;使用这个偏移量后,可以参考 ngx_core_module_create_conf*/
      NULL },
 
    { ngx_string("master_process"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      0,
      offsetof(ngx_core_conf_t, master),
      NULL },
 
    { ngx_string("timer_resolution"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_msec_slot,
      0,
      offsetof(ngx_core_conf_t, timer_resolution),
      NULL },
 
    { ngx_string("pid"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_str_slot,
      0,
      offsetof(ngx_core_conf_t, pid),
      NULL },
 
    { ngx_string("lock_file"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_str_slot,
      0,
      offsetof(ngx_core_conf_t, lock_file),
      NULL },
 
    { ngx_string("worker_processes"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_set_worker_processes,
      0,
      0,
      NULL },
 
    { ngx_string("debug_points"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_enum_slot,
      0,
      offsetof(ngx_core_conf_t, debug_points),
      &ngx_debug_points },
 
    { ngx_string("user"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE12,
      ngx_set_user,
      0,
      0,
      NULL },
 
    { ngx_string("worker_priority"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_set_priority,
      0,
      0,
      NULL },
 
    { ngx_string("worker_cpu_affinity"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_1MORE,
      ngx_set_cpu_affinity,
      0,
      0,
      NULL },
 
    { ngx_string("worker_rlimit_nofile"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      0,
      offsetof(ngx_core_conf_t, rlimit_nofile),
      NULL },
 
    { ngx_string("worker_rlimit_core"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_off_slot,
      0,
      offsetof(ngx_core_conf_t, rlimit_core),
      NULL },
 
    { ngx_string("working_directory"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_str_slot,
      0,
      offsetof(ngx_core_conf_t, working_directory),
      NULL },
 
    { ngx_string("env"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_set_env,
      0,
      0,
      NULL },
 
    { ngx_string("load_module"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_load_module,
      0,
      0,
      NULL },
 
      ngx_null_command
};
 
/**
 * 核心模块上下文
 * ngx_core_module_create_conf 核心模块创建配置文件
 * ngx_core_module_init_conf 核心模块初始化配置文件
 */
static ngx_core_module_t  ngx_core_module_ctx = {
    ngx_string("core"),
    ngx_core_module_create_conf,
    ngx_core_module_init_conf
};
 
/**
 * 核心模块
 */
ngx_module_t  ngx_core_module = {
    NGX_MODULE_V1,
    &ngx_core_module_ctx,                  /* module context */
    ngx_core_commands,                     /* module directives */
    NGX_CORE_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

说明:

  • ngx_core_module 核心模块。主要管理核心模块的信息。
  • ngx_core_module_ctx 核心模块的上下文。每个模块的上下文都不同,所以模块结构中只给了一个void的指针类型,可以指向不同的数据结构。核心模块的ngx_core_module_ctx主要定义了- ngx_core_module_create_conf和ngx_core_module_init_conf回调函数(创建和初始化配置文件)
  • ngx_core_commands 核心模块定义的命令集。当nginx.conf中的命令被拆简后,会通过这个命令集,逐个将核心模块的命令赋值到核心模块的配置文件数据结构上。
2. 核心模块的配置结构ngx_core_conf_t

核心模块的配置结构ngx_core_conf_t。
ngx_core_conf_t的指针地址会按照模块的index索引,放在cycle->conf_ctx数组中。

ngx_core_conf_t结构的创建和初始化。ngx_core_module_create_conf和ngx_core_module_init_conf方法。

/**
 * 核心配置文件信息
 * 对应nginx.conf的
 * #user  nobody;
	worker_processes  1;
	#error_log  logs/error.log;
	#error_log  logs/error.log  notice;
	#error_log  logs/error.log  info;
	#pid        logs/nginx.pid;
 */
typedef struct {
    ngx_flag_t                daemon;
    ngx_flag_t                master;
 
    ngx_msec_t                timer_resolution;
 
    ngx_int_t                 worker_processes;
    ngx_int_t                 debug_points;
 
    ngx_int_t                 rlimit_nofile;
    off_t                     rlimit_core;
 
    int                       priority;
 
    ngx_uint_t                cpu_affinity_auto;
    ngx_uint_t                cpu_affinity_n;
    ngx_cpuset_t             *cpu_affinity;
 
    char                     *username;
    ngx_uid_t                 user;
    ngx_gid_t                 group;
 
    ngx_str_t                 working_directory;
    ngx_str_t                 lock_file;
 
    ngx_str_t                 pid;
    ngx_str_t                 oldpid;
 
    ngx_array_t               env;
    char                    **environment;
} ngx_core_conf_t;

具体解析流程

我们这边主要讲解核心模块配置信息的解析。
通过对核心模块的解析流程的理解,能更好的帮助你了解整个Nginx的模块管理和配置管理的流程。
HTTP等模块的配置文件解析会更加复杂一些,但是基本原理是一致的。

1. 创建核心模块配置文件数据结构ngx_core_conf_t

ngx_init_cycle全局变量的初始化中会初始化Nginx的核心模块的配置信息。核心模块的配置参数在nginx.conf文件中最顶层的一些参数配置。

下面这段代码我们可以看到,遍历模块数组,如果是核心模块,则获取核心模块的上下文cycle->modules[i]->ctx,核心模块上下文是一个自定义的数据结构ngx_core_module_ctx,里面包含了配置文件创建的回调函数ngx_core_module_create_conf

PS:这边只是针对性NGX_CORE_MODULE进行创建配置文件和初始化配置文件。

    /*
     * 核心模块的配置文件创建
     * 配置创建调用nginx.c 中的 ngx_core_module_create_conf
     * 以及其他核心模块的init_conf,例如:ngx_event_core_module_ctx中的ngx_event_core_create_conf
     * */
    for (i = 0; cycle->modules[i]; i++) {
        if (cycle->modules[i]->type != NGX_CORE_MODULE) {
            continue;
        }
 
        module = cycle->modules[i]->ctx;
 
        if (module->create_conf) {
            rv = module->create_conf(cycle); //模块回调函数,创建模块的配置信息
            if (rv == NULL) {
                ngx_destroy_pool(pool);
                return NULL;
            }
            cycle->conf_ctx[cycle->modules[i]->index] = rv; //配置文件复制
        }
    }
2. 创建一个临时的ngx_conf_s结构

ngx_conf_s主要定义了需要解析的配置文件,需要解析的模块类型以及命令集类型的信息。辅助解析配置文件。

    /* 解析配置文件 */
    ngx_memzero(&conf, sizeof(ngx_conf_t));
    /* STUB: init array ? */
    conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));
    if (conf.args == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
 
    /* 创建一个临时的内存池,后面会清空掉;conf也主要用于解析配置文件的临时变量 */
    conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
    if (conf.temp_pool == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
 
 
    conf.ctx = cycle->conf_ctx;
    conf.cycle = cycle;
    conf.pool = pool;
    conf.log = log;
    conf.module_type = NGX_CORE_MODULE; /* 配置文件模块类型 */
    conf.cmd_type = NGX_MAIN_CONF; /* 命令集类型 */
3. 调用解析配置函数ngx_conf_param和ngx_conf_parse
  • ngx_conf_param:主要解析命令行中的核心模块配置参数,
  • 例如:nginx -t -c /usr/local/nginx/conf/nginx.conf
  • ngx_conf_parse:主要解析配置文件/usr/local/nginx/conf/nginx.conf 信息
  • ngx_conf_param:最终也是调用ngx_conf_parse
    /* 解析命令行中的配置参数;例如:nginx -t -c /usr/local/nginx/conf/nginx.conf */
    if (ngx_conf_param(&conf) != NGX_CONF_OK) {
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }
 
    /* 解析配置文件/usr/local/nginx/conf/nginx.conf 信息 */
    if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }
4. 配置文件核心解析函数ngx_conf_parse

ngx_conf_parse解析命令中的配置信息和文件中的配置信息.

主要工作是按行读取配置文件,并且解析成配置token数组,并将token数组进行模块commend命令集匹配和设置。

其中比较重要的函数:

  • ngx_conf_read_token 按行读取配置文件,并将命令解析成token数组cf->args;
  • ngx_conf_handler 将命令token数组与模块命令集匹配并设置到模块配置文件数据结构上。
/**
 * 解析配置信息核心函数
 * 包含:解析命令中的配置信息和文件中的配置信息
 */
char *
ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
{
  ...
 
    /* 打开配置文件: /usr/local/nginx/conf/nginx.conf */
    if (filename) {
 
        /* open configuration file */
 
        fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); //只读方式打开文件
        if (fd == NGX_INVALID_FILE) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
                               ngx_open_file_n " \"%s\" failed",
                               filename->data);
            return NGX_CONF_ERROR;
        }
 
        prev = cf->conf_file;
 
        cf->conf_file = &conf_file;
 
        if (ngx_fd_info(fd, &cf->conf_file->file.info) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
                          ngx_fd_info_n " \"%s\" failed", filename->data);
        }
 
        /* 配置文件buf,默认大小4096 */
        cf->conf_file->buffer = &buf;
 
        buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log);
        if (buf.start == NULL) {
            goto failed;
        }
 
        buf.pos = buf.start;
        buf.last = buf.start;
        buf.end = buf.last + NGX_CONF_BUFFER;
        buf.temporary = 1;
 
        /* 读取配置文件数据,保存到cf->conf_file中 */
        cf->conf_file->file.fd = fd;
        cf->conf_file->file.name.len = filename->len;
        cf->conf_file->file.name.data = filename->data;
        cf->conf_file->file.offset = 0;
        cf->conf_file->file.log = cf->log;
        cf->conf_file->line = 1;
 
        type = parse_file;
 
        ...
    } else if (cf->conf_file->file.fd != NGX_INVALID_FILE) {
 
        /**
         * 解析块:events {  worker_connections  1024; }
         */
        type = parse_block;
 
    } else {
        type = parse_param;
    }
 
    for ( ;; ) {
 
    	/* 将配置信息解析成 token;仅仅是将配置文件的数据解析成一个个的单词,按行解析 */
        rc = ngx_conf_read_token(cf);
 
        /*
         * ngx_conf_read_token() may return
         *
         *    NGX_ERROR             there is error 解析失败
         *    NGX_OK                the token terminated by ";" was found  遇到结尾符号;,则表示解析成功
         *    NGX_CONF_BLOCK_START  the token terminated by "{" was found  遇到{模块配置开始标识
         *    NGX_CONF_BLOCK_DONE   the "}" was found	遇到模块}结束标识
         *    NGX_CONF_FILE_DONE    the configuration file is done 遇到文件解析完毕
         */
 
        if (rc == NGX_ERROR) {
            goto done;
        }
 
        /* 一个模块解析结束,则跳到done模块代码 */
        if (rc == NGX_CONF_BLOCK_DONE) {
 
            if (type != parse_block) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\"");
                goto failed;
            }
 
            goto done;
        }
 
        /* 一个模块解析结束,则跳到done模块代码 */
        if (rc == NGX_CONF_FILE_DONE) {
 
            if (type == parse_block) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "unexpected end of file, expecting \"}\"");
                goto failed;
            }
 
            goto done;
        }
 
        if (rc == NGX_CONF_BLOCK_START) {
 
            if (type == parse_param) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "block directives are not supported "
                                   "in -g option");
                goto failed;
            }
        }
 
        /* rc == NGX_OK || rc == NGX_CONF_BLOCK_START */
 
        /* 当遇到 NGX_CONF_BLOCK_START 和  NGX_OK*/
        if (cf->handler) {
 
            /*
             * the custom handler, i.e., that is used in the http's
             * "types { ... }" directive
             */
 
            if (rc == NGX_CONF_BLOCK_START) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"{\"");
                goto failed;
            }
 
            rv = (*cf->handler)(cf, NULL, cf->handler_conf);
            if (rv == NGX_CONF_OK) {
                continue;
            }
 
            if (rv == NGX_CONF_ERROR) {
                goto failed;
            }
 
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, rv);
 
            goto failed;
        }
 
        /* 配置文件处理;将配置文件设置到模块上 */
        rc = ngx_conf_handler(cf, rc);
 
        if (rc == NGX_ERROR) {
            goto failed;
        }
    }
 
failed:
...
 
    return NGX_CONF_OK;
}
5. 命令token解析 ngx_conf_read_token

ngx_conf_read_token 主要是将配置文件分解成逐个的单词数组。例如配置文件中遇到“空格”则为分隔符,“;”为结束符。

每一个数组,就是一条配置命令语句。数组会放置到cf->args数组上。

例如nginx.conf配置文件如下:

user  nfsnobody nfsnobody;
worker_processes 8;
error_log  /usr/local/nginx-1.4.7/nginx_error.log  crit;
pid        /usr/local/nginx-1.4.7/nginx.pid;
#Specifies the value for maximum file descriptors that can be opened by this process.
worker_rlimit_nofile 65535;
events
{
use epoll;
worker_connections 65535;}

解析成下面的格式:

#分解成逐个单词数组:
user
nfsnobody
nfsnobody
 
worker_processes
8
 
error_log
/usr/local/nginx-1.4.7/nginx_error.log
crit
 
pid
/usr/local/nginx-1.4.7/nginx.pid
 
worker_rlimit_nofile
65535
 
events
/**
 * 读取配置信息
 * 把每次分析的值放到cf->args这个数组里面	碰到{} ; 返回
 * 例如配置文件如下:
 * user  nfsnobody nfsnobody;
 * worker_processes 8;
 * error_log  /usr/local/nginx-1.4.7/nginx_error.log  crit;
 * pid        /usr/local/nginx-1.4.7/nginx.pid;
 * #Specifies the value for maximum file descriptors that can be opened by this process.
 * worker_rlimit_nofile 65535;
 * events
 * {
 * use epoll;
 * worker_connections 65535;}
 *
 * 分解成逐个单词:
 * user
 * nfsnobody
 * nfsnobody
 * worker_processes
 * 8
 * error_log
 * /usr/local/nginx-1.4.7/nginx_error.log
 * crit
 * pid
 * /usr/local/nginx-1.4.7/nginx.pid
 * worker_rlimit_nofile
 * 65535
 * events
 *
 */
static ngx_int_t
ngx_conf_read_token(ngx_conf_t *cf)
{
    u_char      *start, ch, *src, *dst;
    off_t        file_size;
    size_t       len;
    ssize_t      n, size;
    ngx_uint_t   found, need_space, last_space, sharp_comment, variable;
    ngx_uint_t   quoted, s_quoted, d_quoted, start_line;
    ngx_str_t   *word;
    ngx_buf_t   *b, *dump;
 
    found = 0; //表示找到一个 token
    need_space = 0;
    last_space = 1; //标志位,表示上一个字符为token分隔符
    sharp_comment = 0; //注释 #符号
    variable = 0; //变量符号 $
    quoted = 0;   //标志位,表示上一个字符为反斜杠
    s_quoted = 0; //标志位,表示已扫描一个双引号,期待另一个双引号
    d_quoted = 0; //标志位,表示已扫描一个单引号,期待另一个单引号
 
    cf->args->nelts = 0;
    b = cf->conf_file->buffer; //buffer 每次4096
    dump = cf->conf_file->dump;
    start = b->pos;
    start_line = cf->conf_file->line;
 
    file_size = ngx_file_size(&cf->conf_file->file.info);
 
    for ( ;; ) {
 
    	/* buf中的数据已经处理完毕,则需要判断是否文件读取完了,如果没有读取完,则继续解析配置文件 */
        if (b->pos >= b->last) {
 
        	/* 文件已经读取完毕,返回NGX_CONF_FILE_DONE */
            if (cf->conf_file->file.offset >= file_size) {
 
                if (cf->args->nelts > 0 || !last_space) {
 
                    if (cf->conf_file->file.fd == NGX_INVALID_FILE) {
                        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                           "unexpected end of parameter, "
                                           "expecting \";\"");
                        return NGX_ERROR;
                    }
 
                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                  "unexpected end of file, "
                                  "expecting \";\" or \"}\"");
                    return NGX_ERROR;
                }
 
                return NGX_CONF_FILE_DONE;
            }
 
            /* buf中已经使用的长度  */
            len = b->pos - start;
 
            /* 如果len=4096 则表明buf全部读取满了;如果读取了4096个字符,还是没有发现"和'的标示符号,则认为读取失败,参数太长了 */
            if (len == NGX_CONF_BUFFER) {
                cf->conf_file->line = start_line;
 
                if (d_quoted) {
                    ch = '"';
 
                } else if (s_quoted) {
                    ch = '\'';
 
                } else {
                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                       "too long parameter \"%*s...\" started",
                                       10, start);
                    return NGX_ERROR;
                }
 
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "too long parameter, probably "
                                   "missing terminating \"%c\" character", ch);
                return NGX_ERROR;
            }
 
            /* 将数据移动到buf的头部 */
            if (len) {
                ngx_memmove(b->start, start, len);
            }
 
            /* 如果buf有空闲,则继续读取文件中的数据到buf中 */
            size = (ssize_t) (file_size - cf->conf_file->file.offset);
 
            if (size > b->end - (b->start + len)) {
                size = b->end - (b->start + len);
            }
 
            n = ngx_read_file(&cf->conf_file->file, b->start + len, size,
                              cf->conf_file->file.offset);
 
            if (n == NGX_ERROR) {
                return NGX_ERROR;
            }
 
            if (n != size) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   ngx_read_file_n " returned "
                                   "only %z bytes instead of %z",
                                   n, size);
                return NGX_ERROR;
            }
 
            /* 设置b->pos和b->last的位置,并重新设置start的位置 */
            b->pos = b->start + len;
            b->last = b->pos + n;
            start = b->start;
 
            if (dump) {
                dump->last = ngx_cpymem(dump->last, b->pos, size);
            }
        }
 
        /* ch字符用于读取配置文件信息 */
        ch = *b->pos++;
 
        /* 如果遇到换行符号 \n */
        if (ch == LF) {
            cf->conf_file->line++;
 
            /* 判断改行是否是注释 如果遇到\n结尾,并且是注释,则设置sharp_comment = 0;当sharp_comment=1 则注释字符不处理*/
            if (sharp_comment) {
                sharp_comment = 0;
            }
        }
 
        /* 注释,则直接跳过 */
        if (sharp_comment) {
            continue;
        }
 
        /* 如果为反引号,则设置反引号标识,并且不对该字符进行解析   */
        if (quoted) {
            quoted = 0;
            continue;
        }
 
        /* 上一个字符为单引号或者双引号,期待一个分隔符   */
        if (need_space) {
 
           ...
        }
    }
}

设值到模块配置文件数据结构上 ngx_conf_handler

ngx_conf_handler方法主要是将拿到的token数组cf->args,
按照模块的命令集cycle->modules[i]->commands 设置值。
(后面解析event和http头部核心模块的时候,会用到)

通过模块的index索引值,拿到cycle->ctx 模块配置文件数据结构。
通过rv = cmd->set(cf, cmd, conf),调用命令集中定义的设值值的回调方法。
这边会根据模块类型,将配置信息逐个解析到相应的模块上。

/**
 * 配置文件处理
 * cycle->modules
 */
static ngx_int_t
ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
    char           *rv;
    void           *conf, **confp;
    ngx_uint_t      i, found;
    ngx_str_t      *name;
    ngx_command_t  *cmd;
 
    name = cf->args->elts;
 
    found = 0;
 
    /* 循环配置模块 */
    for (i = 0; cf->cycle->modules[i]; i++) {
 
        cmd = cf->cycle->modules[i]->commands;
        if (cmd == NULL) {
            continue;
        }
 
        for ( /* void */ ; cmd->name.len; cmd++) {
 
            if (name->len != cmd->name.len) {
                continue;
            }
 
            /* 检查配置名称和token的第一个元素的名称是否一致,如果不一致,则说明命令不一样 */
            if (ngx_strcmp(name->data, cmd->name.data) != 0) {
                continue;
            }
 
            found = 1;
 
            if (cf->cycle->modules[i]->type != NGX_CONF_MODULE
                && cf->cycle->modules[i]->type != cf->module_type)
            {
                continue;
            }
            ...
 
            /* is the directive's argument count right ? */
 
            if (!(cmd->type & NGX_CONF_ANY)) {
            ...
            }
 
            /* set up the directive's configuration context */
 
            conf = NULL;
 
            /* 设置配置文件的值  设置配置项对外面的配置信息; */
            if (cmd->type & NGX_DIRECT_CONF) {
            	/* ngx_core_module; */
                conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index];
 
            } else if (cmd->type & NGX_MAIN_CONF) {
                conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]);
 
            } else if (cf->ctx) {
                confp = *(void **) ((char *) cf->ctx + cmd->conf);
 
                if (confp) {
                    conf = confp[cf->cycle->modules[i]->ctx_index];
                }
            }
 
            /**
             * 配置文件设置值;
             * conf为配置的指针地址;
             * cmd为命令结构;
             * conf为配置指针地址 一般情况下 conf为模块自定义的配置文件数据结构地址
             *
             */
            rv = cmd->set(cf, cmd, conf);
 
            if (rv == NGX_CONF_OK) {
                return NGX_OK;
            }
 
            if (rv == NGX_CONF_ERROR) {
                return NGX_ERROR;
            }
 
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "\"%s\" directive %s", name->data, rv);
 
            return NGX_ERROR;
        }
    }
 
    ...
 
    return NGX_ERROR;
}

nginx_conf_file.c文件中定义了多种配置文件值设值的方法:

char *ngx_conf_set_flag_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char *ngx_conf_set_str_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char *ngx_conf_set_str_array_slot(ngx_conf_t *cf, ngx_command_t *cmd,
    void *conf);
char *ngx_conf_set_keyval_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char *ngx_conf_set_num_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char *ngx_conf_set_size_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char *ngx_conf_set_off_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char *ngx_conf_set_msec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char *ngx_conf_set_sec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char *ngx_conf_set_bufs_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char *ngx_conf_set_enum_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
char *ngx_conf_set_bitmask_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

调用核心模块的配置

通过核心模块ngx_core_module的索引index值,就可以得到核心配置的数据结构 ngx_core_conf_t

    /* 获取核心配置文件的数据结构 ngx_core_conf_t */
    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);

6、多进程实现

默认情况下,Nginx都是多进程的运行模式。
采用多进程模式最大的好处:

每个进程的资源独立
不需要添加各种繁琐的锁了

Nginx多进程实现的流程图

在这里插入图片描述

Nginx多进程具体实现

1. ngx_master_process_cycle 进入多进程模式

ngx_master_process_cycle方法主要做了两个工作:

  • 主进程进行信号的监听和处理
  • 开启子进程
/**
 * Nginx的多进程运行模式
 */
void ngx_master_process_cycle(ngx_cycle_t *cycle) {
	char *title;
	u_char *p;
	size_t size;
	ngx_int_t i;
	ngx_uint_t n, sigio;
	sigset_t set;
	struct itimerval itv;
	ngx_uint_t live;
	ngx_msec_t delay;
	ngx_listening_t *ls;
	ngx_core_conf_t *ccf;
 
	/* 设置能接收到的信号 */
	sigemptyset(&set);
	sigaddset(&set, SIGCHLD);
	sigaddset(&set, SIGALRM);
	sigaddset(&set, SIGIO);
	sigaddset(&set, SIGINT);
	sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
	sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
	sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
	sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
	sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
	sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
 
	if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
		ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
				"sigprocmask() failed");
	}
 
	sigemptyset(&set);
 
	size = sizeof(master_process);
 
	for (i = 0; i < ngx_argc; i++) {
		size += ngx_strlen(ngx_argv[i]) + 1;
	}
 
	/* 保存进程标题 */
	title = ngx_pnalloc(cycle->pool, size);
	if (title == NULL) {
		/* fatal */
		exit(2);
	}
 
	p = ngx_cpymem(title, master_process, sizeof(master_process) - 1);
	for (i = 0; i < ngx_argc; i++) {
		*p++ = ' ';
		p = ngx_cpystrn(p, (u_char *) ngx_argv[i], size);
	}
 
	ngx_setproctitle(title);
 
	/* 获取核心配置 ngx_core_conf_t */
	ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
 
	/* 启动工作进程 - 多进程启动的核心函数 */
	ngx_start_worker_processes(cycle, ccf->worker_processes,
			NGX_PROCESS_RESPAWN);
	ngx_start_cache_manager_processes(cycle, 0);
 
	ngx_new_binary = 0;
	delay = 0;
	sigio = 0;
	live = 1;
 
	/* 主线程循环 */
	for (;;) {
 
		/* delay用来设置等待worker推出的时间,master接受了退出信号后,
		 * 首先发送退出信号给worker,而worker退出需要一些时间*/
		if (delay) {
			if (ngx_sigalrm) {
				sigio = 0;
				delay *= 2;
				ngx_sigalrm = 0;
			}
 
			ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
					"termination cycle: %M", delay);
 
			itv.it_interval.tv_sec = 0;
			itv.it_interval.tv_usec = 0;
			itv.it_value.tv_sec = delay / 1000;
			itv.it_value.tv_usec = (delay % 1000) * 1000;
 
			if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
				ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
						"setitimer() failed");
			}
		}
 
		ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
 
		/* 等待信号的到来,阻塞函数 */
		sigsuspend(&set);
 
		ngx_time_update();
 
		ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
				"wake up, sigio %i", sigio);
 
		/* 收到了SIGCHLD信号,有worker退出(ngx_reap == 1) */
		if (ngx_reap) {
			ngx_reap = 0;
			ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
 
			live = ngx_reap_children(cycle);
		}
 
		if (!live && (ngx_terminate || ngx_quit)) {
			ngx_master_process_exit(cycle);
		}
 
		/* 中止进程  */
		if (ngx_terminate) {
			if (delay == 0) {
				delay = 50;
			}
 
			if (sigio) {
				sigio--;
				continue;
			}
 
			sigio = ccf->worker_processes + 2 /* cache processes */;
 
			if (delay > 1000) {
				ngx_signal_worker_processes(cycle, SIGKILL);
			} else {
				ngx_signal_worker_processes(cycle,
						ngx_signal_value(NGX_TERMINATE_SIGNAL));
			}
 
			continue;
		}
 
		/* 退出进程 */
		if (ngx_quit) {
			ngx_signal_worker_processes(cycle,
					ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
 
			ls = cycle->listening.elts;
			for (n = 0; n < cycle->listening.nelts; n++) {
				if (ngx_close_socket(ls[n].fd) == -1) {
					ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
							ngx_close_socket_n " %V failed", &ls[n].addr_text);
				}
			}
			cycle->listening.nelts = 0;
 
			continue;
		}
 
		/* 收到SIGHUP信号 重新初始化配置 */
		if (ngx_reconfigure) {
			ngx_reconfigure = 0;
 
			if (ngx_new_binary) {
				ngx_start_worker_processes(cycle, ccf->worker_processes,
						NGX_PROCESS_RESPAWN);
				ngx_start_cache_manager_processes(cycle, 0);
				ngx_noaccepting = 0;
 
				continue;
			}
 
			ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
 
			cycle = ngx_init_cycle(cycle);
			if (cycle == NULL) {
				cycle = (ngx_cycle_t *) ngx_cycle;
				continue;
			}
 
			ngx_cycle = cycle;
			ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
					ngx_core_module);
			ngx_start_worker_processes(cycle, ccf->worker_processes,
					NGX_PROCESS_JUST_RESPAWN);
			ngx_start_cache_manager_processes(cycle, 1);
 
			/* allow new processes to start */
			ngx_msleep(100);
 
			live = 1;
			ngx_signal_worker_processes(cycle,
					ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
		}
 
		/* 当ngx_noaccepting==1时,会把ngx_restart设为1,重启worker  */
		if (ngx_restart) {
			ngx_restart = 0;
			ngx_start_worker_processes(cycle, ccf->worker_processes,
					NGX_PROCESS_RESPAWN);
			ngx_start_cache_manager_processes(cycle, 0);
			live = 1;
		}
 
		/* 收到SIGUSR1信号,重新打开log文件 */
		if (ngx_reopen) {
			ngx_reopen = 0;
			ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
			ngx_reopen_files(cycle, ccf->user);
			ngx_signal_worker_processes(cycle,
					ngx_signal_value(NGX_REOPEN_SIGNAL));
		}
 
		/* SIGUSER2,热代码替换 */
		if (ngx_change_binary) {
			ngx_change_binary = 0;
			ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
			ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
		}
 
		/* 收到SIGWINCH信号不在接受请求,worker退出,master不退出 */
		if (ngx_noaccept) {
			ngx_noaccept = 0;
			ngx_noaccepting = 1;
			ngx_signal_worker_processes(cycle,
					ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
		}
	}
}
2. ngx_start_worker_processes 创建工作进程

通过循环创建N个子进程。每个子进程都有独立的内存空间。
子进程的个数由Nginx的配置:ccf->worker_processes决定

/**
 * 创建工作进程
 */
static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n,
		ngx_int_t type) {
	ngx_int_t i;
	ngx_channel_t ch;
 
	ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");
 
	ngx_memzero(&ch, sizeof(ngx_channel_t));
 
	ch.command = NGX_CMD_OPEN_CHANNEL;
 
	/* 循环创建工作进程  默认ccf->worker_processes=8个进程,根据CPU个数决定   */
	for (i = 0; i < n; i++) {
 
		/* 打开工作进程  (ngx_worker_process_cycle 回调函数,主要用于处理每个工作线程)*/
		ngx_spawn_process(cycle, ngx_worker_process_cycle,
				(void *) (intptr_t) i, "worker process", type);
 
		ch.pid = ngx_processes[ngx_process_slot].pid;
		ch.slot = ngx_process_slot;
		ch.fd = ngx_processes[ngx_process_slot].channel[0];
 
		ngx_pass_open_channel(cycle, &ch);
	}
}
3. ngx_spawn_process fork工作进程

ngx_spawn_process方法主要用于fork出各个工作进程。具体主要看fork过程的代码。

    /* fork 一个子进程 */
    pid = fork();
 
    switch (pid) {
 
    case -1:
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "fork() failed while spawning \"%s\"", name);
        ngx_close_channel(ngx_processes[s].channel, cycle->log);
        return NGX_INVALID_PID;
 
    case 0:
    	/* 如果pid fork成功,则调用 ngx_worker_process_cycle方法 */
        ngx_pid = ngx_getpid();
        proc(cycle, data);
        break;
 
    default:
        break;
    }
4. ngx_worker_process_cycle 子进程的回调函数

ngx_worker_process_cycle为子进程的回调函数,一切子进程的工作从这个方法开始。
Nginx的进程最终也是有事件驱动的,所有这个方法中,最终会调用ngx_process_events_and_timers事件驱动的核心函数。

/**
 * 子进程 回调函数
 * 每个进程的逻辑处理就从这个方法开始
 */
static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) {
	ngx_int_t worker = (intptr_t) data;
 
	ngx_process = NGX_PROCESS_WORKER;
	ngx_worker = worker;
 
	/* 工作进程初始化 */
	ngx_worker_process_init(cycle, worker);
 
	ngx_setproctitle("worker process");
 
	/* 进程循环 */
	for (;;) {
 
		/* 判断是否是退出的状态,如果退出,则需要清空socket连接句柄 */
		if (ngx_exiting) {
			ngx_event_cancel_timers();
 
			if (ngx_event_timer_rbtree.root
					== ngx_event_timer_rbtree.sentinel) {
				ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
 
				ngx_worker_process_exit(cycle);
			}
		}
 
		ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
 
		/* 事件驱动核心函数 */
		ngx_process_events_and_timers(cycle);
 
		if (ngx_terminate) {
			ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
 
			ngx_worker_process_exit(cycle);
		}
 
		/* 如果是退出 */
		if (ngx_quit) {
			ngx_quit = 0;
			ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
					"gracefully shutting down");
			ngx_setproctitle("worker process is shutting down");
 
			if (!ngx_exiting) {
				ngx_exiting = 1;
				ngx_close_listening_sockets(cycle);
				ngx_close_idle_connections(cycle);
			}
		}
 
		/* 如果是重启 */
		if (ngx_reopen) {
			ngx_reopen = 0;
			ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
			ngx_reopen_files(cycle, -1);
		}
	}
}
5. ngx_worker_process_init 工作进程初始化
/**
 * 工作进程初始化
 */
static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker) {
	sigset_t set;
	ngx_int_t n;
	ngx_uint_t i;
	ngx_cpuset_t *cpu_affinity;
	struct rlimit rlmt;
	ngx_core_conf_t *ccf;
	ngx_listening_t *ls;
 
	/* 配置环境变量 */
	if (ngx_set_environment(cycle, NULL) == NULL) {
		/* fatal */
		exit(2);
	}
 
	/* 获取核心配置 */
	ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
 
	if (worker >= 0 && ccf->priority != 0) {
		if (setpriority(PRIO_PROCESS, 0, ccf->priority) == -1) {
			ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
					"setpriority(%d) failed", ccf->priority);
		}
	}
 
	if (ccf->rlimit_nofile != NGX_CONF_UNSET) {
		rlmt.rlim_cur = (rlim_t) ccf->rlimit_nofile;
		rlmt.rlim_max = (rlim_t) ccf->rlimit_nofile;
 
		if (setrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
			ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
					"setrlimit(RLIMIT_NOFILE, %i) failed", ccf->rlimit_nofile);
		}
	}
 
	if (ccf->rlimit_core != NGX_CONF_UNSET) {
		rlmt.rlim_cur = (rlim_t) ccf->rlimit_core;
		rlmt.rlim_max = (rlim_t) ccf->rlimit_core;
 
		if (setrlimit(RLIMIT_CORE, &rlmt) == -1) {
			ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
					"setrlimit(RLIMIT_CORE, %O) failed", ccf->rlimit_core);
		}
	}
 
	/* 设置UID GROUPUID */
	if (geteuid() == 0) {
		if (setgid(ccf->group) == -1) {
			ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
					"setgid(%d) failed", ccf->group);
			/* fatal */
			exit(2);
		}
 
		if (initgroups(ccf->username, ccf->group) == -1) {
			ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
					"initgroups(%s, %d) failed", ccf->username, ccf->group);
		}
 
		if (setuid(ccf->user) == -1) {
			ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
					"setuid(%d) failed", ccf->user);
			/* fatal */
			exit(2);
		}
	}
 
	/* 设置CPU亲和性 */
	if (worker >= 0) {
		cpu_affinity = ngx_get_cpu_affinity(worker);
 
		if (cpu_affinity) {
			ngx_setaffinity(cpu_affinity, cycle->log);
		}
	}
 
#if (NGX_HAVE_PR_SET_DUMPABLE)
 
	/* allow coredump after setuid() in Linux 2.4.x */
 
	if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) {
		ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
				"prctl(PR_SET_DUMPABLE) failed");
	}
 
#endif
 
	/* 切换工作目录 */
	if (ccf->working_directory.len) {
		if (chdir((char *) ccf->working_directory.data) == -1) {
			ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
					"chdir(\"%s\") failed", ccf->working_directory.data);
			/* fatal */
			exit(2);
		}
	}
 
	sigemptyset(&set);
 
	/* 清除所有信号 */
	if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
		ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
				"sigprocmask() failed");
	}
 
	srandom((ngx_pid << 16) ^ ngx_time());
 
	/*
	 * disable deleting previous events for the listening sockets because
	 * in the worker processes there are no events at all at this point
	 */
	/* 清除sokcet的监听 */
	ls = cycle->listening.elts;
	for (i = 0; i < cycle->listening.nelts; i++) {
		ls[i].previous = NULL;
	}
 
	/* 对模块初始化  */
	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);
			}
		}
	}
 
	/**
	 *将其他进程的channel[1]关闭,自己的channel[0]关闭
	 */
	for (n = 0; n < ngx_last_process; n++) {
 
		if (ngx_processes[n].pid == -1) {
			continue;
		}
 
		if (n == ngx_process_slot) {
			continue;
		}
 
		if (ngx_processes[n].channel[1] == -1) {
			continue;
		}
 
		if (close(ngx_processes[n].channel[1]) == -1) {
			ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
					"close() channel failed");
		}
	}
 
	if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
		ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
				"close() channel failed");
	}
 
#if 0
	ngx_last_process = 0;
#endif
 
	/**
	 * 给ngx_channel注册一个读事件处理函数
	 */
	if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
			ngx_channel_handler) == NGX_ERROR) {
		/* fatal */
		exit(2);
	}
}

7、多进程的惊群和进程负载均衡处理

Linux2.6版本之前还存在对于socket的accept的惊群现象。之后的版本已经解决掉了这个问题。

惊群是指多个进程/线程在等待同一资源时,每当资源可用,所有的进程/线程都来竞争资源的现象。

Nginx采用的是多进程的模式。假设Linux系统是2.6版本以前,当有一个客户端要连到Nginx服务器上,Nginx的N个进程都会去监听socket的accept的,如果全部的N个进程都对这个客户端的socket连接进行了监听,就会造成资源的竞争甚至数据的错乱。我们要保证的是,一个链接在Nginx的一个进程上处理,包括accept和read/write事件。

Nginx解决惊群和进程负载均衡处理的要点

  • Nginx的N个进程会争抢文件锁,当只有拿到文件锁的进程,才能处理accept的事件。
  • 没有拿到文件锁的进程,只能处理当前连接对象的read事件
  • 当单个进程总的connection连接数达到总数的7/8的时候,就不会再接收新的accpet事件。
  • 如果拿到锁的进程能很快处理完accpet,而没拿到锁的一直在等待(等待时延:ngx_accept_mutex_delay),容易造成进程忙的很忙,空的很空
    在这里插入图片描述

具体的实现

1. ngx_process_events_and_timers 进程事件分发器

此方法为进程实现的核心函数。
主要作用:事件分发;惊群处理;简单的负载均衡。

负载均衡:
  • 当事件配置初始化的时候,会设置一个全局变量:ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
  • 当ngx_accept_disabled为正数的时候,connection达到连接总数的7/8的时候,就不再处理新的连接accept事件,只处理当前连接的read事件
惊群处理:
  • 通过ngx_trylock_accept_mutex争抢文件锁,拿到文件锁的,才可以处理accept事件。
  • ngx_accept_mutex_held是拿到锁的一个标志,当拿到锁了,flags会被设置成NGX_POST_EVENTS,这个标志会在事件处理函数ngx_process_events中将所有事件(accept和read)放入对应的ngx_posted_accept_events和ngx_posted_events队列中进行延后处理。
  • 当没有拿到锁,调用事件处理函数ngx_process_events的时候,可以明确都是read的事件,所以可以直接调用事件ev->handler方法回调处理。
  • 拿到锁的进程,接下来会优先处理ngx_posted_accept_events队列上的accept事件,处理函数:ngx_event_process_posted
    处理完accept事件后,就将文件锁释放
    接下来处理ngx_posted_events队列上的read事件,处理函数:ngx_event_process_posted
/**
 * 进程事件分发器
 */
void ngx_process_events_and_timers(ngx_cycle_t *cycle) {
	ngx_uint_t flags;
	ngx_msec_t timer, delta;
 
	if (ngx_timer_resolution) {
		timer = NGX_TIMER_INFINITE;
		flags = 0;
 
	} else {
		timer = ngx_event_find_timer();
		flags = NGX_UPDATE_TIME;
 
#if (NGX_WIN32)
 
		/* handle signals from master in case of network inactivity */
 
		if (timer == NGX_TIMER_INFINITE || timer > 500) {
			timer = 500;
		}
 
#endif
	}
 
	/**
	 * ngx_use_accept_mutex变量代表是否使用accept互斥体
	 * 默认是使用,可以通过accept_mutex off;指令关闭;
	 * accept mutex 的作用就是避免惊群,同时实现负载均衡
	 */
	if (ngx_use_accept_mutex) {
 
		/**
		 * 	ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
		 * 	当connection达到连接总数的7/8的时候,就不再处理新的连接accept事件,只处理当前连接的read事件
		 * 	这个是比较简单的一种负载均衡方法
		 */
		if (ngx_accept_disabled > 0) {
			ngx_accept_disabled--;
 
		} else {
			/* 获取锁失败 */
			if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
				return;
			}
 
			/* 拿到锁 */
			if (ngx_accept_mutex_held) {
				/**
				 * 给flags增加标记NGX_POST_EVENTS,这个标记作为处理时间核心函数ngx_process_events的一个参数,这个函数中所有事件将延后处理。
				 * accept事件都放到ngx_posted_accept_events链表中,
				 * epollin|epollout普通事件都放到ngx_posted_events链表中
				 **/
				flags |= NGX_POST_EVENTS;
 
			} else {
 
				/**
				 * 1. 获取锁失败,意味着既不能让当前worker进程频繁的试图抢锁,也不能让它经过太长事件再去抢锁
				 * 2. 开启了timer_resolution时间精度,需要让ngx_process_change方法在没有新事件的时候至少等待ngx_accept_mutex_delay毫秒之后再去试图抢锁
				 * 3. 没有开启时间精度时,如果最近一个定时器事件的超时时间距离现在超过了ngx_accept_mutex_delay毫秒,也要把timer设置为ngx_accept_mutex_delay毫秒
				 * 4. 不能让ngx_process_change方法在没有新事件的时候等待的时间超过ngx_accept_mutex_delay,这会影响整个负载均衡机制
				 * 5. 如果拿到锁的进程能很快处理完accpet,而没拿到锁的一直在等待,容易造成进程忙的很忙,空的很空
				 */
				if (timer == NGX_TIMER_INFINITE
						|| timer > ngx_accept_mutex_delay) {
					timer = ngx_accept_mutex_delay;
				}
			}
		}
	}
 
	delta = ngx_current_msec;
 
	/**
	 * 事件调度函数
	 * 1. 当拿到锁,flags=NGX_POST_EVENTS的时候,不会直接处理事件,
	 * 将accept事件放到ngx_posted_accept_events,read事件放到ngx_posted_events队列
	 * 2. 当没有拿到锁,则处理的全部是read事件,直接进行回调函数处理
	 * 参数:timer-epoll_wait超时时间  (ngx_accept_mutex_delay-延迟拿锁事件   NGX_TIMER_INFINITE-正常的epollwait等待事件)
	 */
	(void) ngx_process_events(cycle, timer, flags);
 
	delta = ngx_current_msec - delta;
 
	ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
			"timer delta: %M", delta);
	/**
	 * 1. ngx_posted_accept_events是一个事件队列,暂存epoll从监听套接口wait到的accept事件
	 * 2. 这个方法是循环处理accpet事件列队上的accpet事件
	 */
	ngx_event_process_posted(cycle, &ngx_posted_accept_events);
 
	/**
	 * 如果拿到锁,处理完accept事件后,则释放锁
	 */
	if (ngx_accept_mutex_held) {
		ngx_shmtx_unlock(&ngx_accept_mutex);
	}
 
	if (delta) {
		ngx_event_expire_timers();
	}
 
	/**
	 *1. 普通事件都会存放在ngx_posted_events队列上
	 *2. 这个方法是循环处理read事件列队上的read事件
	 */
	ngx_event_process_posted(cycle, &ngx_posted_events);
}


2. ngx_trylock_accept_mutex 获取accept锁
  • ngx_accept_mutex_held是拿到锁的唯一标识的全局变量。
  • 当拿到锁,则调用ngx_enable_accept_events,将新的connection加入event事件上
  • 如果没有拿到锁,则调用ngx_disable_accept_events。
/**
 * 获取accept锁
 */
ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle) {
	/**
	 * 拿到锁
	 */
	if (ngx_shmtx_trylock(&ngx_accept_mutex)) {
 
		ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
				"accept mutex locked");
 
		/* 多次进来,判断是否已经拿到锁 */
		if (ngx_accept_mutex_held && ngx_accept_events == 0) {
			return NGX_OK;
		}
 
		/* 调用ngx_enable_accept_events,开启监听accpet事件*/
		if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
			ngx_shmtx_unlock(&ngx_accept_mutex);
			return NGX_ERROR;
		}
 
		ngx_accept_events = 0;
		ngx_accept_mutex_held = 1;
 
		return NGX_OK;
	}
 
	ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
			"accept mutex lock failed: %ui", ngx_accept_mutex_held);
 
	/**
	 * 没有拿到锁,但是ngx_accept_mutex_held=1
	 */
	if (ngx_accept_mutex_held) {
		/* 没有拿到锁,调用ngx_disable_accept_events,将accpet事件删除 */
		if (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {
			return NGX_ERROR;
		}
 
		ngx_accept_mutex_held = 0;
	}
 
	return NGX_OK;
}


3. ngx_enable_accept_events 和 ngx_disable_accept_events
/**
 * 开启accept事件的监听
 * 并将accept事件加入到event上
 */
static ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle) {
	ngx_uint_t i;
	ngx_listening_t *ls;
	ngx_connection_t *c;
 
	ls = cycle->listening.elts;
	for (i = 0; i < cycle->listening.nelts; i++) {
 
		c = ls[i].connection;
 
		if (c == NULL || c->read->active) {
			continue;
		}
 
		if (ngx_add_event(c->read, NGX_READ_EVENT, 0) == NGX_ERROR) {
			return NGX_ERROR;
		}
	}
 
	return NGX_OK;
}
 
/**
 * 关闭accept事件的监听
 * 并将accept事件从event上删除
 */
static ngx_int_t ngx_disable_accept_events(ngx_cycle_t *cycle, ngx_uint_t all) {
	ngx_uint_t i;
	ngx_listening_t *ls;
	ngx_connection_t *c;
 
	ls = cycle->listening.elts;
	for (i = 0; i < cycle->listening.nelts; i++) {
 
		c = ls[i].connection;
 
		/* 如果c->read->active,则表示是活跃的连接,已经被使用中 */
		if (c == NULL || !c->read->active) {
			continue;
		}
 
#if (NGX_HAVE_REUSEPORT)
 
		/*
		 * do not disable accept on worker's own sockets
		 * when disabling accept events due to accept mutex
		 */
 
		if (ls[i].reuseport && !all) {
			continue;
		}
 
#endif
 
		/* 删除事件 */
		if (ngx_del_event(c->read, NGX_READ_EVENT,
				NGX_DISABLE_EVENT) == NGX_ERROR) {
			return NGX_ERROR;
		}
	}
 
	return NGX_OK;
}


4. ngx_event_process_posted 事件队列处理

对ngx_posted_accept_events或ngx_posted_events队列上的accept/read事件进行回调处理。

/**
 * 处理事件队列
 *
 */
void ngx_event_process_posted(ngx_cycle_t *cycle, ngx_queue_t *posted) {
	ngx_queue_t *q;
	ngx_event_t *ev;
 
	while (!ngx_queue_empty(posted)) {
 
		q = ngx_queue_head(posted);
		ev = ngx_queue_data(q, ngx_event_t, queue);
 
		ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
				"posted event %p", ev);
 
		ngx_delete_posted_event(ev);
 
		/* 事件回调函数 */
		ev->handler(ev);
	}
}


5. ngx_process_events 事件的核心处理函数

这个方法,我们主要看epoll模型下的ngx_epoll_process_events方法(ngx_epoll_module.c)

如果抢到了锁,则会将accpet/read事件放到队列上延后处理。
没有抢到锁的进程都是处理当前连接的read事件,所以直接进行处理。

        /* 读取事件 EPOLLIN */
        if ((revents & EPOLLIN) && rev->active) {
 
#if (NGX_HAVE_EPOLLRDHUP)
            if (revents & EPOLLRDHUP) {
                rev->pending_eof = 1;
            }
 
            rev->available = 1;
#endif
 
            rev->ready = 1;
 
            /* 如果进程抢到锁,则放入事件队列 */
            if (flags & NGX_POST_EVENTS) {
                queue = rev->accept ? &ngx_posted_accept_events
                                    : &ngx_posted_events;
 
                ngx_post_event(rev, queue);
 
            } else {
            	/* 没有抢到锁,则直接处理read事件*/
                rev->handler(rev);
            }
        }


四、HTTP模块

1、HTTP模块的初始化

前面几章整理了Nginx的事件模块,这一章开始整理HTTP模块。

HTTP模块的初始化工作,都在src/http/nginx_http.c 中的ngx_http_block函数中进行的。

http模块的配置

在看nginx_http_block之前,我们还必须看一下nginx.conf中 HTTP大模块的配置文件,只有理解了HTTP模块的配置,才能更好的理解HTTP模块如何初始化的。

http 
{
  include       mime.types;
  default_type  application/octet-stream;
 
  #charset  gb2312;
      
  server_names_hash_bucket_size 128;
  client_header_buffer_size 32k;
  large_client_header_buffers 4 32k;
  client_max_body_size 8m;
      
  sendfile on;
  tcp_nopush     on;
 
  keepalive_timeout 60;
 
  tcp_nodelay on;
 
  fastcgi_connect_timeout 300;
  fastcgi_send_timeout 300;
  fastcgi_read_timeout 300;
  fastcgi_buffer_size 64k;
  fastcgi_buffers 4 64k;
  fastcgi_busy_buffers_size 128k;
  fastcgi_temp_file_write_size 128k;
 
  gzip on;
  gzip_min_length  1k;
  gzip_buffers     4 16k;
  gzip_http_version 1.0;
  gzip_comp_level 2;
  gzip_types       text/plain application/x-javascript text/css application/xml;
  gzip_vary on;
 
  #limit_zone  crawler  $binary_remote_addr  10m;
 
  server
  {
    listen       80;
    #server_name  blog.s135.com;
    index index.html index.htm index.php;
    root   /home/wwwroot/;
 
    #limit_conn   crawler  20;    
                             
    location ~ .*\.(php|php5)?$
    {      
      #fastcgi_pass  unix:/tmp/php-cgi.sock;
      fastcgi_pass  127.0.0.1:9000;
      fastcgi_index index.php;
      include fcgi.conf;
    }
    
    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
    {
      expires      30d;
    }
 
    location ~ .*\.(js|css)?$
    {
      expires      1h;
    }    
 
    log_format  access  '$remote_addr - $remote_user [$time_local] "$request" '
              '$status $body_bytes_sent "$http_referer" '
              '"$http_user_agent" $http_x_forwarded_for';
    access_log  /usr/local/nginx-1.4.7/logs/access.log  access;
      }
 
  server
  {
    listen  80;
    server_name  status.blog.s135.com;
    location / {
    stub_status on;
    access_log   off;
    }
  }


从上面的配置文件我们可以看到,HTTP的配置主要分为4层:

  • 最外层的http{} 模块。模块类型:NGX_CORE_MODULE (最外层的核心模块)
  • http核心模块中的main配置:http{include mine type; }。模块类型:NGX_HTTP_MODULE (全局的HTTP模块的配置信息)
  • http核心模块中的server配置:server{}。模块类型:NGX_HTTP_MODULE (主要是配置Server的信息)
  • http核心模块中的location本地信息配置:location{}。模块类型:NGX_HTTP_MODULE (主要一个server对应的本地资源信息:静态资源、反向代理端口地址、各种语言容器端口等)

最外层的http模块,类型NGX_CORE_MODULE,属于核心模块,核心模块在最开始配置文件初始化的时候,就会调用指令的命令集。所以在核心模块启动的时候就会调用http的模块配置解析指令函数:ngx_http_block

HTTP核心模块的数据结构

/**
 * HTTP模块命令集
 * HTTP模块也是一个大模块,最外层为:
 * http {
 * ....
 * }
 * ngx_http_block:该方法就是回调函数
 * HTTP核心模块
 */
static ngx_command_t  ngx_http_commands[] = {
 
    { ngx_string("http"),
      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
      ngx_http_block,
      0,
      0,
      NULL },
 
      ngx_null_command
};
 
/**
 *HTTP核心模块上下文
 */
static ngx_core_module_t  ngx_http_module_ctx = {
    ngx_string("http"),
    NULL,
    NULL
};
 
/**
 * HTTP核心模块 结构
 * 模块类型:NGX_CORE_MODULE
 * 通过调用ngx_http_block方法,解析{}中的HTTP模块配置
 */
ngx_module_t  ngx_http_module = {
    NGX_MODULE_V1,
    &ngx_http_module_ctx,                  /* module context */
    ngx_http_commands,                     /* module directives */
    NGX_CORE_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};


从上面的结构中可以看到http{} 是NGX_CORE_MODULE,在核心模块初始化的时候,会调用ngx_http_commands命令集中的回调函数,逐个解析核心模块的配置信息。

而HTTP模块的总入口就是这个http{}命令集的回调函数:ngx_http_block

如果对配置文件如何解析有遗忘,请回顾《 Nginx源码分析 - 主流程篇 - 解析配置文件 》

ngx_http_block函数详解

/**
 *ngx_http_commands 命令集的回调函数
 *HTTP模块初始化的入口函数
 *
 */
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char                        *rv;
    ngx_uint_t                   mi, m, s;
    ngx_conf_t                   pcf;
    ngx_http_module_t           *module;
    ngx_http_conf_ctx_t         *ctx;
    ngx_http_core_loc_conf_t    *clcf;
    ngx_http_core_srv_conf_t   **cscfp;
    ngx_http_core_main_conf_t   *cmcf;
 
    if (*(ngx_http_conf_ctx_t **) conf) {
        return "is duplicate";
    }
 
    /* the main http context */
 
    /* 分配一块内存,存放http配置上下文 */
    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
    if (ctx == NULL) {
        return NGX_CONF_ERROR;
    }
 
    *(ngx_http_conf_ctx_t **) conf = ctx;
 
 
    /* count the number of the http modules and set up their indices */
 
    /* 计算http模块个数 */
    ngx_http_max_module = ngx_count_modules(cf->cycle, NGX_HTTP_MODULE);
 
 
    /* the http main_conf context, it is the same in the all http contexts */
 
    /**
     * 最外层的HTTP配置
     * http
	  {
	  include       mime.types;
	  default_type  application/octet-stream;
     */
    ctx->main_conf = ngx_pcalloc(cf->pool,
                                 sizeof(void *) * ngx_http_max_module);
    if (ctx->main_conf == NULL) {
        return NGX_CONF_ERROR;
    }
 
 
    /*
     * the http null srv_conf context, it is used to merge
     * the server{}s' srv_conf's
     */
    /**
     * server层的配置
     *   server
  {
    listen       80;
    #server_name  blog.s135.com;
    index index.html index.htm index.php;
    root   /home/wwwroot/;
     */
    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
    if (ctx->srv_conf == NULL) {
        return NGX_CONF_ERROR;
    }
 
 
    /*
     * the http null loc_conf context, it is used to merge
     * the server{}s' loc_conf's
     */
 
    /**
     * location 层的配置
    location ~ .*\.(php|php5)?$
    {
      #fastcgi_pass  unix:/tmp/php-cgi.sock;
      fastcgi_pass  127.0.0.1:9000;
      fastcgi_index index.php;
      include fcgi.conf;
    }
     */
    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
    if (ctx->loc_conf == NULL) {
        return NGX_CONF_ERROR;
    }
 
 
    /*
     * create the main_conf's, the null srv_conf's, and the null loc_conf's
     * of the all http modules
     */
     /**
      * 调用:create_main_conf、create_srv_conf、create_loc_conf
      * 创建配置
      */
    for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
 
        module = cf->cycle->modules[m]->ctx;
        mi = cf->cycle->modules[m]->ctx_index;
 
        if (module->create_main_conf) {
            ctx->main_conf[mi] = module->create_main_conf(cf);
            if (ctx->main_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
 
        if (module->create_srv_conf) {
            ctx->srv_conf[mi] = module->create_srv_conf(cf);
            if (ctx->srv_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
 
        if (module->create_loc_conf) {
            ctx->loc_conf[mi] = module->create_loc_conf(cf);
            if (ctx->loc_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
    }
 
    pcf = *cf;
    cf->ctx = ctx;
 
    /**
     * preconfiguration 预先初始化配置信息
     */
    for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
 
        module = cf->cycle->modules[m]->ctx;
 
        if (module->preconfiguration) {
            if (module->preconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }
 
    /* parse inside the http{} block */
 
    cf->module_type = NGX_HTTP_MODULE;
    cf->cmd_type = NGX_HTTP_MAIN_CONF;
    rv = ngx_conf_parse(cf, NULL);
 
    if (rv != NGX_CONF_OK) {
        goto failed;
    }
 
    /*
     * init http{} main_conf's, merge the server{}s' srv_conf's
     * and its location{}s' loc_conf's
     */
 
    /**
     * 初始化main配置
     * 合并 server srv_conf
     * 合并location loc_conf
     */
    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
    cscfp = cmcf->servers.elts;
 
    for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
 
        module = cf->cycle->modules[m]->ctx;
        mi = cf->cycle->modules[m]->ctx_index;
 
        /* init http{} main_conf's */
 
        if (module->init_main_conf) {
            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }
 
        rv = ngx_http_merge_servers(cf, cmcf, module, mi);
        if (rv != NGX_CONF_OK) {
            goto failed;
        }
    }
 
 
    /* create location trees */
 
    /**
     * 创建 location模块的trees
     */
    for (s = 0; s < cmcf->servers.nelts; s++) {
 
        clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
 
        if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
 
        if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    }
 
 
    if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
 
    if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
 
 
    for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
 
        module = cf->cycle->modules[m]->ctx;
 
        if (module->postconfiguration) {
            if (module->postconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }
 
    if (ngx_http_variables_init_vars(cf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
 
    /*
     * http{}'s cf->ctx was needed while the configuration merging
     * and in postconfiguration process
     */
 
    *cf = pcf;
 
 
    if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
 
 
    /* optimize the lists of ports, addresses and server names */
 
    /* ngx_http_optimize_servers 初始化listen 端口号 ip地址 服务器等监听信息*/
    if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
 
    return NGX_CONF_OK;
 
failed:
 
    *cf = pcf;
 
    return rv;
}


2、TCP连接建立过程

上一章,我们讲解了HTTP模块是如何初始化的。这一章节,主要讲解监听套接字初始化函数ngx_http_optimize_servers和Nginx整个连接的过程

监听套接字初始化函数ngx_http_optimize_servers

ngx_http_optimize_servers主要处理Nginx服务的监听套接字

/**
 * ngx_http_optimize_servers:处理Nginx服务的监听套接字
 * 说明:主要遍历Nginx服务器提供的端口,然后根据每一个IP地址:port这种配置创建一个监听套接字
 * ngx_http_init_listening:初始化监听套接字
 */
static ngx_int_t
ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
    ngx_array_t *ports)
{
    ngx_uint_t             p, a;
    ngx_http_conf_port_t  *port;
    ngx_http_conf_addr_t  *addr;
 
    if (ports == NULL) {
        return NGX_OK;
    }
 
    /* 根据Nginx配置的端口号进行遍历 */
    port = ports->elts;
    for (p = 0; p < ports->nelts; p++) {
 
        ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
                 sizeof(ngx_http_conf_addr_t), ngx_http_cmp_conf_addrs);
 
        /*
         * check whether all name-based servers have the same
         * configuration as a default server for given address:port
         */
 
        addr = port[p].addrs.elts;
        for (a = 0; a < port[p].addrs.nelts; a++) {
 
            if (addr[a].servers.nelts > 1
#if (NGX_PCRE)
                || addr[a].default_server->captures
#endif
               )
            {
                if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
                    return NGX_ERROR;
                }
            }
        }
 
        /* 初始化监听套接字 */
        if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
            return NGX_ERROR;
        }
    }
 
    return NGX_OK;
}


ngx_http_init_listening 主要初始化侦听套接字

/**
 * 初始化侦听套接字
 */
static ngx_int_t
ngx_http_init_listening(ngx_conf_t *cf, ngx_http_conf_port_t *port)
{
    ngx_uint_t                 i, last, bind_wildcard;
    ngx_listening_t           *ls;
    ngx_http_port_t           *hport;
    ngx_http_conf_addr_t      *addr;
 
    addr = port->addrs.elts;
    last = port->addrs.nelts;
 
    /*
     * If there is a binding to an "*:port" then we need to bind() to
     * the "*:port" only and ignore other implicit bindings.  The bindings
     * have been already sorted: explicit bindings are on the start, then
     * implicit bindings go, and wildcard binding is in the end.
     */
 
    if (addr[last - 1].opt.wildcard) {
        addr[last - 1].opt.bind = 1;
        bind_wildcard = 1;
 
    } else {
        bind_wildcard = 0;
    }
 
    i = 0;
 
    /* 根据IP地址 遍历 创建 listening*/
    while (i < last) {
 
        if (bind_wildcard && !addr[i].opt.bind) {
            i++;
            continue;
        }
 
        /* 创建侦听套接字 listening */
        ls = ngx_http_add_listening(cf, &addr[i]);
        if (ls == NULL) {
            return NGX_ERROR;
        }
 
        hport = ngx_pcalloc(cf->pool, sizeof(ngx_http_port_t));
        if (hport == NULL) {
            return NGX_ERROR;
        }
 
        ls->servers = hport;
 
        hport->naddrs = i + 1;
 
        switch (ls->sockaddr->sa_family) {
 
#if (NGX_HAVE_INET6)
        case AF_INET6:
            if (ngx_http_add_addrs6(cf, hport, addr) != NGX_OK) {
                return NGX_ERROR;
            }
            break;
#endif
        default: /* AF_INET */
            if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) {
                return NGX_ERROR;
            }
            break;
        }
 
        if (ngx_clone_listening(cf, ls) != NGX_OK) {
            return NGX_ERROR;
        }
 
        addr++;
        last--;
    }
 
    return NGX_OK;
}


ngx_http_add_listening 创建侦听套接字 listening

/**
 * 创建侦听套接字 listening
 */
static ngx_listening_t *
ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
{
    ngx_listening_t           *ls;
    ngx_http_core_loc_conf_t  *clcf;
    ngx_http_core_srv_conf_t  *cscf;
 
    /* 创建一个套接字 */
    ls = ngx_create_listening(cf, &addr->opt.sockaddr.sockaddr,
                              addr->opt.socklen);
    if (ls == NULL) {
        return NULL;
    }
 
    ls->addr_ntop = 1;
 
    /* 侦听套接字 的回调函数。该回调函数在ngx_event_accept函数中回调;
     * 回调之后,会将读取事件回调函数rev->handler()修改成方法:ngx_http_wait_request_handler*/
    ls->handler = ngx_http_init_connection;
 
    cscf = addr->default_server;
    ls->pool_size = cscf->connection_pool_size;
    ls->post_accept_timeout = cscf->client_header_timeout;
 
    clcf = cscf->ctx->loc_conf[ngx_http_core_module.ctx_index];
 
    ls->logp = clcf->error_log;
    ls->log.data = &ls->addr_text;
    ls->log.handler = ngx_accept_log_error;
 
 
    return ls;
}


Nginx整个连接的过程

通过《Nginx源码分析 - Event事件篇 - Event模块的进程初始化ngx_event_process_init 》 和当前篇章的ngx_http_optimize_servers方法,我们就能将整个Nginx的连接过程串起来了。

整个流程如下:

  • 在Nginx main函数的ngx_init_cycle()方法中,调用了ngx_open_listening_sockets函数,这个函数负责将创建的监听套接字进行套接字选项的设置(比如非阻塞、接受发送的缓冲区、绑定、监听处理)
  • HTTP模块初始化优先于Event模块,HTTP模块通过ngx_http_block()方法进行初始化,然后调用ngx_http_optimize_servers()进行套接字的创建和初始化(ngx_http_init_listening、ngx_http_add_listening、ngx_create_listening)。根据每一个IP地址:port这种配置创建监听套接字。
  • ngx_http_add_listening函数,还会将ls->handler监听套接字的回调函数设置为ngx_http_init_connection。ngx_http_init_connection此函数主要初始化一个客户端连接connection。
  • Event模块的初始化主要调用ngx_event_process_init()函数。该函数每个worker工作进程都会初始化调用。然后设置read/write的回调函数。
  • ngx_event_process_init函数中,会将接收客户端连接的事件,设置为rev->handler=ngx_event_accept方法,ngx_event_accept方法,只有在第一次客户端和Nginx服务端创建连接关系的时候调用。
  • 当客户端有连接上来,Nginx工作进程就会进入事件循环(epoll事件循环函数:ngx_epoll_process_events),发现有read读取的事件,则会调用ngx_event_accept函数。
  • 调用ngx_event_accept函数,会调用ngx_get_connection方法,得到一个客户端连接结构:ngx_connection_t结构。ngx_event_accept函数最终会调用监听套接字的handler回调函数,ls->handler©; 。
  • 从流程3中,我们知道ls->handler的函数对应ngx_http_init_connection方法。此方法主要初始化客户端的连接ngx_connection_t,并将客户端连接read读取事件的回调函数修改成rev->handler = ngx_http_wait_request_handler
  • 也就是说,当客户端连接上来,第一次事件循环的read事件会调用回调函数:ngx_event_accept函数;而后续的read事件的handler已经被ngx_http_init_connection方法修改掉,改成了ngx_http_wait_request_handler函数了。所以客户端的读取事件都会走ngx_http_wait_request_handler函数。
  • ngx_http_wait_request_handler函数也是整个HTTP模块的数据处理的入口函数了。
    如下图表格:
    在这里插入图片描述

3、HTTP Request解析过程

上一章我们讲解了整个Nginx的tcp连接过程,我们知道当客户端刚连接上来的时候,会触发ngx_event_accept方法;当read事件进来的时候,就会调用ngx_http_wait_request_handler。ngx_http_wait_request_handler方法也是http模块数据处理的入口。

这一章,我们从ngx_http_wait_request_handler函数讲起,深入整个http的request的解析过程。nginx文件:http/ngx_http_request.c

HTTP Request解析流程图

在这里插入图片描述
说明:

  • Nginx的HTTP核心模块只解析request的请求行和请求头,不会主动读取HTTP 请求body数据,但是提供
  • ngx_http_read_client_request_body方法,供各个filter模块处理。
  • ngx_http_wait_request_handler:等待read事件上来,并且等到HTTP的request数据
  • ngx_http_process_request_line:处理HTTP的request的请求行
  • ngx_http_process_request_header:处理HTTP的request的请求头
  • ngx_http_handler:HTTP核心处理函数,模块filter链的分发函数
  • 设置r->write_event_handler = ngx_http_core_run_phases,Nginx的write事件模块,从ngx_http_core_run_phases方法开始

为什么不主动读取HTTP BODY数据?

看到上面的图,可能很多人会比较奇怪了,为什么HTTP的核心模块只读取了HTTP协议的请求行和请求头,而没有读取HTTP的请求body内容。

因为大部分情况下,HTTP协议是不需要用到HTTP body中的数据的,例如你返回一张图片或者一个静态页面。

只有在需要将body数据传输到后端的例如JAVA、PHP等的时候,才会需要将HTTP的body数据带过去(POST请求、文件上传等)。所以Nginx只有在特殊的模块下(比如proxy模块,fastcgi模块,uwsgi模块等),才会去主动调用ngx_http_read_client_request_body方法,将body传递到后端。

源码分析

1. 等待read事件ngx_http_wait_request_handler

等待read事件上来,并且等到HTTP的request数据。

ngx_http_wait_request_handler主要是一个等待数据到来的功能。里面有一个设计亮点:此函数会一直等待TCP管道中的数据,如果触发了read事件,但是没有读取到数据,则Nginx会将buf内存删除,然后继续等待read事件的到来,好处是防止大量非法请求上来,又占用内存不释放,导致Nginx内存暴涨。

/**
 * read事件回调函数;主要处理读取事件
 * 等待处理http request 数据
 */
static void
ngx_http_wait_request_handler(ngx_event_t *rev)
{
    u_char                    *p;
    size_t                     size;
    ssize_t                    n;
    ngx_buf_t                 *b;
    ngx_connection_t          *c;
    ngx_http_connection_t     *hc;
    ngx_http_core_srv_conf_t  *cscf;
 
    /* 从rev中获取连接 ngx_connection_t 对象 */
    c = rev->data;
 
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http wait request handler");
 
    /* 客户端超时 */
    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        ngx_http_close_connection(c);
        return;
    }
 
    /* 客户端连接关闭 */
    if (c->close) {
        ngx_http_close_connection(c);
        return;
    }
 
    /* 获取sev的conf配置 ngx_http_core_srv_conf_t*/
    hc = c->data;
    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
 
    /* 每次读取数据的buf大小 */
    size = cscf->client_header_buffer_size;
 
    /* ngx_connection_s 中的buffer:用于接收和缓存客户端发来的字符流 */
    b = c->buffer;
 
    /* 如果buffer为null了,则直接创建一个临时buf ngx_create_temp_buf*/
    if (b == NULL) {
        b = ngx_create_temp_buf(c->pool, size);
        if (b == NULL) {
            ngx_http_close_connection(c);
            return;
        }
 
        c->buffer = b;
 
    /* 如果buf的start为null,则直接创建一块内存,并且调整ngx_buf_t的姿势 */
    } else if (b->start == NULL) {
 
        b->start = ngx_palloc(c->pool, size);
        if (b->start == NULL) {
            ngx_http_close_connection(c);
            return;
        }
 
        b->pos = b->start;
        b->last = b->start;
        b->end = b->last + size;
    }
 
    /**
     * 接收数据的回调函数:os/ngx_recv.c文件夹中的 ngx_unix_recv
     * ngx_unix_recv:主要调用系统的recv函数,循环接收TCP管道中的数据
     */
    n = c->recv(c, b->last, size);
 
    /*
     * NGX_AGAIN
     * 这个是一个Http连接第一次等待读取数据,如果第一次接收的数据为空,则表示当前客户端连接上来了,但是数据还未上来,
     * 则将当前连接上的读事件添加到定时器机制中,同时将读事件注册到epoll 事件机制中,return 从当前函数返回
     * */
    if (n == NGX_AGAIN) {
 
        if (!rev->timer_set) {
            ngx_add_timer(rev, c->listening->post_accept_timeout);
            ngx_reusable_connection(c, 1);
        }
 
        /* 重新把读事件注册到事件中,每次epoll_wait后,fd的事件类型将会清空,需要再次注册读写事件 */
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_connection(c);
            return;
        }
 
        /*
         * We are trying to not hold c->buffer's memory for an idle connection.
         * 因为啥也不做,暂时现将buf清空
         */
        if (ngx_pfree(c->pool, b->start) == NGX_OK) {
            b->start = NULL;
        }
 
        return;
    }
 
    /* 读取错误 ,则关闭连接 */
    if (n == NGX_ERROR) {
        ngx_http_close_connection(c);
        return;
    }
 
    /* 客户端主动关闭连接 */
    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client closed connection");
        ngx_http_close_connection(c);
        return;
    }
 
    /* 真正读取到数据后的处理 */
    b->last += n;
 
    if (hc->proxy_protocol) {
        hc->proxy_protocol = 0;
 
        p = ngx_proxy_protocol_read(c, b->pos, b->last);
 
        if (p == NULL) {
            ngx_http_close_connection(c);
            return;
        }
 
        b->pos = p;
 
        if (b->pos == b->last) {
            c->log->action = "waiting for request";
            b->pos = b->start;
            b->last = b->start;
            ngx_post_event(rev, &ngx_posted_events);
            return;
        }
    }
 
    c->log->action = "reading client request line";
 
    ngx_reusable_connection(c, 0);
 
    /* 调用 ngx_http_create_request 方法构造ngx_http_request_t 请求结构体,并设置到当前连接的data 成员 */
    c->data = ngx_http_create_request(c);
    if (c->data == NULL) {
        ngx_http_close_connection(c);
        return;
    }
 
    /* 设置当前读事件的回调方法为 ngx_http_process_request_line
     * 并执行该回调方法开始接收并解析HTTP头部的请求行 */
    rev->handler = ngx_http_process_request_line;
    ngx_http_process_request_line(rev);
}


2. 处理头部请求行ngx_http_process_request_line

ngx_http_process_request_line:主要用来处理HTTP协议的请求头。

/**
 * read事件的时候,读取和处理HTTP头部的行
 * 请求行如下:
 * POST /index.php HTTP/1.1
 */
static void
ngx_http_process_request_line(ngx_event_t *rev)
{
    ssize_t              n;
    ngx_int_t            rc, rv;
    ngx_str_t            host;
    ngx_connection_t    *c;
    ngx_http_request_t  *r;
 
 
    c = rev->data; //ngx_connection_t
    r = c->data; //ngx_http_request_t
 
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
                   "http process request line");
 
    /* 超时清空 */
    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        c->timedout = 1;
        ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
        return;
    }
 
    rc = NGX_AGAIN;
    /* 循环处理请求 NGX_AGAIN为标识服务*/
    for ( ;; ) {
 
        if (rc == NGX_AGAIN) {
 
        	/* 读取请求数据  也会调用方法:os/ngx_recv.c文件夹中的 ngx_unix_recv*/
            n = ngx_http_read_request_header(r);
 
            if (n == NGX_AGAIN || n == NGX_ERROR) {
                return;
            }
        }
 
        /*
         * 如果数据接收已经完整了,则对HTTP请求行进行 parse解析
         * */
        rc = ngx_http_parse_request_line(r, r->header_in);
 
        /* 如果解析成功 */
        if (rc == NGX_OK) {
 
            /* the request line has been parsed successfully */
 
            r->request_line.len = r->request_end - r->request_start;
            r->request_line.data = r->request_start;
            r->request_length = r->header_in->pos - r->request_start;
 
            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                           "http request line: \"%V\"", &r->request_line);
 
            r->method_name.len = r->method_end - r->request_start + 1;
            r->method_name.data = r->request_line.data;
 
            if (r->http_protocol.data) {
                r->http_protocol.len = r->request_end - r->http_protocol.data;
            }
 
            if (ngx_http_process_request_uri(r) != NGX_OK) {
                return;
            }
 
            if (r->host_start && r->host_end) {
 
                host.len = r->host_end - r->host_start;
                host.data = r->host_start;
 
                rc = ngx_http_validate_host(&host, r->pool, 0);
 
                if (rc == NGX_DECLINED) {
                    ngx_log_error(NGX_LOG_INFO, c->log, 0,
                                  "client sent invalid host in request line");
                    ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
                    return;
                }
 
                if (rc == NGX_ERROR) {
                    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                    return;
                }
 
                if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
                    return;
                }
 
                r->headers_in.server = host;
            }
 
            if (r->http_version < NGX_HTTP_VERSION_10) {
 
                if (r->headers_in.server.len == 0
                    && ngx_http_set_virtual_server(r, &r->headers_in.server)
                       == NGX_ERROR)
                {
                    return;
                }
 
                ngx_http_process_request(r);
                return;
            }
 
 
            if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
                              sizeof(ngx_table_elt_t))
                != NGX_OK)
            {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }
 
            c->log->action = "reading client request headers";
 
            /* 请求行解析成功后,将read事件的回调函数设置为:ngx_http_process_request_headers
             * ngx_http_process_request_headers:用于处理http的header数据*/
            rev->handler = ngx_http_process_request_headers;
            ngx_http_process_request_headers(rev);
 
            return;
        }
 
        /* 如果ngx_http_parse_request_line解析结果有误,则ngx_http_finalize_request 析构request结构*/
        if (rc != NGX_AGAIN) {
 
            /* there was error while a request line parsing */
 
            ngx_log_error(NGX_LOG_INFO, c->log, 0,
                          ngx_http_client_errors[rc - NGX_HTTP_CLIENT_ERROR]);
            ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
            return;
        }
 
        /* NGX_AGAIN: a request line parsing is still incomplete */
 
        /* 如果解析的header头部非常大,则调用ngx_http_alloc_large_header_buffer处理buffer*/
        if (r->header_in->pos == r->header_in->end) {
 
            rv = ngx_http_alloc_large_header_buffer(r, 1);
 
            if (rv == NGX_ERROR) {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }
 
            if (rv == NGX_DECLINED) {
                r->request_line.len = r->header_in->end - r->request_start;
                r->request_line.data = r->request_start;
 
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                              "client sent too long URI");
                ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE);
                return;
            }
        }
    }
}


ngx_http_read_request_header:主要调用系统的recv函数,循环接收TCP管道中的数据,接收数据的回调函数:os/ngx_recv.c文件夹中的 ngx_unix_recv

/**
 * 读取请求数据
 */
static ssize_t
ngx_http_read_request_header(ngx_http_request_t *r)
{
    ssize_t                    n;
    ngx_event_t               *rev;
    ngx_connection_t          *c;
    ngx_http_core_srv_conf_t  *cscf;
 
    c = r->connection;
    rev = c->read;
 
    n = r->header_in->last - r->header_in->pos;
 
    if (n > 0) {
        return n;
    }
 
    /**
     * 接收数据的回调函数:os/ngx_recv.c文件夹中的 ngx_unix_recv
     * ngx_unix_recv:主要调用系统的recv函数,循环接收TCP管道中的数据
     */
    if (rev->ready) {
        n = c->recv(c, r->header_in->last,
                    r->header_in->end - r->header_in->last);
    } else {
        n = NGX_AGAIN; //如果还未可以读取,则返回NGX_AGAIN 继续等待数据到来
    }
 
    /* 如果是等待状态,则继续放进事件event循环中*/
    if (n == NGX_AGAIN) {
        if (!rev->timer_set) {
            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
            ngx_add_timer(rev, cscf->client_header_timeout);
        }
 
        /* 重新把读事件注册到事件中,每次epoll_wait后,fd的事件类型将会清空,需要再次注册读写事件 */
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return NGX_ERROR;
        }
 
        return NGX_AGAIN;
    }
 
    /* 客户端主动关闭连接 */
    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client prematurely closed connection");
    }
 
    /* 客户端关闭连接或者请求错误,则对request进行析构处理*/
    if (n == 0 || n == NGX_ERROR) {
        c->error = 1;
        c->log->action = "reading client request headers";
 
        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        return NGX_ERROR;
    }
 
    r->header_in->last += n;
 
    return n;
}


3. 处理HTTP协议的header部分ngx_http_process_request_headers

ngx_http_process_request_headers:主要用于解析HTTP头部的header部分数据。例如:

Host: localhost
Accept-Language: zh-cn,zh;q=0.5

/**
 * 读取请求数据
 */
static ssize_t
ngx_http_read_request_header(ngx_http_request_t *r)
{
    ssize_t                    n;
    ngx_event_t               *rev;
    ngx_connection_t          *c;
    ngx_http_core_srv_conf_t  *cscf;
 
    c = r->connection;
    rev = c->read;
 
    n = r->header_in->last - r->header_in->pos;
 
    if (n > 0) {
        return n;
    }
 
    /**
     * 接收数据的回调函数:os/ngx_recv.c文件夹中的 ngx_unix_recv
     * ngx_unix_recv:主要调用系统的recv函数,循环接收TCP管道中的数据
     */
    if (rev->ready) {
        n = c->recv(c, r->header_in->last,
                    r->header_in->end - r->header_in->last);
    } else {
        n = NGX_AGAIN; //如果还未可以读取,则返回NGX_AGAIN 继续等待数据到来
    }
 
    /* 如果是等待状态,则继续放进事件event循环中*/
    if (n == NGX_AGAIN) {
        if (!rev->timer_set) {
            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
            ngx_add_timer(rev, cscf->client_header_timeout);
        }
 
        /* 重新把读事件注册到事件中,每次epoll_wait后,fd的事件类型将会清空,需要再次注册读写事件 */
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return NGX_ERROR;
        }
 
        return NGX_AGAIN;
    }
 
    /* 客户端主动关闭连接 */
    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client prematurely closed connection");
    }
 
    /* 客户端关闭连接或者请求错误,则对request进行析构处理*/
    if (n == 0 || n == NGX_ERROR) {
        c->error = 1;
        c->log->action = "reading client request headers";
 
        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        return NGX_ERROR;
    }
 
    r->header_in->last += n;
 
    return n;
}


4. 处理request信息ngx_http_process_request

ngx_http_process_request:设置read和write的回调函数ngx_http_request_handler,ngx_http_request_handler通过状态机来判断是读事件还是写事件。

调用HTTP模块的filter链,filter链分为header和body链两部分

/**
 * 读取请求数据
 */
static ssize_t
ngx_http_read_request_header(ngx_http_request_t *r)
{
    ssize_t                    n;
    ngx_event_t               *rev;
    ngx_connection_t          *c;
    ngx_http_core_srv_conf_t  *cscf;
 
    c = r->connection;
    rev = c->read;
 
    n = r->header_in->last - r->header_in->pos;
 
    if (n > 0) {
        return n;
    }
 
    /**
     * 接收数据的回调函数:os/ngx_recv.c文件夹中的 ngx_unix_recv
     * ngx_unix_recv:主要调用系统的recv函数,循环接收TCP管道中的数据
     */
    if (rev->ready) {
        n = c->recv(c, r->header_in->last,
                    r->header_in->end - r->header_in->last);
    } else {
        n = NGX_AGAIN; //如果还未可以读取,则返回NGX_AGAIN 继续等待数据到来
    }
 
    /* 如果是等待状态,则继续放进事件event循环中*/
    if (n == NGX_AGAIN) {
        if (!rev->timer_set) {
            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
            ngx_add_timer(rev, cscf->client_header_timeout);
        }
 
        /* 重新把读事件注册到事件中,每次epoll_wait后,fd的事件类型将会清空,需要再次注册读写事件 */
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return NGX_ERROR;
        }
 
        return NGX_AGAIN;
    }
 
    /* 客户端主动关闭连接 */
    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client prematurely closed connection");
    }
 
    /* 客户端关闭连接或者请求错误,则对request进行析构处理*/
    if (n == 0 || n == NGX_ERROR) {
        c->error = 1;
        c->log->action = "reading client request headers";
 
        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        return NGX_ERROR;
    }
 
    r->header_in->last += n;
 
    return n;
}


ngx_http_request_handler:通过事件状态来判断读或者写事件,然后调用回调函数

/**
 * 读取请求数据
 */
static ssize_t
ngx_http_read_request_header(ngx_http_request_t *r)
{
    ssize_t                    n;
    ngx_event_t               *rev;
    ngx_connection_t          *c;
    ngx_http_core_srv_conf_t  *cscf;
 
    c = r->connection;
    rev = c->read;
 
    n = r->header_in->last - r->header_in->pos;
 
    if (n > 0) {
        return n;
    }
 
    /**
     * 接收数据的回调函数:os/ngx_recv.c文件夹中的 ngx_unix_recv
     * ngx_unix_recv:主要调用系统的recv函数,循环接收TCP管道中的数据
     */
    if (rev->ready) {
        n = c->recv(c, r->header_in->last,
                    r->header_in->end - r->header_in->last);
    } else {
        n = NGX_AGAIN; //如果还未可以读取,则返回NGX_AGAIN 继续等待数据到来
    }
 
    /* 如果是等待状态,则继续放进事件event循环中*/
    if (n == NGX_AGAIN) {
        if (!rev->timer_set) {
            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
            ngx_add_timer(rev, cscf->client_header_timeout);
        }
 
        /* 重新把读事件注册到事件中,每次epoll_wait后,fd的事件类型将会清空,需要再次注册读写事件 */
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return NGX_ERROR;
        }
 
        return NGX_AGAIN;
    }
 
    /* 客户端主动关闭连接 */
    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client prematurely closed connection");
    }
 
    /* 客户端关闭连接或者请求错误,则对request进行析构处理*/
    if (n == 0 || n == NGX_ERROR) {
        c->error = 1;
        c->log->action = "reading client request headers";
 
        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        return NGX_ERROR;
    }
 
    r->header_in->last += n;
 
    return n;
}


ngx_http_run_posted_requests:主要用于子请求,子请求会将请求链内容合并到主请求上。会损耗一些性能,因为会重新走一遍write的回调函数ngx_http_core_run_phases解析过程。

例如location配置:

/**
 * 读取请求数据
 */
static ssize_t
ngx_http_read_request_header(ngx_http_request_t *r)
{
    ssize_t                    n;
    ngx_event_t               *rev;
    ngx_connection_t          *c;
    ngx_http_core_srv_conf_t  *cscf;
 
    c = r->connection;
    rev = c->read;
 
    n = r->header_in->last - r->header_in->pos;
 
    if (n > 0) {
        return n;
    }
 
    /**
     * 接收数据的回调函数:os/ngx_recv.c文件夹中的 ngx_unix_recv
     * ngx_unix_recv:主要调用系统的recv函数,循环接收TCP管道中的数据
     */
    if (rev->ready) {
        n = c->recv(c, r->header_in->last,
                    r->header_in->end - r->header_in->last);
    } else {
        n = NGX_AGAIN; //如果还未可以读取,则返回NGX_AGAIN 继续等待数据到来
    }
 
    /* 如果是等待状态,则继续放进事件event循环中*/
    if (n == NGX_AGAIN) {
        if (!rev->timer_set) {
            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
            ngx_add_timer(rev, cscf->client_header_timeout);
        }
 
        /* 重新把读事件注册到事件中,每次epoll_wait后,fd的事件类型将会清空,需要再次注册读写事件 */
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return NGX_ERROR;
        }
 
        return NGX_AGAIN;
    }
 
    /* 客户端主动关闭连接 */
    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client prematurely closed connection");
    }
 
    /* 客户端关闭连接或者请求错误,则对request进行析构处理*/
    if (n == 0 || n == NGX_ERROR) {
        c->error = 1;
        c->log->action = "reading client request headers";
 
        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        return NGX_ERROR;
    }
 
    r->header_in->last += n;
 
    return n;
}

/**
 * 读取请求数据
 */
static ssize_t
ngx_http_read_request_header(ngx_http_request_t *r)
{
    ssize_t                    n;
    ngx_event_t               *rev;
    ngx_connection_t          *c;
    ngx_http_core_srv_conf_t  *cscf;
 
    c = r->connection;
    rev = c->read;
 
    n = r->header_in->last - r->header_in->pos;
 
    if (n > 0) {
        return n;
    }
 
    /**
     * 接收数据的回调函数:os/ngx_recv.c文件夹中的 ngx_unix_recv
     * ngx_unix_recv:主要调用系统的recv函数,循环接收TCP管道中的数据
     */
    if (rev->ready) {
        n = c->recv(c, r->header_in->last,
                    r->header_in->end - r->header_in->last);
    } else {
        n = NGX_AGAIN; //如果还未可以读取,则返回NGX_AGAIN 继续等待数据到来
    }
 
    /* 如果是等待状态,则继续放进事件event循环中*/
    if (n == NGX_AGAIN) {
        if (!rev->timer_set) {
            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
            ngx_add_timer(rev, cscf->client_header_timeout);
        }
 
        /* 重新把读事件注册到事件中,每次epoll_wait后,fd的事件类型将会清空,需要再次注册读写事件 */
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return NGX_ERROR;
        }
 
        return NGX_AGAIN;
    }
 
    /* 客户端主动关闭连接 */
    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client prematurely closed connection");
    }
 
    /* 客户端关闭连接或者请求错误,则对request进行析构处理*/
    if (n == 0 || n == NGX_ERROR) {
        c->error = 1;
        c->log->action = "reading client request headers";
 
        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        return NGX_ERROR;
    }
 
    r->header_in->last += n;
 
    return n;
}


5. 核心分发函数ngx_http_handler

ngx_http_handler函数主要用于设置write事件回调函数。r->write_event_handler = ngx_http_core_run_phases

/**
 * 读取请求数据
 */
static ssize_t
ngx_http_read_request_header(ngx_http_request_t *r)
{
    ssize_t                    n;
    ngx_event_t               *rev;
    ngx_connection_t          *c;
    ngx_http_core_srv_conf_t  *cscf;
 
    c = r->connection;
    rev = c->read;
 
    n = r->header_in->last - r->header_in->pos;
 
    if (n > 0) {
        return n;
    }
 
    /**
     * 接收数据的回调函数:os/ngx_recv.c文件夹中的 ngx_unix_recv
     * ngx_unix_recv:主要调用系统的recv函数,循环接收TCP管道中的数据
     */
    if (rev->ready) {
        n = c->recv(c, r->header_in->last,
                    r->header_in->end - r->header_in->last);
    } else {
        n = NGX_AGAIN; //如果还未可以读取,则返回NGX_AGAIN 继续等待数据到来
    }
 
    /* 如果是等待状态,则继续放进事件event循环中*/
    if (n == NGX_AGAIN) {
        if (!rev->timer_set) {
            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
            ngx_add_timer(rev, cscf->client_header_timeout);
        }
 
        /* 重新把读事件注册到事件中,每次epoll_wait后,fd的事件类型将会清空,需要再次注册读写事件 */
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return NGX_ERROR;
        }
 
        return NGX_AGAIN;
    }
 
    /* 客户端主动关闭连接 */
    if (n == 0) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                      "client prematurely closed connection");
    }
 
    /* 客户端关闭连接或者请求错误,则对request进行析构处理*/
    if (n == 0 || n == NGX_ERROR) {
        c->error = 1;
        c->log->action = "reading client request headers";
 
        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
        return NGX_ERROR;
    }
 
    r->header_in->last += n;
 
    return n;
}


4、HTTP模块的阶段处理PHASE handler

上一章我们讲解了HTTP request的解析过程。我们基本知道了Nginx是如何解析HTTP的行和头。

这一章我们主要讲解Nginx的阶段处理(PHASE 状态机实现)。阶段处理的概念如何理解,我举个例子:一个HTTP请求过来,除了解析HTTP的行和头外,还需要解析URI的rewrite、接受HTTP BODY中的POST数据、转发给后端JAVA/PHP服务器进行数据处理等等一系列的操作。

Nginx的PHASE阶段处理共包含11部分,通过这11个阶段的处理,就能完整的处理一个HTTP请求了。

HTTP模块11个阶段处理

在这里插入图片描述

typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0, /* 读取请求内容阶段 */
 
    NGX_HTTP_SERVER_REWRITE_PHASE, /* Server请求地址重写阶段 */
 
    NGX_HTTP_FIND_CONFIG_PHASE, /* 配置查找阶段 */
    NGX_HTTP_REWRITE_PHASE,  /* Location请求地址重写阶段 */
    NGX_HTTP_POST_REWRITE_PHASE, /* 请求地址重写提交阶段 */
 
    NGX_HTTP_PREACCESS_PHASE,  /* 访问权限检查准备阶段 */
 
    NGX_HTTP_ACCESS_PHASE,  /* 访问权限检查阶段 */
    NGX_HTTP_POST_ACCESS_PHASE,  /* 访问权限检查提交阶段 */
 
    NGX_HTTP_TRY_FILES_PHASE,   /* 配置项try_files处理阶段 */
    NGX_HTTP_CONTENT_PHASE,    /* 内容产生阶段 */
 
    NGX_HTTP_LOG_PHASE  /* 日志模块处理阶段 */
} ngx_http_phases;


  • 四个phase是不支持添加http功能的handler的,这四个阶段分别是NGX_FIND_CONFIG_PHASE、NGX_POST_REWRITE_PHASE、NGX_POST_ACCESS_PHASE、NGX_TRY_FILES_PHASE
  • 如果需要自定义添加模块挂载的Nginx的阶段处理上,推荐挂载到这三个阶段:NGX_HTTP_PREACCESS_PHASE、NGX_HTTP_ACCESS_PHASE、NGX_HTTP_CONTENT_PHASE-

阶段处理的初始化ngx_http_block

我们从前面的篇章中知道,HTTP模块的初始化从 ngx_http_block这个方法开始。阶段处理的初始化也在这个方法中。

/**
 *ngx_http_commands 命令集的回调函数
 *HTTP模块初始化的入口函数
 *
 */
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
.........
    /* 初始化阶段数组 只有这里面的6个阶段可以挂载模块 */
    if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
........
    /**
     * 初始化阶段处理
     */
    if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
}


ngx_http_init_phases 初始化阶段数组
/**
 * 初始化阶段处理数组
 * 每一个可以挂载模块的阶段,都定义了一个cmcf->phases[?].handlers的数组
 * 每个阶段被调用的时候,都会去遍历改阶段处理数组下需要处理的逻辑函数
 */
static ngx_int_t
ngx_http_init_phases(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
    if (ngx_array_init(&cmcf->phases[NGX_HTTP_POST_READ_PHASE].handlers,
                       cf->pool, 1, sizeof(ngx_http_handler_pt))
        != NGX_OK)
    {
        return NGX_ERROR;
    }
 
    if (ngx_array_init(&cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers,
                       cf->pool, 1, sizeof(ngx_http_handler_pt))
        != NGX_OK)
    {
        return NGX_ERROR;
    }
 
    if (ngx_array_init(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers,
                       cf->pool, 1, sizeof(ngx_http_handler_pt))
        != NGX_OK)
    {
        return NGX_ERROR;
    }
 
    if (ngx_array_init(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers,
                       cf->pool, 1, sizeof(ngx_http_handler_pt))
        != NGX_OK)
    {
        return NGX_ERROR;
    }
 
    if (ngx_array_init(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers,
                       cf->pool, 2, sizeof(ngx_http_handler_pt))
        != NGX_OK)
    {
        return NGX_ERROR;
    }
 
    if (ngx_array_init(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers,
                       cf->pool, 4, sizeof(ngx_http_handler_pt))
        != NGX_OK)
    {
        return NGX_ERROR;
    }
 
    if (ngx_array_init(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers,
                       cf->pool, 1, sizeof(ngx_http_handler_pt))
        != NGX_OK)
    {
        return NGX_ERROR;
    }
 
    return NGX_OK;
}


ngx_http_init_phase_handlers 初始化阶段处理
/**
 * 初始化阶段处理
 *
 * nginx在接收并解析完请求行,请求头之后,就会依次调用各个phase handler
 *
 * 	NGX_HTTP_POST_READ_PHASE	读取请求内容阶段
	NGX_HTTP_SERVER_REWRITE_PHASE	Server请求地址重写阶段
	NGX_HTTP_FIND_CONFIG_PHASE	配置查找阶段
	NGX_HTTP_REWRITE_PHASE	Location请求地址重写阶段
	NGX_HTTP_POST_REWRITE_PHASE	请求地址重写提交阶段
	NGX_HTTP_PREACCESS_PHASE	访问权限检查准备阶段
	NGX_HTTP_ACCESS_PHASE	访问权限检查阶段
	NGX_HTTP_POST_ACCESS_PHASE	访问权限检查提交阶段
	NGX_HTTP_TRY_FILES_PHASE	配置项try_files处理阶段
	NGX_HTTP_CONTENT_PHASE	内容产生阶段
	NGX_HTTP_LOG_PHASE	日志模块处理阶段
 */
static ngx_int_t
ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
    ngx_int_t                   j;
    ngx_uint_t                  i, n;
    ngx_uint_t                  find_config_index, use_rewrite, use_access;
    ngx_http_handler_pt        *h;
    ngx_http_phase_handler_t   *ph;
    ngx_http_phase_handler_pt   checker;
 
    cmcf->phase_engine.server_rewrite_index = (ngx_uint_t) -1;
    cmcf->phase_engine.location_rewrite_index = (ngx_uint_t) -1;
    find_config_index = 0;
    use_rewrite = cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers.nelts ? 1 : 0;
    use_access = cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers.nelts ? 1 : 0;
 
    n = use_rewrite + use_access + cmcf->try_files + 1 /* find config phase */;
 
    for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
        n += cmcf->phases[i].handlers.nelts;
    }
 
    ph = ngx_pcalloc(cf->pool,
                     n * sizeof(ngx_http_phase_handler_t) + sizeof(void *));
    if (ph == NULL) {
        return NGX_ERROR;
    }
 
    cmcf->phase_engine.handlers = ph;
    n = 0;
 
    for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
        h = cmcf->phases[i].handlers.elts;
 
        switch (i) {
 
        /* server中的rewrite*/
        case NGX_HTTP_SERVER_REWRITE_PHASE:
            if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) {
                cmcf->phase_engine.server_rewrite_index = n;
            }
            checker = ngx_http_core_rewrite_phase;
 
            break;
 
        /* 根据URI查找 location */
        case NGX_HTTP_FIND_CONFIG_PHASE:
            find_config_index = n;
 
            ph->checker = ngx_http_core_find_config_phase;
            n++;
            ph++;
 
            continue;
 
        /* localtion级别的rewrite */
        case NGX_HTTP_REWRITE_PHASE:
            if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) {
                cmcf->phase_engine.location_rewrite_index = n;
            }
            checker = ngx_http_core_rewrite_phase;
 
            break;
 
        /* server、location级别的rewrite都是在这个phase进行收尾工作的 */
        case NGX_HTTP_POST_REWRITE_PHASE:
            if (use_rewrite) {
                ph->checker = ngx_http_core_post_rewrite_phase;
                ph->next = find_config_index;
                n++;
                ph++;
            }
 
            continue;
 
        /* 细粒度的access,比如权限验证、存取控制 */
        case NGX_HTTP_ACCESS_PHASE:
            checker = ngx_http_core_access_phase;
            n++;
            break;
 
        /* 根据上述两个phase得到access code进行操作 */
        case NGX_HTTP_POST_ACCESS_PHASE:
            if (use_access) {
                ph->checker = ngx_http_core_post_access_phase;
                ph->next = n;
                ph++;
            }
 
            continue;
 
        /* 实现try_files指令 */
        case NGX_HTTP_TRY_FILES_PHASE:
            if (cmcf->try_files) {
                ph->checker = ngx_http_core_try_files_phase;
                n++;
                ph++;
            }
 
            continue;
 
        /* 生成http响应 */
        case NGX_HTTP_CONTENT_PHASE:
            checker = ngx_http_core_content_phase;
            break;
 
        default:
            checker = ngx_http_core_generic_phase;
        }
 
        n += cmcf->phases[i].handlers.nelts;
 
        for (j = cmcf->phases[i].handlers.nelts - 1; j >=0; j--) {
            ph->checker = checker;
            ph->handler = h[j];
            ph->next = n;
            ph++;
        }
    }
 
    return NGX_OK;
}


阶段处理过程ngx_http_core_run_phases

阶段处理核心函数ngx_http_core_run_phases

先看下阶段处理返回的几个状态含义:
NGX_OK: 表示该阶段已经处理完成,需要转入下一个阶段;
NGX_DECLINED: 表示需要转入本阶段的下一个handler继续处理;
NGX_AGAIN, NGX_DONE:表示需要等待某个事件发生才能继续处理(比如等待网络IO),此时Nginx为了不阻塞其他请求的处理,必须中断当前请求的执行链,等待事件发生之后继续执行该handler;
NGX_ERROR: 表示发生了错误,需要结束该请求。

/**
 * 11个阶段处理HTTP请求
 */
void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ngx_int_t                   rc;
    ngx_http_phase_handler_t   *ph;
    ngx_http_core_main_conf_t  *cmcf;
 
    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
 
    ph = cmcf->phase_engine.handlers;
 
    /* 遍历解析和处理各个阶段的HTTP请求 如果返回rc==NGX_AGAIN 则交由下一个阶段处理;返回OK则返回结果  */
    while (ph[r->phase_handler].checker) {
 
		rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
 
        if (rc == NGX_OK) {
            return;
        }
    }
}
 
/**
 * 内容接收阶段
 */
ngx_int_t
ngx_http_core_generic_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
{
    ngx_int_t  rc;
 
    /*
     * generic phase checker,
     * used by the post read and pre-access phases
     */
 
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "generic phase: %ui", r->phase_handler);
 
    /* handler 回调函数*/
    rc = ph->handler(r);
 
    /* 本阶段处理完成,跳转到下一个阶段处理 */
    if (rc == NGX_OK) {
        r->phase_handler = ph->next;
        return NGX_AGAIN;
    }
 
    /* 本阶段当前的回调函数处理完成,继续执行本阶段其他回调函数 */
    if (rc == NGX_DECLINED) {
        r->phase_handler++;
        return NGX_AGAIN;
    }
 
    if (rc == NGX_AGAIN || rc == NGX_DONE) {
        return NGX_OK;
    }
 
    /* rc == NGX_ERROR || rc == NGX_HTTP_...  */
 
    ngx_http_finalize_request(r, rc);
 
    return NGX_OK;
}


挂载自定义模块

Nginx的自定义模块在http/modules/目录下。如果你编写了一个模块,并且想在阶段处理中,编写自己的模块,那么就非常简单了。只要在模块init初始化的时候,将回调函数注册到阶段上,就能实现自定义阶段处理拦截。

我们看下ngx_http_static_module.c模块的阶段处理的注册。

/**
 * 模块初始化
 */
static ngx_int_t
ngx_http_static_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;
 
    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
    /* 注册到NGX_HTTP_CONTENT_PHASE阶段 */
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }
 
    /* 设置阶段回调函数 */
    *h = ngx_http_static_handler;
 
    return NGX_OK;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值