使用 acl 服務器框架編寫監聽多個地址的服務器程序

      在編寫服務器應用程序時,有時會有這樣一種應用場景:後端的業務數據及業務邏輯相同,但但願給前端應用提供的功能範圍及協議方式有些差異。如:前端

      場景一:但願來自於外網的客戶端以只讀權限訪問後端數據,同時但願來自於內網的客戶端能夠以讀/寫方式訪問後端數據;後端

      場景二:但願某個網段的客戶端以 HTTP 協議訪問後端業務,同時但願某個網段的客戶端以私有協議方式訪問後端業務。服務器

      爲了處理上面的應用場景,固然能夠寫多個服務器程序,每一個服務器程序處理不一樣的協議格式和權限範圍,但這勢必會形成不少冗餘代碼,增長額外的工做量及出錯可能性。acl 的服務器框架模型容許一個服務器進程同時監聽多個地址,利用這一點即可以輕鬆解決上面的應用場景問題,同時大大減小了程序工做量及維護成本。多線程

      下面以一個簡單的例子,說明如何使用這一特性來處理不一樣的協議過程。框架

      爲了簡單起見,本例子使用了 使用 acl 生成嚮導快速建立服務器程序 文章中介紹的服務器生成嚮導過程來生成一個簡單的 DEMO(假設讓該服務器程序監聽:127.0.0.1:8088 和 192.168.166.162:8080 兩個地址)。假設由服務器生成嚮導程序生成了服務器模板類型爲 master_threads (線程池模型)的程序 echo_server。而後在 echo_server 程序目錄下打開  master_service.cpp 源程序,修改 函數 master_service::thread_on_accept ,master_service::thread_on_close 及 master_service::thread_on_read,內容以下:socket

// 當客戶端鏈接流有數據可讀/出現異常時的回調函數
bool master_service::thread_on_read(acl::socket_stream* conn)
{
	// 得到客戶端鏈接本地的哪一個監聽服務地址,其中 get_local 的參數爲 true 表示要求得到
	// ip:port 格式的全地址
	const char* local_addr = conn->get_local(true);
	const char* str = (const char*) conn->get_ctx();
	logger("connection from local %s on read fd %d, info: %s",
		local_addr, conn->sock_handle(), str);

	acl::string buf;
	// 從客戶端讀取一行數據
	if (conn->gets(buf, false) == false)
	{
		logger("get error from client %s, local addr: %s",
				conn->get_peer(true), local_addr);
		return false;  // 返回 false 通知服務器框架將鏈接關閉
	}

	// 回寫數據
	if (conn->write(buf) == -1)
	{
		logger("write to client %s error, local addr: %s",
				conn->get_peer(true), local_addr);
		return false;
	}

	// 返回 true 通知服務器框架繼續監控該客戶端鏈接流
	return true;
}

