JAVA NIO編程入門(二)

一挽唉、回顧

上一篇文章 JAVA NIO編程入門(一)我們學習了NIO編程的基礎知識,并通過一個小demo實戰(zhàn)幫助了解NIO編程的channel藐翎,buffer等概念橘忱。本文會繼續(xù)學習JAVA NIO編程,并通過一個小示例來幫助理解相關知識赡鲜,通過本文你將可以學習到

  • buffer的聚集和分散(Scatter/Gather)
  • SocketChannel和ServerSocketChannel的使用
  • 選擇器的使用

二空厌、什么是聚集和分散(Scatter/Gather)

  • 分散(scatter)從Channel中讀取是指在讀操作時將讀取的數(shù)據(jù)寫入多個buffer中。因此银酬,Channel將從Channel中讀取的數(shù)據(jù)“分散(scatter)”到多個Buffer中嘲更。
  • 聚集(gather)寫入Channel是指在寫操作時將多個buffer的數(shù)據(jù)寫入同一個Channel,因此揩瞪,Channel 將多個Buffer中的數(shù)據(jù)“聚集(gather)”后發(fā)送到Channel赋朦。

分散(Scatter)示意圖

image

從通道填充buffer,必須填充完前一個buffer才會填充后面的buffer李破,這也意味著不能動態(tài)調整每個buffer的接受大小宠哄。

聚集(Gather)示意圖

image

聚集和分散是相反的形式,從buffer寫入數(shù)據(jù)到通道嗤攻,只會寫入buffer的positon位置到limit位置的內容毛嫉,也就是意味著可以動態(tài)的寫入內容到通道中。

三妇菱、選擇器

什么是選擇器

Selector(選擇器)是Java NIO中能夠檢測多個NIO通道承粤,并能夠知道通道是否為諸如讀寫事件做好準備的組件。這樣闯团,一個單獨的線程可以管理多個channel辛臊,從而管理多個網絡連接,提高效率房交。

為什么要用選擇器

使用了選擇器就可以用一個線程管理多個channel彻舰,如果多個channel由多個線程管理,線程之前的切換是消耗資源的,而單個線程就避免了線程之間切換的消耗刃唤。

選擇器常用方法

方法名 功能
register(Selector sel, int ops) 向選擇器注冊通道口猜,并且可以選擇注冊指定的事件,目前事件分為4種透揣;1.Connect济炎,2.Accept,3.Read辐真,4.Write须尚,一個通道可以注冊多個事件
select() 阻塞到至少有一個通道在你注冊的事件上就緒了
selectNow() 不會阻塞,不管什么通道就緒都立刻返回
select(long timeout) 和select()一樣侍咱,除了最長會阻塞timeout毫秒(參數(shù))
selectedKeys() 一旦調用了select()方法耐床,并且返回值表明有一個或更多個通道就緒了,然后可以通過調用selector的selectedKeys()方法楔脯,訪問“已選擇鍵集(selected key set)”中的就緒通道
wakeUp() 可以使調用select()阻塞的對象返回撩轰,不阻塞。
close() 用完Selector后調用其close()方法會關閉該Selector昧廷,且使注冊到該Selector上的所有SelectionKey實例無效堪嫂。通道本身并不會關閉

四、實戰(zhàn)

實戰(zhàn)需求說明

編碼客戶端和服務端木柬,服務端可以接受客戶端的請求皆串,并返回一個報文,客戶端接受報文并解析輸出眉枕。

