那些年我們追過的C++網絡庫(PartI)

jopen 9年前發布 | 20K 次閱讀 網絡 C/C++開發

為什么要用 C++ 編寫服務端程序?

如果說答案是性能,那么肯定有人會滿不在乎。覺得性能不夠的話, 只要加機器就可以了。然而更少的機器,意味著更低的能耗,更少的硬件投入,更少的人力資源投入去維護機器。總而言之,更低的成本。

肯定會有人說,C++的開發速度太慢了。然而這并不是絕對的。C++也可以做到非常快速的開發。有句俗語 * “腳本一時爽,重構火葬場” * 說的正是腳本語言開發的項目進入維護階段后無窮的災難。而 C++ 經過了幾十年的發展, 擁有龐大的工具鏈. 不管是動態分析還是靜態分析都有大量的工具, 能極大的幫助程序員減少錯誤.c++得益于精良的設計,嚴格的檢查,越是大型的工程,越是能降低開發成本。

但這并不意味著C++就不適合小型項目了。小型的項目,也可以快速開發。因為 C++11 開始,已經 感覺像是全新的語言了,可以完全以腳本的形式去使用,獲得接近甚至超越腳本語言的開發速度,同時得益于編譯優化,獲得不俗的運行時性能。C++正是魚和熊掌得兼的語言。

為什么要用asio這個庫?

事實上如果使用C++開發服務端程序,你有多得數不清的選擇。什么 ACE 啦,libuv 啦,libevent 啦,libev 啦,甚至可以直接使用 epoll/iocp 這樣的系統API。為什么要用 asio 呢?

那些年我們用過的網絡庫

在計算機史前文明時代, 曾經有個世界難題, 叫 "c10k problem". 這個是繼 y2k problem 后的又一個重大攻關項目. 全世界的文藝青年都想拿下解決這個問題的榮譽, 正可謂八仙過海, 各顯神通.

那一年, NPTL 還沒有研究出來. 還不能創建成千上萬個線程
那一年, windows 還在藍屏中掙扎, 無暇顧及網絡.

然而, 曙光還是有的. 異步的出現帶給了人以希望. 古老的UNIX早就想到了, 提供了 select() 系統調用供人驅使.然而問題還是有的, select 只能支持 1024 個文件描述符, windows 上的 select 更是劣質到只能使用64個. 就算通過修改定義強迫接受一萬個文件描述符, 也沒有解決實際的問題. select 實在是太慢了.

在這種背景下, IBM 老大哥帶領著MS老弟先搞了 IOCP . 然而開源的人有開源的做法, 在 NIH 綜合癥的影響下, BSD 的人敢為天下所不齒, 發明了 Kqueue. 同樣在 NIH 綜合癥影響下, Linux 的一群 M* 的猴子搗鼓出了 epoll.

分裂, 讓人頭疼.

然而, 他們都聲稱自己的新接口對 select 有質的提升, 是破解 c10k 問題的不二法寶. 你用也得用, 不用也得用.為了讓自己編寫的網絡程序能跨平臺, 程序員開始了對3大各自為陣的法寶的膜拜學習.
除了需要應對多套互不兼容的 API , 異步本身也需要更高級的抽象, 把程序員從編寫異步代碼的地獄模式里拯救出來. 于是程序員們急需一個上天入地無所不能的法寶的法寶, 把這3家法寶給統御起來.

率先站出來悳瑟的是 ACE.

悳瑟的 ACE

恰亂世剛過, 天下待定, C++ 委員會的老人們卻韜光養晦, 不問世事. 所謂亂世出英雄, 英雄出少年, 歐文大學出了名秀才. 憑借其洋洋灑灑的一片雄文 《Pattern-Oriented Software Architecture》 中舉去了首府學城, 并為ACE奠定了無可撼動的地位.

ACE 的名字, 也許靈感來自 Adaptive Clubbed Rod, 這也是當年一位英雄少年的寶貝. 既是寶貝, 必需如意. 即是后來的葫蘆娃都怕了 "如意寶貝".

ACE 如意在什么地方呢?如意其一, 支持 IOCP/kqueu/epoll/select/you_name_it 各種接口, 號曰沒有不能跨的平臺. 如意其二, 支持多種模型。這些模型都在《Pattern-Oriented Software Architecture》有過詳細敘述. ACE 本身就是這篇論文的實踐,因為他知道, 紙上得來終覺淺 絕知此事要躬行。 如意其三, 接口和模式排列組合下, 多少種, 竟可不修改代碼而適應。

