傳統(tǒng)IO和NIO的實(shí)例比較

我們先通過(guò)一段代碼來(lái)看看傳統(tǒng)IO的特點(diǎn):我們構(gòu)建一個(gè)服務(wù)端魁巩,然后使用telnet進(jìn)行客戶(hù)端的連接測(cè)試伯诬。
這里要注意如果你的window沒(méi)有安裝telnet客戶(hù)端的話(huà)咳短,使用telnet命令是會(huì)報(bào)"telnet不是內(nèi)部或外部命令"的。如下圖:

image.png

解決方法:
? ?操作過(guò)程:點(diǎn)擊"開(kāi)始"→"控制器面板"→" 查看方式:類(lèi)型"則點(diǎn)擊"程序"("查看方式:大圖標(biāo)"則點(diǎn)擊"程序和功能")→ "啟動(dòng)或關(guān)閉windows功能"→ 在"Windows功能"界面勾選Telnet服務(wù)器和客戶(hù)端 →最后點(diǎn)擊"確定"等待安裝苛预。勾選Telnet客戶(hù)端如下圖:

勾選Telnet客戶(hù)端.png

操作成功之后會(huì)再次進(jìn)入命令行輸入telnet便會(huì)出現(xiàn)如下界面:

telnet使用.png

好了孵稽,準(zhǔn)備工作就做好了许起,現(xiàn)在開(kāi)始上我們的代碼了十偶,用Java傳統(tǒng)IO寫(xiě)個(gè)服務(wù)器端:

public class OioServer {

    public static void main(String[] args) {

        ServerSocket serverSocket = null;

        try {
            //創(chuàng)建一個(gè)Socket服務(wù),監(jiān)聽(tīng)10000端口
            serverSocket = new ServerSocket(10000);
            System.out.println("服務(wù)器啟動(dòng)...");
            while(true){
               final Socket socket = serverSocket.accept();
                System.out.println("來(lái)了一個(gè)新的客戶(hù)端連接");
               handler(socket);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服務(wù)handler
     */
    public static void handler(Socket socket){

        byte[] buffer = new byte[1024];
        InputStream inputStream = null;
        try {
            inputStream = socket.getInputStream();
            while(true){
                int read = inputStream.read(buffer);
                if(read!=-1){
                    System.out.println(new String(buffer,0,read));
                }
            }
        } catch (IOException e) {
            System.out.println("獲取Socket的輸入流失斣跋浮惦积!");
            e.printStackTrace();
        }
    }
}

啟動(dòng)這個(gè)main我們使用telnet方式模擬客戶(hù)端來(lái)看看傳統(tǒng)的IO有哪些特征:
運(yùn)行結(jié)果:


image.png

首先很典型的就是啟動(dòng)的時(shí)候使用Dubug方式,我們發(fā)現(xiàn)線(xiàn)程堵塞在final Socket socket = serverSocket.accept()這一行猛频,然后使用telnet連接之后狮崩,程序又堵塞在了handler方法的這一行int read = inputStream.read(buffer);,而且我們開(kāi)了一個(gè)telnet客戶(hù)端的情況下鹿寻,如果再次開(kāi)一個(gè)會(huì)發(fā)現(xiàn)睦柴,這個(gè)時(shí)候我們是無(wú)法在第一個(gè)telnet連接還沒(méi)有斷開(kāi)的情況下開(kāi)啟第二個(gè)連接的。
所以綜合上訴我們總結(jié)出了傳統(tǒng)IO一下3個(gè)特征:

1.堵塞
2.單線(xiàn)程情況下只響應(yīng)一個(gè)客戶(hù)端連接的事件

現(xiàn)在我們?cè)谏厦娴拇a上面做一些優(yōu)化毡熏,就是解決傳統(tǒng)IO只能連接一個(gè)客戶(hù)端的問(wèn)題坦敌,因?yàn)樯厦娴拇a連接不上實(shí)際上是因?yàn)槲覀儼逊?wù)器的端口連接監(jiān)聽(tīng)和監(jiān)聽(tīng)處理都放到了一個(gè)線(xiàn)程去做,
而在第一個(gè)客戶(hù)端沒(méi)有關(guān)閉的情況下線(xiàn)程實(shí)際上堵塞在了第一個(gè)客戶(hù)端的read這一行(我去監(jiān)聽(tīng)第一個(gè)客戶(hù)端去了)痢法,這個(gè)時(shí)候你開(kāi)啟第二個(gè)telnet來(lái)連接服務(wù)器狱窘,我們的請(qǐng)求當(dāng)然是不會(huì)被搭理的咯

