在“协做半驻留式服务器程序开发框架 --- 基于 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 便可以将这个新的服务加载了。
参考:app
协做半驻留式服务器程序开发框架 --- 基于 Postfix 服务器框架改造
利用ACL库快速建立你的网络程序
我的微博:http://weibo.com/zsxxsz
QQ 群:242722074框架