一、 前言
自從J2SE 1.4版本以來,JDK發布了全新的I/O類庫,簡稱NIO,其不但引入了全新的高效的I/O機制,同時,也引入了多路復用的異步模式。NIO的包中主要包含了這樣幾種抽象數據類型:
•Buffer:包含數據且用於讀寫的線形表結構。其中還提供了一個特殊類用於內存映射文件的I/O操作。
•Charset:它提供Unicode字符串影射到字節序列以及逆映射的操作。
•Channels:包含socket,file和pipe三種管道,都是全雙工的通道。
•Selector:多個異步I/O操作集中到一個或多個線程中(可以被看成是Unix中select()函數的面向對象版本)。
我的大學同學趙锟在使用NIO類庫書寫相關網絡程序的時候,發現了一些Java異常RuntimeException,異常的報錯信息讓他開始了對NIO的Selector進行了一些調查。當趙锟對我共享了Selector的一些底層機制的猜想和調查時候,我們覺得這是一件很有意思的事情,於是在伙同趙锟進行過一系列的調查後,我倆發現了很多有趣的事情,於是導致了這篇文章的產生。這也是為什麼本文的作者署名為我們兩人的原因。
先要說明的一點是,趙锟和我本質上都是出身於Unix/Linux/C/C++的開發人員,對於Java,這並不是我們的長處,這篇文章本質上出於對Java的Selector的好奇,因為從表面上來看Selector似乎做到了一些讓我們這些C/C++出身的人比較驚奇的事情。
下面讓我來為你講述一下這段故事。
二、 故事開始 : 讓C++程序員寫Java程序!
沒有嚴重內存問題,大量豐富的SDK類庫,超容易的跨平台,除了在性能上有些微辭,C++出身的程序員從來都不會覺得Java是一件很困難的事情。當然,對於長期習慣於使用操作系統API(系統調用System Call)的C/C++程序來說,面對Java中的比較“另類”地操作系統資源的方法可能會略感困惑,但萬變不離其宗,只需要對面向對象的設計模式有一定的了解,用不了多長時間,Java的SDK類庫也能玩得隨心所欲。
在使用Java進行相關網絡程序的的設計時,出身C/C++的人,首先想到的框架就是多路復用,想到多路復用,Unix/Linux下馬上就能讓從想到select, poll, epoll系統調用。於是,在看到Java的NIO中的Selector類時必然會倍感親切。稍加查閱一下SDK手冊以及相關例程,不一會兒,一個多路復用的框架便呈現出來,隨手做個單元測試,沒啥問題,一切和C/C++照舊。然後告訴兄弟們,框架搞定,以後咱們就在Windows上開發及單元測試,完成後到運行環境Unix上集成測試。心中並暗自念到,跨平台就好啊,開發活動都可以跨平台了。
然而,好景不長,隨著代碼越來越多,邏輯越來越復雜。好好的框架居然在Windows上單元測試運行開始出現異常,看著Java運行異常出錯的函數棧,異常居然由Selector.open()拋出,錯誤信息居然是Unable to establish loopback connection。
“Selector.open()居然報loopback connection錯誤,憑什麼?不應該啊?open的時候又沒有什麼loopback的socket連接,怎麼會報這個錯?”
長期使用C/C++的程序當然會對操作系統的調用非常熟悉,雖然Java的虛擬機搞的什麼系統調用都不見了,但C/C++的程序員必然要比Java程序敏感許多。