Netplus試圖解決什么問題
本文為初見,Netplus快速開始之PingPong Example系列第二篇
1. 背景
網(wǎng)絡(luò)信息傳遞中的兩個基本問題,即建立連接,傳遞消息,都已被各操作系統(tǒng)解決好了,各家的技術(shù)細(xì)節(jié)并不一樣,暴露出來的接口雖有相似之處,但仍然難以做到一份代碼,到處運(yùn)行。一些代碼庫,如libuv, libevent,在系統(tǒng)兼容以及IO事件通知的上面做得非常優(yōu)秀,然,從實(shí)際應(yīng)用開發(fā)者的角度來看,它們?nèi)匀贿z留了很多需要開發(fā)者自己去解決的問題,如跨平臺的一些細(xì)節(jié)、IO事件處理、線程安全及性能調(diào)優(yōu)、應(yīng)用層協(xié)議的解析。而這些問題都涉及更多的知識以及編程經(jīng)驗(yàn),這就是門檻啊,任何試圖降低門檻的努力都是有價值的,也肯定是值得的。
為降低門檻,Netplus在很多方面都做嘗試。
2. Netplus為降低門檻進(jìn)行的嘗試
2.1 簡化對象管理
對象管理是個古老的話題,各語言在這方面都做得相當(dāng)出色,尤其是Java,需要的時候,我們new一個,不用的時候,直接置null, 這種簡單直接的使用方式,著實(shí)迷人,c++是否也能如此呢?
c++在這方面也是努力,stl中有如下方案:
std::auto_ptr
std::unique_ptr
std::shared_ptr
std::weak_ptr
std::enable_shared_from_this
已經(jīng)有這么多解決方案了,還沒有解決好問題嗎?為啥,Netplus又造輪子 ?
簡單列一下理由如下:
并沒有解決好線程安全的問題。
shared_ptr使用不當(dāng)會造成二次delete,最典型的當(dāng)屬如這樣的代碼,int* p=new int; std::shared_ptr<int>(p); std::shared_ptr<int>(p)。雖然這是一個錯誤的使用案例,但是我相信寫過這樣代碼的人應(yīng)該不少。
有多種方式獲取到raw_pointer,有了raw_pointer。特別是周期長,不段有新人加入的項(xiàng)目,鬼曉得會有人拿這個raw_pointer來干什么用(最好你不明白我在說什么 )。
sizeof(std::shared_ptr<T>) == sizeof(T*)*2,理想的期望當(dāng)然是如sizeof(netp::ref_ptr<T>) == sizeof(T*)
std::shared_ptr<T>對象在創(chuàng)建的時候,還會額外new一個對象來存control_block, 這對于有大量小對象的系統(tǒng)來說,性能會受到不少的影響。
工具太多,眼花繚亂,新人會有一些心理負(fù)擔(dān)。
我說了列一下的,沒想到一直子列了這么多,就點(diǎn)到為止吧。
注:我列的這些問題,并不是想說std::shared_ptr有多糟糕,除此之外,std::shared_ptr也是有很多好處的,比如,它是非侵入式的,除了托管對象,它還能托管數(shù)組,因可自定義deleter,使得它還可以托管一些其它資源,如file descriptor,等system handle。造成今天這樣的局面,有一些是歷史遺留問題,還有一些就是選擇。
Netplus其中一個重要的目標(biāo)是降低門檻,因此,易用,夠用,簡潔成為其第一要考慮的問題,綜合考慮之后,Netplus選擇了侵入式的引用計數(shù)方式,它具備如下性質(zhì):
阻止顯式new/delete (編譯器會報錯)
sizeof(netp::ref_ptr<T>) == sizeof(T*)
小對象友好
線程安全
通過這些努力,我們達(dá)成了下面這樣的小目標(biāo)
想要的時候make_ref<T>(...)
不要的時候,直接置null
詳細(xì)的關(guān)于Netplus智能指針,請參:
Concept: Smart Pointergithub.com
2.2 形式上消滅回調(diào)函數(shù)
先看下面代碼:
struct foo{};
struct bar{};
struct callback_ctx_t {
foo* foo_ptr;
bar* bar_ptr;
};
typedef void (*callback_t)(int v, callback_ctx_t* ctx);
void do_async( callback_t cb, callback_ctx_t* ctx ){
xx_cb = cb;
xx_ctx = ctx;
// do ..
// if the expected event happens in the future, we must call xx_cb(event_result, ctx )
}
//this func would be called in future, if event happens
void do_if_event_ready(){
int v=8;//set event result value as 8 for simplicity
xx_cb(v,ctx);
}
void callback_impl(int v, callback_ctx_t* ctx) {
if(v == 8) {
//do...
}
}
int main(int argc, char** argv) {
callback_ctx_t* ctx = new callback_ctx_t;
ctx->foo_ptr = new foo();
ctx->bar_ptr = new bar();
do_async(callback_impl, ctx);
for(;;){}
}
這是一個典型的回調(diào)案例。
do_async調(diào)用后,它的結(jié)果要等到某event發(fā)生之后才會有。于是我們傳入一個函數(shù),這個函數(shù)主要是用來接收將來才會知道的那個值,同時,為了能在回調(diào)函數(shù)里面執(zhí)行相關(guān)的操作,我們可能還要傳入一個ctx,在ctx里面保存需要的上下文。
回調(diào)雖然能解決問題,但是,它不便于代碼閱讀,不便于直觀的邏輯表達(dá),更不便于資源管理,工程中的很多問題都與回調(diào)有關(guān)。
我們能否有更好的表達(dá)方式呢?
我們能不能在do_async返回一個對象,這個對象可以直接取將來的值,值的類型是int?這樣不就沒有回調(diào)了嗎?
于是,經(jīng)過精心設(shè)計,代碼變成下面這樣:
netp::ref_ptr<netp::promise<int>> do_async(){
xx_promise = netp::make_ref<netp::promise<int>>():
// do ..
return xx_promise;
}
//this func would be called in future, if event happens
void do_if_event_ready(){
int v=8;//set event result value as 8 for simplicity
xx_promsie->set(v);
}
struct foo{};
struct bar{};
int main(int argc, char** argv) {
foo* foo_ptr = new foo();
bar* bar_ptr = new bar();
netp::ref_ptr<netp::promise<int>> int_p = do_async();
int_p ->if_done([foo_ptr,bar_ptr](int v){
//this lambda would be called once the event ready
if(v == 8) {
//do ...
}
});
for(;;){}
}
有沒有發(fā)現(xiàn)點(diǎn)什么呢?
我們在形式上消滅了回調(diào),整個代碼的表達(dá),有沒有感覺更直觀一點(diǎn)?請注意if_done這一行,有發(fā)現(xiàn)什么了嗎,仔細(xì)體會,帶著如下問題仔細(xì)體會?
線程調(diào)度的本質(zhì)
協(xié)程的本質(zhì)
任務(wù)調(diào)度的本質(zhì)
任務(wù)與線程,與CPU資源是何關(guān)系,如何最大化cpu利用率?
Promise的一小步,表達(dá)上的一大步。
結(jié)合c++11的lambda,終于,異步的代碼看起來,也如同步代碼那般直觀,ctx的傳遞再也不需要構(gòu)造專門的對象,我們可以直接通過lambda捕獲參數(shù)傳入。
Netplus里,幾乎所有的IO調(diào)用,都是返回Promise對象,函數(shù)簽名如下:
netp::ref_ptr<netp::promise<int>> ch_write(netp::ref_ptr<netp::packet> const& outlet);
netp::ref_ptr<netp::promise<int>> ch_write_to(netp::ref_ptr<netp::packet> const& outlet, address const& to);
netp::ref_ptr<netp::promise<int>> ch_close();
netp::ref_ptr<netp::channel_dial_promise<int>> dial(std::string const& host,...);
更多關(guān)于Promise,請參:
Concept: Promisegithub.com
3. 隱藏平臺差異,將IO編程的共性進(jìn)行抽象封裝
將平臺相關(guān)、IO事件處理、線程相關(guān)、性能相關(guān),這些各APP里面都需要考慮的共性,進(jìn)行抽象、封裝。隱藏平臺的細(xì)節(jié)和差異。
目前Netplus支持如下平臺:
Windows on x86
Linux on x86/arm
IOS/MAC on x86/arm
Android on x86/arm
開發(fā)者再也不用關(guān)心平臺以及平臺相關(guān)的差異性。
4. 借鑒Netty,Pipeline化消息處理
Netplus在設(shè)計的時候,借鑒了很多來自netty的概念,如,channel, channel handler, pipeline, executor, scheduler, 那些熟悉 netty的朋友,就算不熟悉c++,應(yīng)該也能快速上手。
通過Pipeline管理好Handler以及它的次序,便能如流水線一般對協(xié)議進(jìn)行逐層處理,最終將業(yè)務(wù)需要的消息形態(tài)遞送到業(yè)務(wù)邏輯的代碼處。
通過此設(shè)計,使得開發(fā)者只需專注于自己的業(yè)務(wù)本身,或通過添加新的Handler,去適配自有協(xié)議,便能進(jìn)行網(wǎng)絡(luò)通迅。
為了便于快速開發(fā),Netplus對http/https/websocket等協(xié)議提供了直接的支持,當(dāng)然,也歡迎各位朋友為其添加其它的協(xié)議,讓Netplus日漸豐滿,我們的目標(biāo)始終是,開箱即用,統(tǒng)統(tǒng)一把梭

欲窮千里目,更上一層樓,下文,我們將聊聊Netplus里面的一些基本概念。