所以我們將監(jiān)聽(tīng)和監(jiān)聽(tīng)處理分別交給兩個(gè)不同的線(xiàn)程來(lái)做财搁,這樣的話(huà)训柴,線(xiàn)程也不會(huì)忙不過(guò)來(lái),看下面的代碼:
我們使用線(xiàn)程池來(lái)解決單個(gè)客戶(hù)端的問(wèn)題:

public static void main(String[] args) {

        ExecutorService executorService = Executors.newCachedThreadPool();
        ServerSocket serverSocket = null;

        try {
            //創(chuàng)建一個(gè)Socket服務(wù)妇拯,監(jiān)聽(tīng)10000端口
            serverSocket = new ServerSocket(10000);
            System.out.println("服務(wù)器啟動(dòng)...");
            while(true){
               final Socket socket = serverSocket.accept();
                System.out.println("來(lái)了一個(gè)新的客戶(hù)端連接");
                executorService.submit(new Runnable() {
                    @Override
                    public void run() {
                        handler(socket);
                    }
                });

            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服務(wù)handler
     */
    public static void handler(Socket socket){

        byte[] buffer = new byte[1024];
        InputStream inputStream = null;
        try {
            inputStream = socket.getInputStream();
            while(true){
                int read = inputStream.read(buffer);
                if(read!=-1){
                    System.out.println(new String(buffer,0,read));
                }
            }
        } catch (IOException e) {
            System.out.println("獲取Socket的輸入流失敗洗鸵!");
            e.printStackTrace();
        }
    }

運(yùn)行結(jié)果:


image.png

思考:這就解決了我們之前的問(wèn)題但是如果我這個(gè)服務(wù)器每秒有百萬(wàn)級(jí)別的人去訪(fǎng)問(wèn)呢越锈?線(xiàn)程池就要開(kāi)百萬(wàn)個(gè)線(xiàn)程去監(jiān)聽(tīng),這顯然很不合理膘滨,至少傳統(tǒng)IO在雙十一這種高并發(fā)場(chǎng)景的瓶頸是很明顯的甘凭。

總結(jié): 傳統(tǒng)IO就好比下圖的這種模式:


image.png

一家餐廳,有一個(gè)大門(mén)(ServerSocket)然后每次來(lái)一個(gè)客人火邓,我們就要聘用一個(gè)服務(wù)員進(jìn)行服務(wù)丹弱,服務(wù)員沒(méi)有得到復(fù)用,性能消耗巨大铲咨,這顯然很不合理躲胳,如果來(lái)的客人多了,客人又沒(méi)點(diǎn)多少才纤勒,估計(jì)餐廳老板得破產(chǎn)坯苹。我們現(xiàn)實(shí)中餐廳的服務(wù)員都是一個(gè)人給好幾桌甚至好幾十桌的人服務(wù)的。所以后來(lái)在傳統(tǒng)IO的基礎(chǔ)上摇天,我們有了NIO(new IO),它和傳統(tǒng)IO最大的區(qū)別就是它更加優(yōu)雅粹湃,更加方便恐仑,更加符合生活。
NIO就是我們現(xiàn)實(shí)中飯店的模型为鳄。服務(wù)員可以為多個(gè)客戶(hù)服務(wù)裳仆。


image.png

我們通過(guò)代碼來(lái)實(shí)際驗(yàn)證這種特性:

/**
 * NIO服務(wù)端
 * 代碼來(lái)源網(wǎng)絡(luò),再此感謝各位前輩孤钦。
 * @author -GuiRong
 */
public class NIOServer {
    // 通道管理器
    private Selector selector;

    /**
     * 獲得一個(gè)ServerSocket通道歧斟,并對(duì)該通道做一些初始化的工作
     *
     * @param port 綁定的端口號(hào)
     * @throws IOException
     */
    public void initServer(int port) throws IOException {
        // 獲得一個(gè)ServerSocket通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 設(shè)置通道為非阻塞
        serverChannel.configureBlocking(false);
        // 將該通道對(duì)應(yīng)的ServerSocket綁定到port端口
        serverChannel.socket().bind(new InetSocketAddress(port));
        // 獲得一個(gè)通道管理器
        this.selector = Selector.open();
        // 將通道管理器和該通道綁定,并為該通道注冊(cè)SelectionKey.OP_ACCEPT事件,注冊(cè)該事件后司训,
        // 當(dāng)該事件到達(dá)時(shí)构捡,selector.select()會(huì)返回,如果該事件沒(méi)到達(dá)selector.select()會(huì)一直阻塞壳猜。
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    /**
     * 采用輪詢(xún)的方式監(jiān)聽(tīng)selector上是否有需要處理的事件勾徽,如果有,則進(jìn)行處理
     *
     * @throws IOException
     */
    public void listen() throws IOException {
        System.out.println("服務(wù)端啟動(dòng)成功统扳!");
        // 輪詢(xún)?cè)L問(wèn)selector
        while (true) {
            // 當(dāng)注冊(cè)的事件到達(dá)時(shí)喘帚,方法返回;否則,該方法會(huì)一直阻塞
            selector.select();
            // 獲得selector中選中的項(xiàng)的迭代器咒钟,選中的項(xiàng)為注冊(cè)的事件
            Iterator<?> ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 刪除已選的key,以防重復(fù)處理
                ite.remove();
                
                handler(key);
            }
        }
    }

    /**
     * 處理請(qǐng)求
     *
     * @param key
     * @throws IOException
     */
    public void handler(SelectionKey key) throws IOException {

        // 客戶(hù)端請(qǐng)求連接事件
        if (key.isAcceptable()) {
            handlerAccept(key);
            // 獲得了可讀的事件
        } else if (key.isReadable()) {
            handelerRead(key);
        }
    }

    /**
     * 處理連接請(qǐng)求
     *
     * @param key
     * @throws IOException
     */
    public void handlerAccept(SelectionKey key) throws IOException {
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        // 獲得和客戶(hù)端連接的通道
        SocketChannel channel = server.accept();
        // 設(shè)置成非阻塞
        channel.configureBlocking(false);

        // 在這里可以給客戶(hù)端發(fā)送信息哦
        System.out.println("新的客戶(hù)端連接");
        // 在和客戶(hù)端連接成功之后吹由,為了可以接收到客戶(hù)端的信息,需要給通道設(shè)置讀的權(quán)限朱嘴。
        channel.register(this.selector, SelectionKey.OP_READ);
    }

    /**
     * 處理讀的事件
     *
     * @param key
     * @throws IOException
     */
    public void handelerRead(SelectionKey key) throws IOException {
        // 服務(wù)器可讀取消息:得到事件發(fā)生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 創(chuàng)建讀取的緩沖區(qū)
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int read = channel.read(buffer);
        if (read > 0) {
            byte[] data = buffer.array();
            String msg = new String(data).trim();
            System.out.println("服務(wù)端收到信息:" + msg);

            //回寫(xiě)數(shù)據(jù)
            ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());
            channel.write(outBuffer);// 將消息回送給客戶(hù)端
        } else {
            System.out.println("客戶(hù)端關(guān)閉");
            key.cancel();
        }
    }

    /**
     * 啟動(dòng)服務(wù)端測(cè)試
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NIOServer server = new NIOServer();
        server.initServer(8000);
        server.listen();
    }

}

運(yùn)行結(jié)果:


image.png

NIO的一些疑問(wèn)

1倾鲫、客戶(hù)端關(guān)閉的時(shí)候會(huì)拋出異常,死循環(huán)解決方案

  int read = channel.read(buffer);
    if(read > 0){
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("服務(wù)端收到信息:" + msg);
        
        //回寫(xiě)數(shù)據(jù)
        ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());
        channel.write(outBuffer);// 將消息回送給客戶(hù)端
    }else{
        System.out.println("客戶(hù)端關(guān)閉");
        key.cancel();
    }

2萍嬉、selector.select();阻塞乌昔,那為什么說(shuō)nio是非阻塞的IO?

selector.select()
selector.select(1000);不阻塞
selector.wakeup();也可以喚醒selector
selector.selectNow();也可以立馬返還壤追,視頻里忘了講了磕道,哈,這里補(bǔ)上

3行冰、SelectionKey.OP_WRITE是代表什么意思

  OP_WRITE表示底層緩沖區(qū)是否有空間溺蕉,是則響應(yīng)返還true

附:項(xiàng)目下載地址
注:個(gè)人學(xué)習(xí)筆記,部分資源來(lái)源于網(wǎng)絡(luò)悼做。在此感謝各位前輩疯特!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市贿堰,隨后出現(xiàn)的幾起案子辙芍,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件故硅,死亡現(xiàn)場(chǎng)離奇詭異庶灿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)吃衅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)往踢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人徘层,你說(shuō)我怎么就攤上這事峻呕。” “怎么了趣效?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵瘦癌,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我跷敬,道長(zhǎng)讯私,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任西傀,我火速辦了婚禮斤寇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拥褂。我一直安慰自己娘锁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布饺鹃。 她就那樣靜靜地躺著莫秆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悔详。 梳的紋絲不亂的頭發(fā)上馏锡,一...
    開(kāi)封第一講書(shū)人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音伟端,去河邊找鬼。 笑死匪煌,一個(gè)胖子當(dāng)著我的面吹牛责蝠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播萎庭,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼霜医,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了驳规?” 一聲冷哼從身側(cè)響起肴敛,我...
    開(kāi)封第一講書(shū)人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后医男,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體砸狞,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年镀梭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刀森。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡报账,死狀恐怖研底,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情透罢,我是刑警寧澤榜晦,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站羽圃,受9級(jí)特大地震影響乾胶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜统屈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一胚吁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧愁憔,春花似錦腕扶、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至膜宋,卻和暖如春窿侈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背秋茫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工史简, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人肛著。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓圆兵,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親枢贿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子殉农,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

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

  • 張紅云 平頂山 堅(jiān)持分享第178天( 原創(chuàng)總第346天 2017年11月16日 星期四) 緊張復(fù)習(xí)了一個(gè)...
    紅云_楊柳清風(fēng)閱讀 203評(píng)論 0 0
  • 大家應(yīng)該都知道魯智深吧,四大名著之一的《水滸傳》中108將中我最喜歡的就是魯智深了局荚,他樂(lè)于助人超凳,有一次他...
    田茁希閱讀 350評(píng)論 0 1
  • 這是朗朗解讀的第七張攝影作品 拍攝器材: 尼康D750?騰龍 70-200 2.8 拍攝地點(diǎn): 西藏阿里 拍攝對(duì)象...
    張小葷葷閱讀 169評(píng)論 2 2
  • 想寫(xiě)字都是沒(méi)時(shí)間的時(shí)候,開(kāi)始寫(xiě)卻一片空白了金麸。 Blog荒廢多時(shí)擎析,續(xù)費(fèi)之后又打不開(kāi)了。 我就是想找個(gè)能踏實(shí)寫(xiě)字挥下,組織...
    水御龍神閱讀 434評(píng)論 0 1