在“协作半驻留式服务器程序开发框架 --- 基于 Postfix 服务器框架改造 “文章中,介绍了ACL库中协作式半驻留服务器程序框架,本文将以其中第4)种(多线程进程池)开发框架为基础编写一个简单的 demo 程序,使大家熟悉这类服务器程序的开发方式。
该 demo 一个简单的 echo 服务器程序,主要由 main.c, service_main.c, service_var.c, service_main.h, service_var.h, Makefile 六个文件组成。下面分别介绍一下各个文件的主要功能。
1) main.c 主程序
#include "lib_acl.h" /* ACL库的头文件 */
#include <assert.h>
#include "service_main.h"
#include "service_var.h"
/* 测试函数入口 */
static void service_test(void)
{
const char *addr = "127.0.0.1:8885";
ACL_VSTREAM *sstream, *client;
int ret;
sstream = acl_vstream_listen(addr, 32); /* 创建服务端口的监听套接口并转化流 */
assert(sstream != NULL);
acl_xinetd_params_int_table(NULL, var_conf_int_tab); /* 设置整数类型的配置项 */
acl_xinetd_params_str_table(NULL, var_conf_str_tab); /* 设置字符串类型的配置项 */
acl_xinetd_params_bool_table(NULL, var_conf_bool_tab); /* 设置 bool 类型的配置项 */
printf("listen %s ok\n", addr);
while (1) {
client = acl_vstream_accept(sstream, NULL, 0); /* 等待客户端连接 */
if (client == NULL) {
printf("accept error: %s\n", acl_last_serror());
break;
}
/* 获得一个客户端连接流 */
while (1) {
/* 处理该客户端的请求 */
ret = service_main(client, NULL);
if (ret < 0) {
/* 关闭客户端连接流 */
acl_vstream_close(client);
break;
}
if (ret > 0) {
/* service_main() 内部关闭了客户端流 */
break;
}
}
}
/* 测试结束,关闭监听套接口 */
acl_vstream_close(sstream);
}
/* 程序入口 */
int main(int argc, char *argv[])
{
if (argc == 2 && strcasecmp(argv[1], "test") == 0) {
/* 测试入口 */
service_test();
} else {
/* 服务器框架入口 */
acl_ioctl_app_main(argc, argv, service_main, NULL,
ACL_APP_CTL_INIT_FN, service_init,
ACL_APP_CTL_EXIT_FN, service_exit,
ACL_APP_CTL_CFG_BOOL, var_conf_bool_tab,
ACL_APP_CTL_CFG_INT, var_conf_int_tab,
ACL_APP_CTL_CFG_STR, var_conf_str_tab,
ACL_APP_CTL_END);
}
return (0);
}
该文件中,可以看到两个分支,一个是测试用入口 service_test(),另一个是服务器框架入口 acl_ioctl_app_main()。其中的 service_test() 主要是为了开发者调试自己的程序用,此时程序运行是独立运行的,不需要 acl_master 主进程参与;而 acl_ioctl_app_main() 则为服务器框架入口,需要 acl_master 主进程进行控制,这主要是用在生产环境中。
2) service_main.c 任务处理函数
#include "lib_acl.h"
#include "service_var.h"
#include "service_main.h"
/* 初始化函数 */
void service_init(void *init_ctx acl_unused)
{
const char *myname = "service_init";
acl_msg_info("%s: init ok ...", myname);
}
/* 进程退出前调用的函数 */
void service_exit(void *arg acl_unused)
{
const char *myname = "service_exit";
acl_msg_info("%s: exit now ...", myname);
}
/* 协议处理函数入口 */
int service_main(ACL_VSTREAM *client, void *run_ctx acl_unused)
{
const char *myname = "service_main";
char buf[1024];
int ret;
ret = acl_vstream_gets(client, buf, sizeof(buf));
if (ret == ACL_VSTREAM_EOF) {
if (var_cfg_debug_enable)
acl_msg_info("%s: close client now, (%s)",
myname, var_cfg_debug_msg);
return (-1); /* 返回负值以使框架内部关闭 client 数据流 */
}
if (acl_vstream_writen(client, buf, ret) == ACL_VSTREAM_EOF) {
if (var_cfg_debug_enable)
acl_msg_info("%s: write to client error, close now(%s)",
myname, var_cfg_debug_msg);
return (-1); /* 返回负值以使框架内部关闭 client 数据流 */
}
if (var_cfg_keep_alive) {
if (var_cfg_debug_enable)
acl_msg_info("%s: keep alive, wait client...", myname);
return (0); /* 返回 0 以使框架内部自动监听该数据流从而保持长连接 */
} else {
/* 可以在此处返回 =1, 使框架内部自动关闭 client 数据流,
* 也可以在此处直接关闭 client 数据流,同时返回 1 告诉框架
* 该流已经被用户关闭了不必再关心该 client 数据流.
*/
acl_vstream_close(client);
return (1);
}
}
该文件中主要有三个函数(这三个函数都是在 main.c 中的 acl_ioctl_app_main() 设置的,这样服务器框架就以回调的方式分别调用它们): service_init(), 进程初始化回调函数,用户可以在些函数中做一些全局化的初始化,如数据库的连接建立 ;service_main(), 任务调用入口,每当有一个客户端连接建立时,服务器框架便会调用此函数由应用来处理与客户端的信息交流,其中的 client 参数为由服务器框架已经与客户端之间建立起的数据流,参数 run_ctx 可以在 acl_ioctl_app_main() 中进行设置;service_exit(), 当进程退出前便会回调此函数,用户可以在此函数里做一些清理工作。
3) service_var.c 配置参数源码
#include "lib_acl.h"
#include "service_var.h"
char *var_cfg_debug_msg;
ACL_CFG_STR_TABLE var_conf_str_tab[] = {
{ "debug_msg", "test_msg", &var_cfg_debug_msg },
{ 0, 0, 0 }
};
int var_cfg_debug_enable;
int var_cfg_keep_alive;
ACL_CFG_BOOL_TABLE var_conf_bool_tab[] = {
{ "debug_enable", 1, &var_cfg_debug_enable },
{ "keep_alive", 1, &var_cfg_keep_alive },
{ 0, 0, 0 }
};
int var_cfg_io_timeout;
ACL_CFG_INT_TABLE var_conf_int_tab[] = {
{ "io_timeout", 120, &var_cfg_io_timeout, 0, 0 },
{ 0, 0 , 0 , 0, 0 }
};
该文件主要是一些全局化的配置项参数,主要有三类:int 类型的配置项,bool 类型的配置项,字符串类型的配置项,分别被设置在 var_conf_int_tab,var_conf_bool_tab,var_conf_str_tab 可,而这三个变量也是通过 acl_ioctl_app_main() 以参数方式传递给服务器框架,由框架读取配置文件后将配置内容分别设置在这三个变量中的具体配置变量中。
4) service_main.h 为 service_main.c 的头文件
#ifndef __SERVICE_MAIN_INCLUDE_H__
#define __SERVICE_MAIN_INCLUDE_H__
#include "lib_acl.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* 初始化函数,服务器模板框架启动后仅调用该函数一次
* @param init_ctx {void*} 用户自定义类型指针
*/
extern void service_init(void *init_ctx);
/**
* 进程退出时的回调函数
* @param exist_ctx {void*} 用户自定义类型指针
*/
extern void service_exit(void *exit_ctx);
/**
* 协议处理函数入口
* @param stream {ACL_VSTREAM*} 客户端数据连接流
* @param run_ctx {void*} 用户自定义类型指针
*/
extern int service_main(ACL_VSTREAM *stream, void *run_ctx);
#ifdef __cplusplus
}
#endif
#endif
5) service_var.h 为 service_var.c 的头文件
#ifndef __SERVICE_VAR_INCLUDE_H__
#define __SERVICE_VAR_INCLUDE_H__
#include "lib_acl.h"
/*------------- 字符串配置项 ----------------*/
extern ACL_CFG_STR_TABLE var_conf_str_tab[];
/* 日志调试输出信息 */
extern char *var_cfg_debug_msg;
/*-------------- 布尔值配置项 ---------------*/
extern ACL_CFG_BOOL_TABLE var_conf_bool_tab[];
/* 是否输出日志调试信息 */
extern int var_cfg_debug_enable;
/* 是否与客户端保持长连接 */
extern int var_cfg_keep_alive;
/*-------------- 整数配置项 -----------------*/
extern ACL_CFG_INT_TABLE var_conf_int_tab[];
/* 每次与客户端通信时,读超时时间(秒) */
extern int var_cfg_io_timeout;
#endif
6) Makefile 文件中记录着怎样与 ACL 库 lib_acl.a 及 ACL 的头文件进行编译连接,以及在不同UNIX平台下需要哪些系统库。
7) demo.cf 为配置文件
service server {
# 进程是否禁止运行
master_disable = yes
# 服务地址及端口号
master_service = 127.0.0.1:5001
# 服务监听为域套接口
# master_service = aio_echo.sock
# 服务类型
master_type = inet
# master_type = unix
# 当子进程异常退出时,如果该值非空,则将子进程异常退出的消息通知该服务
# master_notify_addr = 127.0.0.1:5801
# 是否允许延迟接受客户端连接,如果为0则表示关闭该功能,如果大于0则表示打开此功能
# 并且此值代表延迟接受连接的超时值,超过此值时如果客户端依然没有发来数据,则操作
# 系统会在系统层直接关闭该连接
# master_defer_accept = 0
# 是否只允许私有访问, 如果为 y, 则域套接口创建在 {install_path}/var/log/private/ 目录下,
# 如果为 n, 则域套接口创建在 {install_path}/var/log/public/ 目录下,
master_private = n
master_unpriv = n
# 是否需要 chroot: n -- no, y -- yes
master_chroot = n
# 每隔多长时间触发一次,单位为秒(仅对 trigger 模式有效)
master_wakeup = -
# 最大进程数
master_maxproc = 1
# 进程程序名
master_command = ioctl_echo
# 进程日志记录文件
master_log = {install_path}/var/log/ioctl_echo.log
# 进程启动参数,只能为: -u [是否允许以某普通用户的身份运行]
# master_args =
# 传递给服务子进程的环境变量, 可以通过 getenv("SERVICE_ENV") 获得此值
# master_env = logme:FALSE, priority:E_LOG_INFO, action:E_LOG_PER_DAY, flush:sync_flush, imit_size:512,\
# sync_action:E_LOG_SEM, sem_name:/tmp/ioctl_echo.sem
# 每个进程实例处理连接数的最大次数,超过此值后进程实例主动退出
ioctl_use_limit = 100
# 每个进程实例的空闲超时时间,超过此值后进程实例主动退出
ioctl_idle_limit = 120
# 记录进程PID的位置(对于多进程实例来说没有意义)
ioctl_pid_dir = {install_path}/var/pid
# 进程运行时所在的路径
ioctl_queue_dir = {install_path}/var
# 读写超时时间, 单位为秒
ioctl_rw_timeout = 120
# 读缓冲区的缓冲区大小
ioctl_buf_size = 8192
# 每次 accept 时的循环接收的最大次数
ioctl_max_accept = 25
# 在并发访问量非常低的情况下,如访问量在 10 次/秒 以下时,可以找开此值(即赋为1),以加速事件循环过程,
# 从而防止服务进程阻塞在 select 上的时间过长而影响访问速度
# ioctl_enable_dog = 0
# 进程运行时的用户身份
ioctl_owner = root
# 用 select 进行循环时的时间间隔
# 单位为秒
ioctl_delay_sec = 0
# 单位为微秒
ioctl_delay_usec = 500
# 采用事件循环的方式: select(default), poll, kernel(epoll/devpoll/kqueue)
ioctl_event_mode = select
# 线程池的最大线程数
ioctl_max_threads = 250
# 线程的堆栈空间大小,单位为字节,0表示使用系统缺省值
ioctl_stacksize = 0
# 允许访问 udserver 的客户端IP地址范围
ioctl_access_allow = 127.0.0.1:255.255.255.255, 127.0.0.1:127.0.0.1
############################################################################
# 应用自己的配置选项
debug_msg = test msg
debug_enable = 1
keep_alive = 1
}
小结,可以看出开发一个多线程的进程池服务程序是如此之简单,我们无需写一些复杂的服务器控制代码,这个过程完全由ACL服务器框架内部自动处理。这个例子在 acl库的 samples/master/ioctl_echo3 中可以看到,要想使其运行在生产环境下,需要将编译后的可执行程序及配置文件按 "协作半驻留式服务器程序开发框架 --- 基于 Postfix 服务器框架改造 "文章所介绍的 acl_master 框架的运行位置及配置位置中即可,然后运行 acl_master 中的 reload.sh 即可以将这个新的服务加载了。
参考:
协作半驻留式服务器程序开发框架 --- 基于 Postfix 服务器框架改造
利用ACL库快速创建你的网络程序
个人微博:http://weibo.com/zsxxsz
QQ 群:242722074