在編寫服務器應用程序時,有時會有這樣一種應用場景:後端的業務數據及業務邏輯相同,但但願給前端應用提供的功能範圍及協議方式有些差異。如:前端
場景一:但願來自於外網的客戶端以只讀權限訪問後端數據,同時但願來自於內網的客戶端能夠以讀/寫方式訪問後端數據;後端
場景二:但願某個網段的客戶端以 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