使用 acl::master_threads 類編寫多進程多線程服務器程序

      文章《開發多線程進程池服務器程序》講述瞭如何使用 acl 庫中的服務器模板編寫多進程多線程服務器程序,那個例子是用 C 語言實現的,acl_cpp 對 acl 庫用 c++ 語言進行了封裝,其中也包含服務器編程模塊,本文主要講述如何使用 acl_cpp 中的 master_threads 類編寫能夠由 acl_master 服務器父進程控制的服務器應用程序。關於基於acl_master 的服務器程序設計原理,請參考 《協做半駐留式服務器程序開發框架》。html

      1、類接口說明c++

      master_threads 是一個純虛類,其中定義的接口須要子類實現,以下:編程

/**
		 * 純虛函數:當某個客戶端鏈接有數據可讀或關閉或出錯時調用此函數
		 * @param stream {socket_stream*}
		 * @return {bool} 返回 false 則表示當函數返回後須要關閉鏈接,
		 *  不然表示須要保持長鏈接,若是該流出錯,則應用應該返回 false
		 */
		virtual bool thread_on_read(socket_stream*) = 0;

		/**
		 * 當線程池中的某個線程得到一個鏈接時的回調函數,
		 * 子類能夠作一些初始化工做
		 * @param stream {socket_stream*}
		 * @return {bool} 若是返回 false 則表示子類要求關閉鏈接,而不
		 *  必將該鏈接再傳遞至 thread_main 過程
		 */
		virtual bool thread_on_accept(socket_stream*) { return true; }

		/**
		 * 當與某個線程綁定的鏈接關閉時的回調函數
		 * @param stream {socket_stream*}
		 */
		virtual void thread_on_close(socket_stream* ) {}

		/**
		 * 當線程池中一個新線程被建立時的回調函數
		 */
		virtual void thread_on_init() {}

		/**
		 * 當線程池中一個線程退出時的回調函數
		 */
		virtual void thread_on_exit() {}

      master_threads 類提供了兩個函數:服務器

/**
		 * 開始運行,調用該函數是指該服務進程是在 acl_master 服務框架
		 * 控制之下運行,通常用於生產機狀態
		 * @param argc {int} 從 main 中傳遞的第一個參數,表示參數個數
		 * @param argv {char**} 從 main 中傳遞的第二個參數
		 */
		void run_daemon(int argc, char** argv);

		/**
		 * 在單獨運行時的處理函數,用戶能夠調用此函數進行一些必要的調試工做
		 * @param addr {const char*} 服務監聽地址
		 * @param conf {const char*} 配置文件全路徑
		 * @param count {unsigned int} 循環服務的次數,達到此值後函數自動返回;
		 *  若該值爲 0 則表示程序一直循環處理外來請求而不返回
		 * @param threads_count {int} 當該值大於 1 時表示自動採用線程池方式,
		 *  該值只有當 count != 1 時纔有效,即若 count == 1 則僅運行一次就返回
		 *  且不會啓動線程處理客戶端請求
		 * @return {bool} 監聽是否成功
		 */
		bool run_alone(const char* addr, const char* conf = NULL,
			unsigned int count = 1, int threads_count = 1);

       從上面兩個函數,能夠看出 master_threads 類當在生產環境下(由 acl_master 進程統一控制調度),用戶須要調用 run_daemon 函數;若是用戶在開發過程當中須要手工進行調試,則能夠調用 run_alone 函數。多線程

 

      master_threads 的基類 master_base 的幾個虛接口以下:併發

/**
		 * 當進程切換用戶身份前調用的回調函數,能夠在此函數中作一些
		 * 用戶身份爲 root 的權限操做
		 */
		virtual void proc_pre_jail() {}

		/**
		 * 當進程切換用戶身份後調用的回調函數,此函數被調用時,進程
		 * 的權限爲普通受限級別
		 */
		virtual void proc_on_init() {}

		/**
		 * 當進程退出前調用的回調函數
		 */
		virtual void proc_on_exit() {}

		// 在 run_alone 狀態下運行前,調用此函數初始化一些配置

       基類的這幾個虛函數用戶能夠根據須要調用。框架

 

       另外,基類 master_base 還提供了幾個用來讀配置選項的函數:socket

/**
		 * 設置 bool 類型的配置項
		 * @param table {master_bool_tbl*}
		 */
		void set_cfg_bool(master_bool_tbl* table);

		/**
		 * 設置 int 類型的配置項
		 * @param table {master_int_tbl*}
		 */
		void set_cfg_int(master_int_tbl* table);

		/**
		 * 設置 int64 類型的配置項
		 * @param table {master_int64_tbl*}
		 */
		void set_cfg_int64(master_int64_tbl* table);

		/**
		 * 設置 字符串 類型的配置項
		 * @param table {master_str_tbl*}
		 */
		void set_cfg_str(master_str_tbl* table);

 

      2、示例源程序函數