然而 ACE 畢竟嫩了點, 沒過幾年就失勢了. 現在除了一些老程序員還在用, 新生代的程序員已經不再使用 ACE 了. 為什么呢? 陳碩在他的博客里說, ACE 過于復雜,甚至比它試圖封裝的對象更復雜, 程序員是指望用你的如意寶貝去駕馭另外那三家寶貝的, 結果你比他們還難。ACE 犯了早期 C++ 庫都會犯的一個錯誤,過度設計, 過度java化。所謂 java 化, 就是以對象代替接口, 以虛函數代替回調,以繼承代替組合。以虛類代替模板。對象間關系錯綜復雜,牽一發而動全身。除了作者,已經無人能參與 ACE 的開發了。

與此同時,C語言的回歸卻在背后悄然進行。C語言的復辟,帶來了幾個更為糟糕的替代品, libevent 和 libev,以及 乘著nodejs的盛行東風而來的 libuv。

原始的 libevent

C 語言有著頑強的生命力,當然,這并不是因為C語言有多好,在后續的章節了我們還會深入的探討C++相對C的改進。C語言的頑強和人天生的懶惰和偏見是有一定關系的。這種惰性表現為隨遇而安,表現為固執己見。 非要拼命的否定C++,固守 C , 對 C 的缺點視而不見,詆毀C++相對C的改進。固守的結果就是簡陋原始的 libevent . 然而因為保守黨巨大的人數優勢, libevent 應其群眾基礎良好而獲得了空前的廣泛使用。

libevent 就如名字所言,是一個異步事件框架。從 OS 那里獲得事件, 然后派發。派發機制就是“回調函數”。異步異步,歸根結底就是處理從操作系統獲得的事件。iocp也好, epoll也罷,都只是用來獲取事件的接口。libevent 去掉了ACE華而不實的包裝,保留了異步事件,極大的簡化了模型。不得不說軟件工程是個糟糕的發明,從來都把簡單問題復雜化。libevent把簡單問題簡單化,讓異步網絡編程反樸歸真,應該來說,本是一個好庫。

然而 libevent 因為設計缺陷,例如使用全局變量,定時器無法處理時間跳變,諸如此類的設計缺陷導致了 libev 的出現。libev 就是為了克服libevent的缺陷而誕生的。然而,libev 就一定好了嗎?

禁錮的 libev

libev 帶著對 libevent 的怨氣出世了。 吸收了 libevent 的所有缺點,雖然承諾過改進。然而 libev 如何改進的了呢? libev 已經夠原始了,向下改進還不如讓人直接使用系統的 api, 向上改進,一是會導致和libevent的重疊,二是很快就碰到了 C 語言強加的禁錮。

C 語言因其語法~~簡陋~~簡潔而著稱。然而,缺乏必要的抽象能力,導致 C 語言編寫異步程序,就如同安迪拿著小錘子琢開肖生克監獄的墻壁一樣。能,但是要耗費巨大的精力和時間。編寫異步程序, 最需要的2個抽象能力, 其一為協程,其二是函數對象,包括匿名函數對象, 也就是lambda。C統統沒有。函數對象是實現閉包比不可少的,如果沒有函數對象, 就只能通過攜帶 void* 指針的形式迂回完整,繁瑣不說,還特別容易出錯。程序里也到處充滿了類型強轉。到處是臨時定義的類型,就為了傳給 void* 使用。

盡管C 有那么多缺點,然而 libev 還未來得及被C的缺點拖累,因為他不支持 IOCP. 于是 libuv 就出來給 libev 擦屁股了。 支持了 iocp 后的 libuv 就真的只有 C 本身的缺點了嗎?

混亂的 libuv

libuv 可以說是 C 語言的異步庫所能達到的最高高度了。完完全全的觸碰到了C語言的自身瓶頸,好在 libuv 只是 nodejs 的底層庫,上層軟件轉移到 javascript 語言而逃避了 C 的禁錮。

真的是這樣的嗎? libuv 自身還有什么缺點呢?

開源社區avplayer的大拿jackarain曾經說過,一個網絡庫好不好,就看他有沒有正確的處理 TCP 關閉, read write 實現的ui不對。libuv 很遺憾的是,不合格。libuv 的 uvwrite 沒有返回值,允許空回調。也就是忽略write錯誤。網絡出錯的情況下, libuv 的用戶只能稀里糊涂的知道出錯了, 至于錯在哪?數據到底有沒有發出去了? 一概不知道。把數據交給 uvwrite 后,就是一筆糊涂賬了,大概 TCP 不可靠的說法就是從這里傳出來的吧。

ASIO 騰空出世

請期待下篇!

</div> </div> 來自:http://weibo.com/p/1001603887061068320629

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!