nginx源码分析之模块化


模块化编程是C的核心思想,而nginx将这一思想发挥到淋漓尽致。

在阅读源码之前,自己动手写一个模块是最好的入门方式,本文将引导读者如何写一模块,
并分析nginx是如何设计模块化的。

我将以hello world为例,实现访问 http://yourdomain/hello 时,页面上将出现 hello world

1、创建一个目录: hello
2、创建源文件: ngx_http_hello_module.c
3、创建模块配置文件: config
4、创建测试nginx配置文件:nginx.conf

假设放在/tmp下,代码层次如下:
/tmp/hello
        ngx_http_hello_module.c
        config
        nginx.conf

4、将模块编进nginx: ./configure --add-module=/tmp/hello
输出包含如下信息,表示成功
configuring additional modules
adding module in /tmp/hello
 +  was configured


5、开始实现
在实现之前,先分析下模块化在nginx里面是如何设计和实现的。

首先,每个模块就是一个实例,nginx由众多模块组成,协同作用,共同完成服务。
比如 ngx_core_module, ngx_epoll_module, ngx_http_log_module, 还有我们的 ngx_http_hello_module。
这个实例变量,相当于一个形象代言人,是个极具丰富的模型。

每个项目都有配置文件,nginx也不例外。配置文件有多个配置选项,这些配置选项丰富了nginx的服务行为。比如
daemon            on;  # 以守护进程方式启动
worker_processes  4; # 指定工作进程的数量为4

nginx在解析和处理配置文件的方式非常高明
先看下结构体
/* 模块结构体 */
ngx_module_t {
    void                      *ctx;          /* 模块上下文 */
    ngx_command_t    *commands;   /* 指令集 */
}

/* 指令结构体 */
ngx_command_t {
    ngx_str_t             name;        /* 指令名称,用来匹配配置选项的 */         
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); /* 指定如何将配置选项值放到模块配置结构体的 */
}

ngx_xxx_module_t : 模块上下文结构体,有4种
                   ngx_core_module_t, ngx_event_module_t, ngx_http_module_t, ngx_mail_module_t
                   以ngx_core_module_t为例
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_xxx_conf_t     : 模块配置结构体,每个模块都有自己的一个,比如 ngx_core_conf_t,以ngx_core_conf_t为例:
ngx_core_conf_t {
     ngx_flag_t   daemon;                   /* 是否以守护进程启动 */
     ngx_int_t    worker_processes;     /* 工作进程数 */
}

这4个重要的结构体,所做的事情就是将配置文件里值,能让程序代码里轻松的获取配置选项的值。
其中 ngx_module_t, ngx_command_t, ngx_xxx_module_t互相配合,共同产生 ngx_xxx_conf_t。

比如nginx是如何实现是否以守护进程方式启动的,代码如下:
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
if ( ccf->daemon) {
    if (ngx_daemon(cycle->log) != NGX_OK) {
        return 1;
    }
}

注:nginx配置是具有层次的,尤其以http更为复杂,本文只让您明白模块化的大体原理,不作所有的讲解。

清楚nginx如何设计后,我们开始实现ngx_http_hello_module。

直接上代码,请看代码里的注释,我将标出步骤
ngx_http_hello_module.c:
/*
 * Copyright (C) FatHong
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


/* 5、配置结构体,这个专属于 ngx_http_hello_module */
typedef struct {
    ngx_str_t    test;          /* test选项的值将存储在这里 */
} ngx_http_hello_loc_conf_t;


static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_hello(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);


/* 4、指令集 */
static ngx_command_t ngx_http_hello_commands[] = {
    { ngx_string("hello"),                  /* hello指令,对应配置选项 hello; */
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_hello,
      0,
      0,
      NULL },

    { ngx_string("test"),                   /* test指令,对应配置选项 test  "hello world"; */
      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_str_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_hello_loc_conf_t, test),    /* test选项的值将存储在这里 */
      NULL },

    ngx_null_command
};


/* 2、模块上下文,这里是http类型模块 */
static ngx_http_module_t ngx_http_hello_module_ctx = {
    NULL,                              /* preconfiguration */
    NULL,                              /* postconfiguration */

    NULL,                              /* create main configuration */
    NULL,                              /* init main configuration */

    NULL,                              /* create server configuration */
    NULL,                              /* merge server configuration */

    ngx_http_hello_create_loc_conf,    /* create location configuration:3.1、配置结构体钩子注册 */
    NULL                               /* merge location configuration */
};


/* 1、模块创建 */
ngx_module_t ngx_http_hello_module = {
    NGX_MODULE_V1,
    &ngx_http_hello_module_ctx,    /* module context:模块上下文 */
    ngx_http_hello_commands,       /* module directives:模块指令集 */
    NGX_HTTP_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
};

static u_char ngx_hello_string[] = "hello world";

/* 处理函数 */
static ngx_int_t
ngx_http_hello_handler(ngx_http_request_t *r)
{
    ngx_int_t    rc;
    ngx_buf_t   *b;
    ngx_chain_t  out;
    ngx_http_hello_loc_conf_t *hrcf;

    /* 设置响应内容格式,这里为text/html */
    r->headers_out.content_type_len = sizeof("text/html") - 1;
    r->headers_out.content_type.len = sizeof("text/html") - 1;
    r->headers_out.content_type.data = (u_char *) "text/html";

    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    out.buf = b;
    out.next = NULL;

    b->pos = ngx_hello_string;
    b->last = ngx_hello_string + sizeof(ngx_hello_string) - 1;
    b->memory = 1;
    b->last_buf = 1;

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = sizeof(ngx_hello_string) - 1;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* 演示如何获取配置选项的值 */
    hrcf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
    printf("test: %s\n", hrcf->test.data); // 直接输出到标准输出

    return ngx_http_output_filter(r, &out);
}


/* 3.2 配置结构体就是这样产生的 */
static void *
ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_hello_loc_conf_t  *hlcf;

    hlcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
    if (hlcf == NULL) {
        return NULL;
    }

    return hlcf;
}


static char *
ngx_http_hello(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_hello_handler; /* 相当于一个钩子 */

    return NGX_CONF_OK;
} 

config:
ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c"


nginx.conf:

worker_processes 4;
daemon  off;

events {
    worker_connections 1024;
}

http {
     server {
        location / {
            hello;
            test  "hello world"; # 这个指令用于演示配置结构体
        }
     }
}


> ./configure --add-module=/tmp/hello
> make
> ./objs/nginx -c /tmp/hello/nginx.conf

访问 http://yourdomain/hello
页面出现 hello world

并且守护进程输出 test: hello world

本文结束,阅读原文:http://nglua.com/reads/2.html

 


你可能感兴趣的:(nginx源码分析)