Nginx是一个高性能的静态HTTP服务器和反向代理服务器,Nginx本身不支持现在流行的 JSP、ASP、PHP等动态页面,但是它可以通过反向代理将请求发送到后端的服务器,例如 Tomcat、Apache等来完成动态页面的请求处理。
结合Nginx开发从入门到精通一书和Nginx源码学习服务器的高并发处理。
服务器的架构大同小异,而性能的差异主要来自对数据的处理方式上,也即进程模型和事件模型上。一个客户端请求的数据如何进行接收、存储、解析、返回是服务器做的最主要的工作。
##源码文件结构
源码位于src
目录下,分为七个部分。
├── core #core module代码,nginx服务入口
├── event #事件处理逻辑的封装
├── http #作为web/http/proxy server运行时核心模块
├── mail #作为pop3/imap/smtp proxy server核心模块
├── misc #一些utils,定义test个profiler外围模块逻辑
├── os #对各个平台抽象逻辑的封装
└── stream #
最基础的数据结构
C++之所以不被用来写网站是因为,网站的核心是处理字符串。而C++的字符串处理实在比较弱,还要不断惦记GC和内存泄漏。
而Nginx首先封装了一下字符串的数据结构和API。定义位于nginx/src/core/nginx_string.h
中。
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
Nginx中一个字符串被表示为指针data指示首地址+len指示长度的方式,这种方式唯一定位一个字符串。和标准的glibc API
用\0
的方式标识字符串结束不同,它有很多好处。
- 通过长度表示字符串长度,减少计算字符串长度的步骤
- 可以重复引用一段字符串内存,减少不必要的内存分配
这样的表示是不是似曾相识?在Redis的sds中有类似的处理。
struct sdshdr {
unsigned int len;
unsigned int free;
char buf[];
};
再看看它的API,API以宏的方式定义。
#define ngx_string(str) { sizeof(str) - 1, (u_char *) str }
#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的字符串,用法是
int main()
{
ngx_str_t stringA = ngx_string("Stop");
printf("len: %d \n", stringA.len);
printf("address: %p \n", stringA.data);
char* p = (char*)("HELLO WORLD");
printf("address: %p \n", p);
printf("string %s \n", p);
return 0;
}
这里直接是宏展开,对结构体进行赋值。先生成了一个临时变量"stop"
,然后把临时变量的长度和地址赋值给结构体。ngx_string
与ngx_null_string
是{,}
格式的,而结构体只能在初始化时进行整体赋值,因而API只能用于赋值时初始化。还需要注意str
必须是常量字符串,因为sizeof
是以\0
为结束标志的。
打印结果
len: 4
address: 0x8048530
address: 0x804854d
string HELLO WORLD
内存管理池ngx_pool_t
ngx_pool_t
是一个资源管理池。它提供一种机制帮助管理一系列的资源(如内存、文件等),接管这些资源的所有权,负责资源的使用和释放。类似于C++中的shared_ptr
智能指针。
ngx_pool_t
定义在文件nginx/src/core/ngx_palloc.h
中,相关的内存分配函数(如ngx_alloc
)定义在nginx/src/os/unix/ngx_alloc.h
中。
typedef struct ngx_pool_s ngx_pool_t; //core.h中
struct ngx_pool_s {
ngx_pool_data_t d;
size_t max;
ngx_pool_t *current;
ngx_chain_t *chain;
ngx_pool_large_t *large;
ngx_pool_cleanup_t *cleanup;
ngx_log_t *log;
};
ngx_pool_data_t
指示资源池数据块的位置信息。
size_t
是数据块的最大值
整个内存管理如图所示:
再看一下相关的API函数。
ngx_alloc
使用malloc
进行内存分配,同时把错误信息写到log文件。
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_create_pool
使用posix_memalign()
申请大小为NGX_POOL_ALIGNMENT
字节对其的内存。
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
if (p == NULL) {
return NULL;
}
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
p->d.end = (u_char *) p + size;
p->d.next = NULL;
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->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;
return p;
}
p->d.last
位移ngx_pool_t
大小指向数据区段未使用部分的开始。
p->d.last
指向数据区段未使用部分的结束。
可见分配的size
大小的内存一部分要分给ngx_pool_t
结构。
p->max
表示分配的最大内存为NGX_MAX_ALLOC_FROM_POOL
。
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize -1)
ngx_pagesize = getpagesize();
最大限制为一个分页大小,在x86上其返回值应为4096Bytes = 4KB。
##参考
http://tengine.taobao.org/book/chapter_02.html
https://github.com/antirez/redis/blob/unstable/src/sds.h
https://code.google.com/p/nginxsrp/wiki/NginxCodeReview