在「協做半駐留式服務器程序開發框架 --- 基於 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框架