// master_threads.cpp : 定義控制檯應用程序的入口點。
//

#include "log.hpp"
#include "util.hpp"
#include "master_threads.hpp"
#include "socket_stream.hpp"

// 字符串類型的配置項
static char *var_cfg_debug_msg;

static acl::master_str_tbl var_conf_str_tab[] = {
	{ "debug_msg", "test_msg", &var_cfg_debug_msg },

	{ 0, 0, 0 }
};

// 布爾類型的配置項
static int  var_cfg_debug_enable;
static int  var_cfg_keep_alive;
static int  var_cfg_loop;

static acl::master_bool_tbl var_conf_bool_tab[] = {
	{ "debug_enable", 1, &var_cfg_debug_enable },
	{ "keep_alive", 1, &var_cfg_keep_alive },
	{ "loop_read", 1, &var_cfg_loop },

	{ 0, 0, 0 }
};

// 整數類型的配置項
static int  var_cfg_io_timeout;

static acl::master_int_tbl var_conf_int_tab[] = {
	{ "io_timeout", 120, &var_cfg_io_timeout, 0, 0 },

	{ 0, 0 , 0 , 0, 0 }
};

static void (*format)(const char*, ...) = acl::log::msg1;

//////////////////////////////////////////////////////////////////////////

class master_threads_test : public acl::master_threads
{
public:
	master_threads_test()
	{

	}

	~master_threads_test()
	{

	}

protected:
	// 基類純虛函數:當客戶端鏈接有數據可讀或關閉時回調此函數,返回 true 表示
	// 繼續與客戶端保持長鏈接,不然表示須要關閉客戶端鏈接
	virtual bool thread_on_read(acl::socket_stream* stream)
	{
		while (true)
		{
			if (on_read(stream) == false)
				return false;
			if (var_cfg_loop == 0)
				break;
		}
		return true;
	}

	bool on_read(acl::socket_stream* stream)
	{
		format("%s(%d)", __FILE__, __LINE__);
		acl::string buf;
		if (stream->gets(buf) == false) // 讀一行數據
		{
			format("gets error: %s", acl::last_serror());
			format("%s(%d)", __FILE__, __LINE__);
			return false;
		}
		if (buf == "quit")  // 若是客戶端要求關閉鏈接,則返回 false 通知服務器框架關閉鏈接
		{
			stream->puts("bye!");
			return false;
		}

		if (buf.empty())
		{
			if (stream->write("\r\n") == -1)
			{
				format("write 1 error: %s", acl::last_serror());
				return false;
			}
		}
		else if (stream->write(buf) == -1)
		{
			format("write 2 error: %s, buf(%s), len: %d",
				acl::last_serror(), buf.c_str(), (int) buf.length());
			return false;
		}
		else if (stream->write("\r\n") == -1)
		{
			format("write 3 client error: %s", acl::last_serror());
			return false;
		}
		return true;
	}

	// 基類虛函數:當接收到一個客戶端請求時,調用此函數,容許
	// 子類事先對客戶端鏈接進行處理,返回 true 表示繼續,不然
	// 要求關閉該客戶端鏈接
	virtual bool thread_on_accept(acl::socket_stream* stream)
	{
		format("accept one client, peer: %s, local: %s, var_cfg_io_timeout: %d\r\n",
			stream->get_peer(), stream->get_local(), var_cfg_io_timeout);
		if (stream->format("hello, you're welcome!\r\n") == -1)
			return false;
		return true;
	}

	// 基類虛函數:當客戶端鏈接關閉時調用此函數
	virtual void thread_on_close(acl::socket_stream*)
	{
		format("client closed now\r\n");
	}

	// 基類虛函數:當線程池建立一個新線程時調用此函數
	virtual void thread_on_init()
	{
#ifdef WIN32
		format("thread init: tid: %lu\r\n", GetCurrentThreadId());
#else
		format("thread init: tid: %lu\r\n", pthread_self());
#endif
	}

	// 基類虛函數:當線程池中的一個線程退出時調用此函數
	virtual void thread_on_exit()
	{
#ifdef WIN32
		format("thread exit: tid: %lu\r\n", GetCurrentThreadId());
#else
		format("thread exit: tid: %lu\r\n", pthread_self());
#endif
	}

	// 基類虛函數:服務進程切換用戶身份前調用此函數
	virtual void proc_pre_jail()
	{
		format("proc_pre_jail\r\n");
	}

	// 基類虛函數:服務進程切換用戶身份後調用此函數
	virtual void proc_on_init()
	{
		format("proc init\r\n");
	}