// 當接收到一個客戶端鏈接時的回調函數
bool master_service::thread_on_accept(acl::socket_stream* conn)
{
	// 得到客戶端鏈接本地的哪一個監聽服務地址,其中 get_local 的參數爲 true 表示要求得到
	// ip:port 格式的全地址
	const char* local_addr = conn->get_local(true);
	logger("connect from local addr: %s", local_addr);

	// 在此處能夠根據 local_addr 的不一樣來區分不一樣的鏈接請求:
	if (strcmp(local_addr, "127.0.0.1:8088") == 0)
	{
		const char* str = strdup("from 127.0.0.1:8088");
		conn->set_ctx(str);
	}
	else if (strcmp(local_addr, "192.168.166.162:8080" == 0)
	{
		const char* str = strdup("from 192.168.166.162:8080");
		conn->set_ctx(str);
	}
	else
	{
		const char* str = strdup("other addr");
		conn->set_ctx(str);
	}

	// 設置客戶端鏈接流的讀寫超時時間(秒)
	conn->set_rw_timeout(10);
	return true;
}

// 當客戶端鏈接關閉前調用的回調函數
void master_service::thread_on_close(acl::socket_stream* conn)
{
	// 釋放由 master_service::thread_on_accept 中分配的內存對象
	char* str = (char*) conn->get_ctx();
	if (str)
		free(str);
}

 

      上面代碼邏輯很簡單地演示了 acl 服務器框架支持監聽多個地址的用處。爲了支持不一樣的業務功能分流,應用能夠在 thread_on_accept 階段經過 socket_stream::set_ctx(void*) 設置不一樣的功能對象,在 thread_on_read 階段經過 socket_stream::get_ctx() 取出設置的對象,經過對對象的功能判斷進行業務功能分流。svn

       固然,還有一點不要忘記,還得須要修改該服務器的配置文件,將 master_service 的監聽地址改爲多個地址,如:127.0.0.1:8088, 192.168.166.162:8080  即:master_service = 127.0.0.1:8088, 192.168.166.162:8080,同時須要將 master_type 值改成 sock,即:master_type = sock。函數

       此外,爲了在獨立方式下測試服務器程序,能夠打開 main.cpp 文件,將其中的 addr 的值設爲 "127.0.0.1:8088, 192.168.166.162:8080" 便可。測試

 

       下面寫一個更加實用一點的例子,能夠先設計一個虛類,裏面定義一個虛方法,在接收到客戶端鏈接 (thread_on_accept) 時,根據鏈接地址不一樣來建立該虛類的子類實例(這些子類只需實現基類中的虛方法便可),在 thread_on_read 時,經過調用子類實例的虛方法來達到協議分流的目的。以下面的例子:spa

 

class base
{
public:
	base() {}
	virtual ~base() {}
	// 純虛方法,須要子類實現
	virtual bool run(acl::socket_stream* conn) = 0;
};

class child1 : public base
{
public:
	child1() {}
	~child1() {}

protected:
	bool run(acl::socket_stream* conn)  // 基類虛方法實現
	{
		acl::string buf;

		// 讀一行數據,但第二個參數爲 true 表示但願將 \r\n 自動去掉
		if (conn->gets(buf, true) == false)
			return false;
		if (conn.format("child1: %s\r\n", buf.c_str()) == -1)
			return false;
		return true;
	}	
};

class child2 : public base
{
public:
	child2() {}
	~child2() {}

protected:
	bool run(acl::socket_stream* conn)  // 基類虛方法實現
	{
		acl::string buf;

		// 讀一行數據,但第二個參數爲 true 表示但願將 \r\n 自動去掉
		if (conn->gets(buf, true) == false)
			return false;
		if (conn.format("child2: %s\r\n", buf.c_str()) == -1)
			return false;
		return true;
	}	
};

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

bool master_service::thread_on_read(acl::socket_stream* conn)
{
	// 將流中參數硬轉化爲 base 類對象
	base* obj = (base*) conn->get_ctx();

	// 調用基類中的純虛方法,而其實是調用了子類的方法
	// 從而實現了協議分流
	return obj->run(conn);
}

bool master_service::thread_on_accept(acl::socket_stream* conn)
{
	const char* local_addr = conn->get_local(true);
	if (strcmp(local_addr, "127.0.0.1:8088") = 0)
	{
		base* obj = new child1();
		conn->set_ctx(obj);
		return true;
	}
	else if (strcmp(local_addr, "127.0.0.1:8080") == 0)
	{
		base* obj = new child2();
		conn->set_ctx(obj);
		return true;
	}
	else
		return false;
}

void master_service::thread_on_close(acl::socket_stream* conn)
{
	base* obj = (base*) conn->get_ctx();
	if (obj)
		delete obj;
}

 

 

參考:

acl 庫下載:https://sourceforge.net/projects/acl/

svn: svn://svn.code.sf.net/p/acl/code/

QQ 羣:242722074

使用 acl 生成嚮導快速建立服務器程序

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

acl 服務器模塊的部署

相關文章
相關標籤/搜索