服務端代碼

 try {
            //創(chuàng)建一個服socket并打開
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //監(jiān)聽綁定8090端口
            serverSocketChannel.socket().bind(new InetSocketAddress(8090));
            //設置為非阻塞模式
            serverSocketChannel.configureBlocking(false);
            while(true){
            //獲取請求連接
                SocketChannel socketChannel = serverSocketChannel.accept();
                if (socketChannel!=null){
                    ByteBuffer buf1 = ByteBuffer.allocate(1024);
                    socketChannel.read(buf1);
                    buf1.flip();
                    if(buf1.hasRemaining())
                        System.out.println(">>>服務端收到數(shù)據(jù):"+new String(buf1.array()));
                    buf1.clear();
                //構造返回的報文恶复,分為頭部和主體,實際情況可以構造復雜的報文協(xié)議速挑,這里只演示谤牡,不做特殊設計。
                    ByteBuffer header = ByteBuffer.allocate(6);
                    header.put("[head]".getBytes());
                    ByteBuffer body   = ByteBuffer.allocate(1024);
                    body.put("i am body!".getBytes());
                    header.flip();
                    body.flip();
                    ByteBuffer[] bufferArray = { header, body };
                    socketChannel.write(bufferArray);

                    socketChannel.close();
                }else{
                    Thread.sleep(1000);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

服務端selector(選擇器版本)

 try {
            //打開選擇器
            Selector selector = Selector.open();
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(8090));
            serverSocketChannel.configureBlocking(false);
            //向通道注冊選擇器姥宝,并且注冊接受事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                //獲取已經準備好的通道數(shù)量
                int readyChannels = selector.selectNow();
                //如果沒準備好翅萤,重試
                if (readyChannels == 0) continue;
                //獲取準備好的通道中的事件集合
                Set selectedKeys = selector.selectedKeys();
                Iterator keyIterator = selectedKeys.iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey key = (SelectionKey) keyIterator.next();
                    if (key.isAcceptable()) {
                        //在自己注冊的事件中寫業(yè)務邏輯,
                         //我這里注冊的是accept事件伶授,
                         //這部分邏輯和上面非選擇器服務端代碼一樣断序。
                        ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) key.channel();
                        SocketChannel socketChannel = serverSocketChannel1.accept();
                        ByteBuffer buf1 = ByteBuffer.allocate(1024);
                        socketChannel.read(buf1);
                        buf1.flip();
                        if (buf1.hasRemaining())
                            System.out.println(">>>服務端收到數(shù)據(jù):" + new String(buf1.array()));
                        buf1.clear();

                        ByteBuffer header = ByteBuffer.allocate(6);
                        header.put("[head]".getBytes());
                        ByteBuffer body = ByteBuffer.allocate(1024);
                        body.put("i am body!".getBytes());
                        header.flip();
                        body.flip();
                        ByteBuffer[] bufferArray = {header, body};
                        socketChannel.write(bufferArray);

                        socketChannel.close();
                    } else if (key.isConnectable()) {
                    } else if (key.isReadable()) {
                    } else if (key.isWritable()) {

                    }
                    //注意每次迭代末尾的keyIterator.remove()調用流纹。
                    //Selector不會自己從已選擇鍵集中移除SelectionKey實例糜烹。必須在處理完通道時自己移除。
                    //下次該通道變成就緒時漱凝,Selector會再次將其放入已選擇鍵集中
                    keyIterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

客戶端代碼

 try {
            //打開socket連接疮蹦,連接本地8090端口,也就是服務端
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("127.0.0.1", 8090));
            //請求服務端茸炒,發(fā)送請求
            ByteBuffer buf1 = ByteBuffer.allocate(1024);
            buf1.put("來著客戶端的請求".getBytes());
            buf1.flip();
            if (buf1.hasRemaining())
                socketChannel.write(buf1);
            buf1.clear();
            //接受服務端的返回愕乎,構造接受緩沖區(qū)阵苇,我們定義頭6個字節(jié)為頭部,后續(xù)其他字節(jié)為主體內容感论。
            ByteBuffer header = ByteBuffer.allocate(6);
            ByteBuffer body   = ByteBuffer.allocate(1024);
            ByteBuffer[] bufferArray = { header, body };

            socketChannel.read(bufferArray);
            header.flip();
            body.flip();
            if (header.hasRemaining())
                System.out.println(">>>客戶端接收頭部數(shù)據(jù):" + new String(header.array()));
            if (body.hasRemaining())
                System.out.println(">>>客戶端接收body數(shù)據(jù):" + new String(body.array()));
            header.clear();
            body.clear();


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

運行結果

服務端:

image

客戶端:

image

這里給出了服務端代碼的兩個版本绅项,一個是非選擇器的版本,一個是選擇器的版本比肄。查看最后運行結果快耿,發(fā)現(xiàn)客戶端根據(jù)雙方約定的協(xié)議格式,正確解析到了頭部和body的內容芳绩,其實這也是聚集和分散最主要的作用和應用場景掀亥,在網絡交互中,進行協(xié)議報文格式的定義和實現(xiàn)妥色。后續(xù)學完NIO編程入門后我們最后進行總結性的實戰(zhàn)搪花,編寫一個RPC的demo框架,實現(xiàn)分布式系統(tǒng)的遠程調用嘹害,有興趣的同學可以關注筆者和后續(xù)的文章撮竿。

參考

JAVA NIO

推薦閱讀

Java鎖之ReentrantLock(一)

Java鎖之ReentrantLock(二)

Java鎖之ReentrantReadWriteLock

JAVA NIO編程入門(一)

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市笔呀,隨后出現(xiàn)的幾起案子倚聚,更是在濱河造成了極大的恐慌,老刑警劉巖凿可,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惑折,死亡現(xiàn)場離奇詭異,居然都是意外死亡枯跑,警方通過查閱死者的電腦和手機惨驶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來敛助,“玉大人粗卜,你說我怎么就攤上這事∧苫鳎” “怎么了续扔?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長焕数。 經常有香客問我纱昧,道長,這世上最難降的妖魔是什么堡赔? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任识脆,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘灼捂。我一直安慰自己离例,他們只是感情好,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布悉稠。 她就那樣靜靜地躺著宫蛆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪的猛。 梳的紋絲不亂的頭發(fā)上洒扎,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機與錄音衰絮,去河邊找鬼袍冷。 笑死,一個胖子當著我的面吹牛猫牡,可吹牛的內容都是我干的胡诗。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼淌友,長吁一口氣:“原來是場噩夢啊……” “哼煌恢!你這毒婦竟也來了?” 一聲冷哼從身側響起震庭,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤瑰抵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后器联,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體二汛,經...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年拨拓,在試婚紗的時候發(fā)現(xiàn)自己被綠了肴颊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡渣磷,死狀恐怖婿着,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情醋界,我是刑警寧澤竟宋,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站形纺,受9級特大地震影響丘侠,放射性物質發(fā)生泄漏。R本人自食惡果不足惜挡篓,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一婉陷、第九天 我趴在偏房一處隱蔽的房頂上張望帚称。 院中可真熱鬧官研,春花似錦秽澳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至始花,卻和暖如春妄讯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酷宵。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工亥贸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人浇垦。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓炕置,卻偏偏與公主長得像,于是被迫代替她去往敵國和親男韧。 傳聞我的和親對象是個殘疾皇子朴摊,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內容

  • 看到這副牌朦前,瞬間想到的一個形象是像一個戰(zhàn)士一樣向前沖介杆,而且對自己要沖向那里,要做到什么樣的事情心里很清楚也很明白韭寸,...
    未央行者閱讀 681評論 1 0
  • 我的前世这溅,一定是一個罪人 惡貫滿盈,無所不為 在別人的嘴里 我一定被謾罵了千百萬遍 所以今生我要遭受的 是地獄的烈...
    素挲閱讀 668評論 1 9
  • 馬上就是我們結婚一周年的紀念日了棒仍,時間過得真快悲靴,從戀愛到結婚,我們一起走過了五年莫其,時間很長癞尚,但我們依舊守在彼此身邊...
    菇涼姓徐閱讀 787評論 0 1