在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_conf
和ngx_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