yzprofile's Notebook

All Posts| Note| Books| About
25 Sep 2012

Tengine Proc模块介绍

Tengine 1.3.0版本发布的时候增加了一个支持开发者通过编写Tengine模块的形式, 来让Tengine启动独立的进程做事情的一个特性.

这个特性我自己是非常满意并充满期待的,利用Nginx的配置解析和良好的模块化,以及底层网络事件的接口封装,可以让开发者非常容易的开发出一套高性能,高可扩展,跨平台的TCP server,并且Nginx也内置了许多优秀的数据结构的实现。当然,想像总是美好的。这个年代有谁再会去裸写TCP server的?写TCP Server有谁会去用Nginx的架子呢?实事上,这个模块的诞生,也主要是为了给Tengine提供一个辅助进程,解决类似Cache Manager这样进程的问题。

下面看下该如何开发一个proc模块:

完整的例子可以在这里得到: ngx_proc_daytime_module

file:config

ngx_addon_name=ngx_proc_daytime_module
PROCS_MODULES="$PROCS_MODULES ngx_proc_daytime_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_proc_daytime_module.c"

file:ngx_proc_daytime_module.c

static ngx_command_t ngx_proc_daytime_commands[] = {

    { ngx_string("listen"),
      NGX_PROC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      NGX_PROC_CONF_OFFSET,
      offsetof(ngx_proc_daytime_conf_t, port),
      NULL },

    { ngx_string("daytime"),
      NGX_PROC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_PROC_CONF_OFFSET,
      offsetof(ngx_proc_daytime_conf_t, enable),
      NULL },

      ngx_null_command
};


static ngx_proc_module_t ngx_proc_daytime_module_ctx = {
    ngx_string("daytime"),            /* module name */
    NULL,                             /* create main conf */
    NULL,                             /* init main conf */
    ngx_proc_daytime_create_conf,     /* create proc conf */
    ngx_proc_daytime_merge_conf,      /* merge proc conf */
    ngx_proc_daytime_prepare,         /* before start process */
    ngx_proc_daytime_process_init,    /* init process */
    ngx_proc_daytime_loop,            /* process loop */
    ngx_proc_daytime_exit_process     /* exit process */
};


ngx_module_t ngx_proc_daytime_module = {
    NGX_MODULE_V1,
    &ngx_proc_daytime_module_ctx,
    ngx_proc_daytime_commands,
    NGX_PROC_MODULE,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NGX_MODULE_V1_PADDING
};

最主要的也就是这几个回调函数了, 其它的同普通Nginx模块都是类似的.

static ngx_int_t ngx_proc_daytime_prepare(ngx_cycle_t *cycle);
static ngx_int_t ngx_proc_daytime_process_init(ngx_cycle_t *cycle);
static ngx_int_t ngx_proc_daytime_loop(ngx_cycle_t *cycle);
static void ngx_proc_daytime_exit_process(ngx_cycle_t *cycle);

ngx_proc_daytime_prepare,在启动进程前校验配置等信息,决定是否启动进程。或者可以利用这一步,在主进程内初始化一些需要子进程继承的数据。

static ngx_int_t
ngx_proc_daytime_prepare(ngx_cycle_t *cycle)
{
    ngx_proc_daytime_conf_t  *pbcf;

    pbcf = ngx_proc_get_conf(cycle->conf_ctx, ngx_proc_daytime_module);
    if (!pbcf->enable) {
        return NGX_DECLINED;
    }

    if (pbcf->port == 0) {
        return NGX_DECLINED;
    }

    return NGX_OK;
}

例子里我们只是判断了一下是否开启daytime模块,如果开启了那么就启动进程。

ngx_proc_daytime_process_init,在进程启动后进行进程初始化。

static ngx_int_t
ngx_proc_daytime_process_init(ngx_cycle_t *cycle)
{
    int                       reuseaddr;
    ngx_event_t              *rev;
    ngx_socket_t              fd;
    ngx_connection_t         *c;
    struct sockaddr_in        sin;
    ngx_proc_daytime_conf_t  *pbcf;

    pbcf = ngx_proc_get_conf(cycle->conf_ctx, ngx_proc_daytime_module);
    fd = ngx_socket(AF_INET, SOCK_STREAM, 0);

    ...

    c = ngx_get_connection(fd, cycle->log);

    ...
    
    c->log = cycle->log;
    rev = c->read;
    rev->log = c->log;
    rev->accept = 1;
    rev->handler = ngx_proc_daytime_accept;

    if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
        return NGX_ERROR;
    }

    pbcf->fd = fd;

    return NGX_OK;
}

例子里,我们创建一个监听socket,并且挂上读事件。

ngx_proc_daytime_loop,每次事件循环,即epoll_wait返回一次,将执行一次loop回调。因为是事件驱动,所以例子里循环里没有实现什么别的功能,只是打印一条日志。

ngx_proc_daytime_exit_process,退出进之前的回调。例子里关闭了监听socket。

static void
ngx_proc_daytime_exit_process(ngx_cycle_t *cycle)
{
    ngx_proc_daytime_conf_t *pbcf;

    pbcf = ngx_proc_get_conf(cycle->conf_ctx, ngx_proc_daytime_module);

    ngx_close_socket(pbcf->fd);
}

并且可以使用ngx_proc_get_confngx_proc_get_main_conf来得到daytime模块的相关配置。当然,作为独立的进程,你可以使用全局变量来作为进程执行的上下文。所以proc模块只提供了get conf的接口。

static void
ngx_proc_daytime_accept(ngx_event_t *ev)
{
    u_char             sa[NGX_SOCKADDRLEN], buf[256], *p;
    socklen_t          socklen;
    ngx_socket_t       s;
    ngx_connection_t  *lc;

    lc = ev->data;
    s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
    if (s == -1) {
        return;
    }

    if (ngx_nonblocking(s) == -1) {
        goto finish;
    }

    /*
      Daytime Protocol

      http://tools.ietf.org/html/rfc867

      Weekday, Month Day, Year Time-Zone
    */
    p = ngx_sprintf(buf, "%s, %s, %d, %d, %d:%d:%d-%s",
                    week[ngx_cached_tm->tm_wday],
                    months[ngx_cached_tm->tm_mon],
                    ngx_cached_tm->tm_mday, ngx_cached_tm->tm_year,
                    ngx_cached_tm->tm_hour, ngx_cached_tm->tm_min,
                    ngx_cached_tm->tm_sec, ngx_cached_tm->tm_zone);

    ngx_write_fd(s, buf, p - buf);

finish:
    ngx_close_socket(s);
}

例子中每个新建连接的accept回调,发送当前时间后关闭连接。

测试配置

processes {
    process daytime {
        daytime on;
        listen 8888;
    }
}
yuen@MacBook-Air ~/
$>> telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Wednesday, October, 26, 2012, 17:52:55-KRATConnection closed by foreign host.

嗯,就到这里。

have fun. : ) EOF