Java NIO學(xué)習(xí)筆記 - BIO編程

Java I/O演進(jìn)

在JDK1.4推出Java NIO之前揣炕,基于Java的所有Socket通信都采用了同步阻塞模式(BIO),這種——請求——應(yīng)答的通信模型簡化了上層的應(yīng)用開發(fā),但是在性能和可靠性方面卻存在著巨大的瓶頸陕贮。因此颖杏,在很長一段時間里,大型的應(yīng)用服務(wù)器都采用C或者C++語言開發(fā)落剪,因?yàn)樗鼈兛梢灾苯邮褂貌僮飨到y(tǒng)提供的異步I/O(AIO)能力。當(dāng)并發(fā)訪問量增大尿庐,響應(yīng)時間延遲增大之后忠怖,采用Java BIO并發(fā)的服務(wù)端軟件只有通過硬件的不斷擴(kuò)容來滿足高并發(fā)和低延時,它極大地增加了企業(yè)的成本抄瑟,并且隨著集群規(guī)模的不斷膨脹凡泣,系統(tǒng)的可維護(hù)性也面巨大的挑戰(zhàn)枉疼,只能通過采購性能更高的硬件服務(wù)器來解決問題,這會導(dǎo)致惡性循環(huán)鞋拟。

正是由于Java傳統(tǒng)BIO的拙劣表現(xiàn)骂维,才使得Java支持非阻塞I/O的呼的呼聲日漸高漲,最終贺纲,JDK1.4版本提供了新的NIO類庫(new input/output)航闺,Java終于也可以支持非阻塞I/O了。

Java I/O 發(fā)展簡史

從JDK1.0到JDK1.3猴誊,Java的I/O類庫都非常原始潦刃,很多UNIX網(wǎng)絡(luò)編程中的概念或者接口在I/O類庫中都沒有體現(xiàn),例如:Pipe懈叹,Channel乖杠,Buffer和Selector等。2002年發(fā)布JDK1.4時澄成,NIO以JSR-51的身份正式隨JDK發(fā)布胧洒。NIO主要的類和接口如下:

  • 進(jìn)行異步I/O操作的緩沖區(qū)ByteBuffer等;
  • 進(jìn)行異步I/O操作的管道Pipe墨状;
  • 進(jìn)行各種I/O操作(異步或者同步)的Channel卫漫,包括ServerSocketChannel和SocketChannel;
  • 多種字符集的編碼能力和解碼能力肾砂;
  • 實(shí)現(xiàn)非阻塞I/O操作的多路復(fù)用器Selector列赎;
  • 基于流行的Perl實(shí)現(xiàn)的正則表達(dá)式類庫;
  • 文件通道FileChannel通今;

新的NIO類庫的提供粥谬,極大地促進(jìn)了基于Java的異步非阻塞編程的發(fā)展和應(yīng)用,但是辫塌,它依然有不完善的地方漏策,特別是對文件系統(tǒng)的處理能力仍顯不足,主要問題如下:

  • 沒有統(tǒng)一的文件屬性(例如讀寫權(quán)限)
  • API能力比較弱臼氨,例如目錄的級聯(lián)創(chuàng)建和遞歸遍歷掺喻,往往需要自己實(shí)現(xiàn)。
  • 底層存儲系統(tǒng)的一些高級API無法使用储矩。
  • 所有的文件操作都是同步阻塞調(diào)用感耙,不支持異步文件讀寫操作。

2011年7月28日持隧,JDK1.7正式發(fā)布即硼。它的一個比較大的亮點(diǎn)就是將原來的NIO類庫進(jìn)行了升級,被稱為NIO2.0屡拨。NIO2.0由JSR-203演進(jìn)而來只酥,它主要提供了如下三個方面的改進(jìn):

  • 提供能夠批量獲取文件屬性的API褥实,這些API具有平臺無關(guān)性,不與特性的文件系統(tǒng)相耦合裂允,另外它還提供了標(biāo)準(zhǔn)文件系統(tǒng)的SPI损离,供各個服務(wù)提供商擴(kuò)展實(shí)現(xiàn)
  • 提供AIO功能,支持基于文件的異步I/O操作和針對網(wǎng)絡(luò)套接字的異步操作
  • 完成JSR-51定義的通道功能绝编,包括對配置和多播數(shù)據(jù)報的支持等僻澎。

傳統(tǒng)BIO編程

網(wǎng)絡(luò)編程的基本模型是Client/Server模型,也就是兩個進(jìn)程之間進(jìn)行相互通信十饥,其中服務(wù)端提供位置信息(綁定的IP地址和監(jiān)聽端口)窟勃,客戶端通過連接操作向服務(wù)器監(jiān)聽的地址發(fā)起連接請求,通過三次握手建立連接绷跑,如果連接建立成功拳恋,雙方就可以通過網(wǎng)絡(luò)套接字(Socket)進(jìn)行通信凡资。

