在無數的日夜里,熬出了多少的黑眼圈,致勤勤懇懇工作的各位朋友與自己。每到了年末的時候總想寫的什么,主要是為了回顧以往一年里到底做了什么,這便是年終總結的主要意義。在此我將要總結的是和我在技術層面上成長的一個項目,那便是開源的plain framework(簡稱PF),我會在這里分享一些關於程序設計的一些心得。
起源
2014年的7月左右,本着對於自己技術的不斷提高,我正式將之前的plain server項目進行整理,准備寫一個可以方便使用的框架。具體原因主要是發覺自己在工作中非常需要,以及技術層面上需要突破,而且也想革新下在此領域里參差不齊的格局。
一個人的野心便成為了他的動力,到了如今我也還是這樣想。可是在這方面卻已經有了許多成熟的框架,諸如ACE、
ZeroMQ等,不過卻感覺使用起來並不怎么方便,要么就是功能太多,要么就是太單一。作為一個網絡應用,需要的幾個模塊我便做出了總結:文件、網絡、數據庫、緩存、腳本。這五大部分,是如今應用中非常流行的,相信只要提供好的支持后這樣的框架可以解決大部分應用的問題。
特別是做游戲的朋友,可以發現一些國內的好框架,比如雲風的skynet。我是在2012年的時候接觸到了,那個時候我剛剛進入這個行業,對於腳本lua也在他的文章里受益匪淺。在此我向這些開源和分享知識的前輩們,表示由衷的感謝。不過skynet是使用C和lua結合的框架,這大概是因為個人愛好的原因。不過每個語言都不能盡善盡美,C++的優點在於對象化,而C呢在於簡潔,其實C++本來是在C的基礎上發展的,是更高層次的語言,所以從本身來說有一定優勢,同時便失去簡潔。我對C/C++沒有任何偏好,不過我對第一個讓我入門的PHP感到非常大的興趣,腳本語言的魅力就是對象化而又不失其簡潔,同時我就討厭如JAVA一般的環境配置。
無論怎樣也好,從語言上來說,每種語言都是方便開發者使用,而PF的初衷也是這樣。
於是我從2014年開始了長達三年左右的開源過程……
結構
從代碼的結構上來說,五大模塊已經全部支持,不過我對結構要求的比較細。可能出於強迫症的原因,在期間我對該結構進行了無數次的更改。
basic -- 基礎支持
cache -- 緩存
db -- 數據庫
engine -- 引擎(主控制)
file -- 文件操作
net -- 網絡
script -- 腳本
sys -- 系統
util -- 工具
版本
在1.0-1.1的版本中,使用了C99的少部分特性,不過是以C++98的標准來編寫的,這樣適合大多數的開發者使用。
在2.0以后的版本,使用C++11。因為我注意到它可以簡化編碼,而且似乎也比較高效,當我完全替換了C++11的模式后,才發現C++17標准已經出來,實在是讓人意想不到,相比的C還停留在C11階段。從目前來看,C++11的項目不會過時,大家也沒有必要擔心。
接口
待續……
編譯
現階段PF只支持LINUX上的編譯和運行,會在后期支持WINDOWS的版本,因為之前許多朋友們出現了編譯上的一些錯誤,為了降低編譯的難度將會分開一個單獨的倉庫。
在2.0rc版本里,在框架目錄下使用make就可以編譯框架和生成一個簡單的pf_simple測試應用了,這比之前的簡單很多。
不過需要准備的環境是:升級到支持編譯C++11的gcc、了解ODBC的配置。
測試
/** * GLOBALS["default.engine.frame"] = number; //default 100. * GLOBALS["default.net.open"] = bool; //default false. * GLOBALS["default.net.service"] = bool; //default false. * GLOBALS["default.net.service_ip"] = string; //default "". * GLOBALS["default.net.service_port"] = number; //default 0. * GLOBALS["default.net.conn_max"] = number; //default NET_CONNECTION_MAX. * GLOBALS["default.script.open"] = bool; //default false. * GLOBALS["default.script.rootpath"] = string; //default SCRIPT_ROOT_PATH. * GLOBALS["default.script.workpath"] = string; //default SCRIPT_WORK_PATH. * GLOBALS["default.script.bootstrap"] = string; //default "bootstrap.lua". * GLOBALS["default.script.type"] = number; //default pf_script::kTypeLua. * GLOBALS["default.cache.open"] = bool; //default fasle. * GLOBALS["default.cache.service"] = bool; //default fasle. * GLOBALS["default.cache.conf"] = string; //default "". * GLOBALS["default.cache.key_map"] = number; //default ID_INVALID. * GLOBALS["default.cache.recycle_map"] = number; //default ID_INVALID. * GLOBALS["default.cache.query_map"] = number; //default ID_INVALID. * GLOBALS["default.db.open"] = bool; //default fasle. * GLOBALS["default.db.type"] = number; //default kDBConnectorTypeODBC. * GLOBALS["default.db.server"] = string; //default "". * GLOBALS["default.db.user"] = string; //default "". * GLOBALS["default.db.password"] = string; //default "". **/ #include "main.h" #include "net.h" #include "packet/sayhello.h" //The script reload function. void reload() { if (is_null(ENGINE_POINTER)) return; auto env = ENGINE_POINTER->get_script(); if (is_null(env)) return; env->reload("preload.lua"); } //The test engine main loop event 1. int32_t times = 0; void main_loop(pf_engine::Kernel &engine) { std::cout << "main_loop ..." << std::endl; ++times; if (times > 10) std::cout << "main_loop exited by 10 times" << std::endl; else engine.enqueue([&engine](){ main_loop(engine); }); } //The test engine main loop event 2. void main_loop1(pf_engine::Kernel &engine) { std::cout << "main_loop1 ..." << std::endl; ++times; if (times > 20) std::cout << "main_loop1 exited by 20 times" << std::endl; else engine.enqueue([&engine](){ main_loop1(engine); }); } //Net test. pf_net::connection::Basic *connector{nullptr}; void main_nconnect(pf_engine::Kernel &engine, pf_net::connection::manager::Connector &mconnector) { mconnector.tick(); if (is_null(connector)) { connector = mconnector.connect( "127.0.0.1", GLOBALS["default.net.service_port"].uint16()); } else { static uint32_t last_time = 0; auto tickcount = TIME_MANAGER_POINTER->get_tickcount(); if (tickcount - last_time > 5000) { SayHello packet; packet.set_str("hello ..."); connector->send(&packet); last_time = tickcount; } } engine.enqueue([&engine, &mconnector](){ main_nconnect(engine, mconnector); }); } //DB test. void db_test(pf_engine::Kernel &engine) { auto db = engine.get_db(); if (is_null(db)) return; if (db->isready()) { db_query_t db_query; pf_db::Query query(&db_query); if (!query.init(db)) return; query.set_tablename("t_test"); query.select("*"); query.from(); query.limit(1); if (query.execute()) { pf_basic::io_cwarn("------------------------db---------------------------"); db_fetch_array_t fectch_array; query.fetcharray(fectch_array); pf_basic::io_cdebug("db_test keys: "); for (pf_basic::type::variable_t &key : fectch_array.keys) std::cout << key.string() << std::endl; pf_basic::io_cdebug("db_test values: "); for (pf_basic::type::variable_t &val : fectch_array.values) std::cout << val.string() << std::endl; pf_basic::io_cwarn("------------------------db---------------------------"); } } else { engine.enqueue([&engine](){ db_test(engine); }); } } int32_t main(int32_t argc, char * argv[]) { /* Base config. */ GLOBALS["app.debug"] = true; GLOBALS["app.name"] = "simple"; //Net. GLOBALS["default.net.open"] = true; GLOBALS["default.net.service"] = true; GLOBALS["default.net.service_port"] = 12306; //DB. GLOBALS["default.db.open"] = true; GLOBALS["default.db.server"] = "pf_test"; GLOBALS["default.db.user"] = "root"; GLOBALS["default.db.password"] = "mysql"; //Script. GLOBALS["default.script.open"] = true; /* engine. */ pf_engine::Kernel engine; pf_engine::Application app(&engine); /* command handler. */ app.register_commandhandler("--reload", "lua script reload.", reload); /* engine event. */ engine.enqueue([](){ std::cout << "main loop function1" << std::endl; }); engine.enqueue([&engine](){ main_loop(engine); }); engine.enqueue([&engine](){ main_loop1(engine); }); engine.enqueue([&engine](){ db_test(engine); }); /* net init. */ pf_net::connection::manager::Connector mconnector; init_net_packets(); mconnector.init(1); engine.enqueue([&engine, &mconnector](){ main_nconnect(engine, mconnector); }); /* run */ app.run(argc, argv); return 0; }
在測試里面加入了基本的網絡、腳本、數據庫的測試,大家可以先嘗試摸索一下,從上述代碼中可以見到現在框架的比1.0版本簡潔許多。
在1.0中我就用到了全局變量,在設計的時候感覺十分冗雜,所以2.0后統一使用GLOBALS來代替,這樣使用起來也方便。
最后
匆匆的寫了這么一些,希望大家不要見怪,等有時間會把這篇文章修改好的。