	// 基類虛函數:服務進程退出前調用此函數
	virtual void proc_on_exit()
	{
		format("proc exit\r\n");
	}
private:
};

int main(int argc, char* argv[])
{
	master_threads_test mt;

	// 設置配置參數表
	mt.set_cfg_int(var_conf_int_tab);
	mt.set_cfg_int64(NULL);
	mt.set_cfg_str(var_conf_str_tab);
	mt.set_cfg_bool(var_conf_bool_tab);

	// 開始運行

	if (argc >= 2 && strcmp(argv[1], "alone") == 0)
	{
		format = (void (*)(const char*, ...)) printf;
		format("listen: 127.0.0.1:8888\r\n");
		mt.run_alone("127.0.0.1:8888", NULL, 2, 10);  // 單獨運行方式
	}
	else
		mt.run_daemon(argc, argv);  // acl_master 控制模式運行
	return 0;
}

 

      這是一個簡單的提供 echo 行服務的服務器程序,能夠支持多個併發鏈接,並且能夠經過配置文件控制所啓動的最大進程數、每一個進程的最大線程數、進程空閒時間、線程空閒時間等控制參數,由於 acl 中的服務器框架都是半駐留的,因此既能夠保證運行效率,又可以在空閒釋放系統資源。該例子所在目錄:acl_cpp/samples/master_threads。oop

      須要指出的一點是,master_threads 內部是單例的,即要求該類的對象只能有一個,不然內部自動產生斷言。只因此沒有采用單例模板來設計單例,主要是爲了避免對外暴露 acl 庫中的接口,使使用 acl_cpp 庫的用戶沒必要關心 acl 庫的頭文件在哪兒。

 

      3、配置文件及程序安裝

      打開 acl_cpp/samples/master_threads/ioctl_echo.cf 配置文件,就其中幾個配置參數說明一下:

 

## 由 acl_master 用來控制服務進程池的配置項
# 爲 no 表示啓用該進程服務,爲 yes 表示禁止該服務進程
master_disable = no

# 表示本服務器進程監聽 127.0.0.1 的 5001 端口
master_service = 127.0.0.1:5001

# 表示是 TCP 套接口服務類型
master_type = inet

# 表示該服務進程池的最大進程數爲 2
master_maxproc = 2

# 進程日誌記錄文件,其中 {install_path} 須要用實際的安裝路徑代替
master_log = {install_path}/var/log/ioctl_echo.log
## 與該服務器框架模板相關的配置參數項
# 每一個服務進程中最大的線程數爲 250
ioctl_max_threads = 250

# 線程的堆棧空間大小,單位爲字節,0表示使用系統缺省值
ioctl_stacksize = 0

# 每一個進程實例處理鏈接數的最大次數,超過此值後進程實例主動退出
ioctl_use_limit = 100

# 每一個進程實例的空閒超時時間,超過此值後進程實例主動退出
ioctl_idle_limit = 120

# 進程運行時的用戶身份
ioctl_owner = root

# 採用事件循環的方式: select(default)/poll/kernel(epoll/devpoll/kqueue)
ioctl_event_mode = select

# 容許訪問 udserver 的客戶端IP地址範圍
ioctl_access_allow = 10.0.0.1:10.0.0.255, 127.0.0.1:127.0.0.1

 

      例如當 acl_master 服務器框架程序的安裝目錄爲:/opt/acl,則:

      /opt/acl/libexec: 該目錄存儲服務器程序(acl_master 程序也存放在該目錄下);

      /opt/acl/conf:該目錄存放 acl_master 程序配置文件 main.cf;

      /opt/acl/conf/service:該目錄存放服務子進程的程序配置文件,該路徑由 main.cf 文件指定;

      /opt/acl/var/log:該目錄存放日誌文件;

      /opt/acl/var/pid:該目錄存放進程號文件。

      該程序編譯經過後,須要把可執行程序放在 /opt/acl/libexec 目錄下,把配置文件放在 /opt/acl/conf/service 目錄下。

      在 /opt/acl/sh 下有啓動/中止 acl_master 服務進程的控制腳本;運行腳本:./start.sh,而後請用下面方法檢查服務是否已經啓動:

      ps -ef|grep acl_master # 查看服務器控制進程是否已經啓動

      netstat -nap|grep LISTEN|grep 5001 # 查看服務端口號是否已經被監聽

      固然,您也能夠查看 /opt/acl/var/log/acl_master 日誌文件,查看服務進程的啓動過程及監聽服務是否正常監聽。

 

      能夠命令行以下測試:telnet 127.0.0.1 5001

      我的微博:http://weibo.com/zsxxsz

       原文地址

       acl_cpp 下載

       acl_cpp 的編譯與使用

       更多文章

       QQ 羣:242722074

相關文章
相關標籤/搜索