在基于傳統(tǒng)同步阻塞模型開發(fā)中砸捏,ServerSocket負(fù)責(zé)綁定IP地址,啟動監(jiān)聽端口隙赁; Socket負(fù)責(zé)發(fā)起連接操作垦藏。連接成功之后,雙方通過輸入和輸出流進(jìn)行同步阻塞式通信伞访。

BIO通信模型

BIO的服務(wù)端通信模型:

bio-model.png

采用BIO通信模型的服務(wù)端掂骏,通常由一個獨(dú)立的Acceptor線程負(fù)責(zé)監(jiān)聽客戶端的連接,它接收到客戶端連接請求之后為每個客戶端創(chuàng)建一個新的線程進(jìn)行鏈路處理厚掷,處理完成之后弟灼,通過輸出流返回應(yīng)答給客戶端,線程銷毀冒黑。這就是典型的 一請求一應(yīng)答通信模型田绑。

該模型最大的問題就是缺乏彈性伸縮能力,當(dāng)客戶端并發(fā)訪問量增加后抡爹,服務(wù)端的線程個數(shù)和客戶端并發(fā)訪問數(shù)呈1:1的正比關(guān)系掩驱,由于線程是Java虛擬機(jī)非常寶貴的系統(tǒng)資源,當(dāng)線程數(shù)膨脹之后冬竟,系統(tǒng)的性能將急劇下降欧穴,隨著并發(fā)訪問量的繼續(xù)增大,系統(tǒng)會發(fā)生線程堆棧溢出泵殴,創(chuàng)建新線程失敗等問題涮帘,并最終導(dǎo)致進(jìn)程宕機(jī)或者僵死,不能對外提供服務(wù)笑诅。

BIO 簡單示例

Server 端

同步阻塞IO的TimeServer:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class TimeServer {
    public static void main(String[] args) throws IOException{
        int port = 8080;
        if(args != null && args.length > 0){
            try{
                port = Integer.valueOf(args[0]);
            }catch (NumberFormatException e){
                //采用默認(rèn)值
            }
        }
        ServerSocket server = null;
        try {
            server = new ServerSocket(port);
            System.out.println("The time server is start in port : " + port);
            Socket socket = null;
            while(true) {
                socket = server.accept();    //接收客戶端連接請求调缨,沒有的時候就阻塞
                System.out.println("收到來自客戶端的請求");
                new Thread(new TimeServerHandler(socket)).start();
            }
        } finally{
            if(server != null) {
                System.out.println("The time server close");
                server.close();
            }
        }
    }
}

TimeServer根據(jù)傳入的參數(shù)設(shè)置監(jiān)聽端口映屋,如果沒有入?yún)ⅲ褂媚J(rèn)值8080同蜻,20行通過構(gòu)造函數(shù)創(chuàng)建ServerSocket棚点,如果端口合法且沒有被占用,服務(wù)端監(jiān)聽成功湾蔓。23-26行通過一個無限循環(huán)來監(jiān)聽客戶端的連接瘫析,如果沒有客戶端接入,則主線程阻塞在ServerSocket的accept操作上默责。啟動TimeServer贬循,通過JvisualVM打印線程堆棧,我們可以發(fā)現(xiàn)主程序確實(shí)阻塞在accept操作上

當(dāng)有新的客戶端接入的時候桃序,執(zhí)行代碼25行杖虾,以Socket為參數(shù)構(gòu)造TimeServerHandler對象,TimeServerHandler是一個Runnable媒熊,使用它為構(gòu)造函數(shù)的參數(shù)創(chuàng)建一個新的客戶端線程處理這條Socket鏈路奇适。下面我們繼續(xù)分析TimeServerHandler的代碼。

同步阻塞IO的TimeServerHandler:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class TimeServerHandler implements Runnable {
    private Socket socket;
    public TimeServerHandler(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(),true);
            String currentTime = null;
            String body = null;
            while(true) {
                body = in.readLine();        //從客戶端讀
                if(body == null) break;
                System.out.println("The time server receive order: " + body);
                currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ?
                        new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
                out.print(currentTime);    //向客戶端寫
            }
        } catch (Exception e) {
            e.printStackTrace();
            if(in != null){
                try {
                    in.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
            if(out != null) {
                out.close();
            }
            if(this.socket != null) {
                try {
                    this.socket.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                this.socket = null;
            }
        }
    }
}

25行通過BufferedReader讀取一行芦鳍,如果已經(jīng)讀到了輸入流的尾部嚷往,則返回值為null,退出循環(huán)柠衅。如果讀到了非空值皮仁,則對內(nèi)容進(jìn)行判斷,如果請求消息為查詢時間的指令”QUERY TIME ORDER”則獲取當(dāng)前最新的系統(tǒng)時間菲宴,通過PrintWriter的println函數(shù)發(fā)送給客戶端贷祈,最后退出循環(huán)。代碼35-52行釋放輸入流喝峦、輸出流势誊、和Socket套接字句柄資源,最后線程自動銷毀并被虛擬機(jī)回收愈犹。

Client端

客戶端通過Socket創(chuàng)建键科,發(fā)送查詢時間服務(wù)器的”QUERY TIME ORDER”指令,然后讀取服務(wù)端的響應(yīng)并將結(jié)果打印出來漩怎,隨后關(guān)閉連接勋颖,釋放資源,程序退出執(zhí)行勋锤。

同步阻塞IO的TimeClient:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class TimeClient {
    public static void main(String[] args) {
        int port = 8080;
        if(args != null && args.length > 0){
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                //采用默認(rèn)值
            }
        }
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            socket = new Socket("127.0.0.1",port);    //創(chuàng)建socket
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(),true);
            out.println("QUERY TIME ORDER"); //向服務(wù)端寫出QUERY TIME ORDER
            System.out.println("Send order 2 server succeed.");
            String resp = in.readLine(); //從服務(wù)端讀入
            System.out.println("Now is : " +  resp);
        } catch (Exception e) {
            e.printStackTrace();
            //不需要處理
        } finally {
            if(out != null ){
                out.close();
            }
            if(in != null){
                try {
                    in.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

第23行客戶端通過PrintWriter向服務(wù)端發(fā)送”QUERY TIME ORDER”指令饭玲,然后通過BufferedReader的readLine讀取響應(yīng)并打印。

BIO潛在的問題

BIO主要的問題在于每當(dāng)有一個新的客戶端請求接入時叁执,服務(wù)端必須創(chuàng)建一個新的線程處理新接入的客戶端鏈路茄厘,一個線程只能處理一個客戶端連接矮冬。在高性能服務(wù)器應(yīng)用領(lǐng)域,往往需要面向成千上萬個客戶端的并發(fā)連接次哈,這種模型顯然無法滿足高性能胎署,高并發(fā)接入的場景。

為了改進(jìn)一線程一連接模型窑滞,后來又演進(jìn)出了一種通過線程池或者消息隊(duì)列實(shí)現(xiàn)1個或者多個線程處理N個客戶端的模型琼牧,由于它的底層通信機(jī)制依然使用同步阻塞I/O,所以被稱為“偽異步”哀卫,下一節(jié)會講到巨坊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市此改,隨后出現(xiàn)的幾起案子趾撵,更是在濱河造成了極大的恐慌,老刑警劉巖共啃,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件占调,死亡現(xiàn)場離奇詭異,居然都是意外死亡勋磕,警方通過查閱死者的電腦和手機(jī)妈候,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門敢靡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挂滓,“玉大人,你說我怎么就攤上這事啸胧「险荆” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵纺念,是天一觀的道長贝椿。 經(jīng)常有香客問我,道長陷谱,這世上最難降的妖魔是什么烙博? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮烟逊,結(jié)果婚禮上渣窜,老公的妹妹穿的比我還像新娘。我一直安慰自己宪躯,他們只是感情好乔宿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著访雪,像睡著了一般详瑞。 火紅的嫁衣襯著肌膚如雪掂林。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天坝橡,我揣著相機(jī)與錄音泻帮,去河邊找鬼。 笑死计寇,一個胖子當(dāng)著我的面吹牛刑顺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播饲常,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蹲堂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贝淤?” 一聲冷哼從身側(cè)響起柒竞,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎播聪,沒想到半個月后朽基,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡离陶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年稼虎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片招刨。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡霎俩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沉眶,到底是詐尸還是另有隱情打却,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布谎倔,位于F島的核電站柳击,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏片习。R本人自食惡果不足惜捌肴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望藕咏。 院中可真熱鬧状知,春花似錦、人聲如沸侈离。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至铺坞,卻和暖如春起宽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背济榨。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工坯沪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人擒滑。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓腐晾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親丐一。 傳聞我的和親對象是個殘疾皇子藻糖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容

  • NIO(Non-blocking I/O,在Java領(lǐng)域库车,也稱為New I/O)巨柒,是一種同步非阻塞的I/O模型,也...
    閃電是只貓閱讀 3,116評論 0 7
  • 從三月份找實(shí)習(xí)到現(xiàn)在柠衍,面了一些公司洋满,掛了不少,但最終還是拿到小米珍坊、百度牺勾、阿里、京東阵漏、新浪驻民、CVTE、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,246評論 11 349
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法袱饭,類相關(guān)的語法川无,內(nèi)部類的語法,繼承相關(guān)的語法虑乖,異常的語法,線程的語...
    子非魚_t_閱讀 31,631評論 18 399
  • 大學(xué)畢業(yè)后我曾經(jīng)對自己說“我再也不參加任何考試晾虑,我只接受社會大學(xué)的考試” 可是但是幾天的學(xué)習(xí)下來我要改變自己的說法...
    哈哈同學(xué)閱讀 136評論 1 0
  • 李乾坤David閱讀 132評論 0 0