java中NIO的使用和例子

NIO的來歷:https://juejin.im/entry/592e29a4ac502e006c9b4dc7

NIO是Java提供的非阻塞I/O API拾碌。

非阻塞的意義在于可以使用一個(gè)線程對(duì)大量的數(shù)據(jù)連接進(jìn)行處理,非常適用于"短數(shù)據(jù)長連接"的應(yīng)用場景,例如即時(shí)通訊軟件饵史。

在一個(gè)阻塞C/S系統(tǒng)中,服務(wù)器要為每一個(gè)客戶連接開啟一個(gè)線程阻塞等待客戶端發(fā)送的消息.若使用非阻塞技術(shù),服務(wù)器可以使用一個(gè)線程對(duì)連接進(jìn)行輪詢,無須阻塞等待.這大大減少了內(nèi)存資源的浪費(fèi),也避免了服務(wù)器在客戶線程中不斷切換帶來的CPU消耗,服務(wù)器對(duì)CPU的有效使用率大大提高.

NIO的核心概念包括Channel,Selector,SelectionKey,Buffer髓抑。

Channel是I/O通道,可以向其注冊(cè)Selector,應(yīng)用成功可以通過select操作獲取當(dāng)前通道已經(jīng)準(zhǔn)備好的可以無阻塞執(zhí)行的操作.這由SelectionKey表示。

SelectionKey的常量字段SelectionKey.OP_***分別對(duì)應(yīng)Channel的幾種操作例如connect(),accept(),read(),write()仗处。

select操作后得到SelectionKey.OP_WRITE或者READ即可在Channel上面無阻塞調(diào)用read和write方法,Channel的讀寫操作均需要通過Buffer進(jìn)行.即讀是講數(shù)據(jù)從通道中讀入Buffer然后做進(jìn)一步處理.寫需要先將數(shù)據(jù)寫入Buffer然后通道接收Buffer眯勾。

下面是一個(gè)使用NIO的基本C/S示例.該示例只為顯示如何使用基本的API而存在,其代碼的健壯性,合理性都不具參考價(jià)值。

這個(gè)示例,實(shí)現(xiàn)一個(gè)簡單的C/S,客戶端向服務(wù)器端發(fā)送消息,服務(wù)器將收到的消息打印到控制臺(tái)婆誓,并將該消息返回給客戶端吃环,客戶端再打印到控制臺(tái)。現(xiàn)實(shí)的應(yīng)用中需要定義發(fā)送數(shù)據(jù)使用的協(xié)議,以幫助服務(wù)器解析消息.本示例只是無差別的使用默認(rèn)編碼將收到的字節(jié)轉(zhuǎn)換字符并打印洋幻。ByteBuffer的容量越小,對(duì)一條消息的處理次數(shù)就越多,容量大就可以在更少的循環(huán)次數(shù)內(nèi)讀完整個(gè)消息.所以真是的應(yīng)用場景,要考慮適當(dāng)?shù)木彺娲笮∫蕴岣咝省?/p>

服務(wù)器端代碼:

package nio;

import java.io.IOException; 
import java.net.InetSocketAddress; 
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey; 
import java.nio.channels.Selector; 
import java.nio.channels.ServerSocketChannel; 
import java.nio.channels.SocketChannel; 
import java.util.*; 
import java.util.concurrent.ConcurrentHashMap; 
 
public class Server { 
    private Selector selector; 
    private ByteBuffer readBuffer = ByteBuffer.allocate(1024);//調(diào)整緩存的大小可以看到打印輸出的變化 
    private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);//調(diào)整緩存的大小可以看到打印輸出的變化 
 
    String str;
    public void start() throws IOException {
        // 打開服務(wù)器套接字通道 
        ServerSocketChannel ssc = ServerSocketChannel.open(); 
        // 服務(wù)器配置為非阻塞 
        ssc.configureBlocking(false); 
        // 進(jìn)行服務(wù)的綁定 
        ssc.bind(new InetSocketAddress("localhost", 8001)); 
        
        // 通過open()方法找到Selector
        selector = Selector.open(); 
        // 注冊(cè)到selector郁轻,等待連接
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        
        while (!Thread.currentThread().isInterrupted()) { 
            selector.select(); 
            Set<SelectionKey> keys = selector.selectedKeys(); 
            Iterator<SelectionKey> keyIterator = keys.iterator(); 
            while (keyIterator.hasNext()) { 
                SelectionKey key = keyIterator.next(); 
                if (!key.isValid()) { 
                    continue; 
                } 
                if (key.isAcceptable()) { 
                    accept(key); 
                } else if (key.isReadable()) { 
                    read(key); 
                } else if (key.isWritable()) {
                    write(key);
                }
                keyIterator.remove(); //該事件已經(jīng)處理,可以丟棄
            } 
        } 
    }

    private void write(SelectionKey key) throws IOException, ClosedChannelException {
        SocketChannel channel = (SocketChannel) key.channel();
        System.out.println("write:"+str);
        
        sendBuffer.clear();
        sendBuffer.put(str.getBytes());
        sendBuffer.flip();
        channel.write(sendBuffer);
        channel.register(selector, SelectionKey.OP_READ);
    } 
 
    private void read(SelectionKey key) throws IOException { 
        SocketChannel socketChannel = (SocketChannel) key.channel(); 
 
        // Clear out our read buffer so it's ready for new data 
        this.readBuffer.clear(); 
//        readBuffer.flip();
        // Attempt to read off the channel 
        int numRead; 
        try { 
            numRead = socketChannel.read(this.readBuffer); 
        } catch (IOException e) { 
            // The remote forcibly closed the connection, cancel 
            // the selection key and close the channel. 
            key.cancel(); 
            socketChannel.close(); 
            
            return; 
        } 
        
        str = new String(readBuffer.array(), 0, numRead);
        System.out.println(str);
        socketChannel.register(selector, SelectionKey.OP_WRITE);
    } 
 
    private void accept(SelectionKey key) throws IOException { 
        ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); 
        SocketChannel clientChannel = ssc.accept(); 
        clientChannel.configureBlocking(false); 
        clientChannel.register(selector, SelectionKey.OP_READ); 
        System.out.println("a new client connected "+clientChannel.getRemoteAddress()); 
    } 
 
    public static void main(String[] args) throws IOException { 
        System.out.println("server started..."); 
        new Server().start(); 
    } 
} 

客戶端代碼:

package nio;

import java.io.IOException; 
import java.net.InetSocketAddress; 
import java.nio.ByteBuffer; 
import java.nio.channels.SelectionKey; 
import java.nio.channels.Selector; 
import java.nio.channels.SocketChannel; 
import java.util.Iterator; 
import java.util.Scanner; 
import java.util.Set; 
 
 
public class Client { 
 
    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    
    public void start() throws IOException { 
        // 打開socket通道  
        SocketChannel sc = SocketChannel.open(); 
        //設(shè)置為非阻塞
        sc.configureBlocking(false); 
        //連接服務(wù)器地址和端口
        sc.connect(new InetSocketAddress("localhost", 8001)); 
        //打開選擇器
        Selector selector = Selector.open(); 
        //注冊(cè)連接服務(wù)器socket的動(dòng)作
        sc.register(selector, SelectionKey.OP_CONNECT); 
        
        Scanner scanner = new Scanner(System.in); 
        while (true) { 
            //選擇一組鍵文留,其相應(yīng)的通道已為 I/O 操作準(zhǔn)備就緒范咨。  
            //此方法執(zhí)行處于阻塞模式的選擇操作故觅。
            selector.select();
            //返回此選擇器的已選擇鍵集。
            Set<SelectionKey> keys = selector.selectedKeys(); 
            System.out.println("keys=" + keys.size()); 
            Iterator<SelectionKey> keyIterator = keys.iterator(); 
            while (keyIterator.hasNext()) { 
                SelectionKey key = keyIterator.next(); 
                keyIterator.remove(); 
                // 判斷此通道上是否正在進(jìn)行連接操作渠啊。 
                if (key.isConnectable()) { 
                    sc.finishConnect(); 
                    sc.register(selector, SelectionKey.OP_WRITE); 
                    System.out.println("server connected..."); 
                    break; 
                } else if (key.isWritable()) { //寫數(shù)據(jù) 
                    System.out.print("please input message:"); 
                    String message = scanner.nextLine(); 
                    //ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes()); 
                    writeBuffer.clear();
                    writeBuffer.put(message.getBytes());
                    //將緩沖區(qū)各標(biāo)志復(fù)位,因?yàn)橄蚶锩鎝ut了數(shù)據(jù)標(biāo)志被改變要想從中讀取數(shù)據(jù)發(fā)向服務(wù)器,就要復(fù)位
                    writeBuffer.flip();
                    sc.write(writeBuffer); 
                    
                    //注冊(cè)寫操作,每個(gè)chanel只能注冊(cè)一個(gè)操作输吏,最后注冊(cè)的一個(gè)生效
                    //如果你對(duì)不止一種事件感興趣,那么可以用“位或”操作符將常量連接起來
                    //int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
                    //使用interest集合
                    sc.register(selector, SelectionKey.OP_READ);
                    sc.register(selector, SelectionKey.OP_WRITE);
                    sc.register(selector, SelectionKey.OP_READ);
                    
                } else if (key.isReadable()){//讀取數(shù)據(jù)
                    System.out.print("receive message:");
                    SocketChannel client = (SocketChannel) key.channel();
                    //將緩沖區(qū)清空以備下次讀取 
                    readBuffer.clear();
                    int num = client.read(readBuffer);
                    System.out.println(new String(readBuffer.array(),0, num));
                    //注冊(cè)讀操作替蛉,下一次讀取
                    sc.register(selector, SelectionKey.OP_WRITE);
                }
            } 
        } 
    } 
 
    public static void main(String[] args) throws IOException { 
        new Client().start(); 
    } 
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贯溅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子躲查,更是在濱河造成了極大的恐慌它浅,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镣煮,死亡現(xiàn)場離奇詭異姐霍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)典唇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門镊折,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人介衔,你說我怎么就攤上這事恨胚。” “怎么了炎咖?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵赃泡,是天一觀的道長。 經(jīng)常有香客問我乘盼,道長升熊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任绸栅,我火速辦了婚禮僚碎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘阴幌。我一直安慰自己勺阐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布矛双。 她就那樣靜靜地躺著渊抽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天惧盹,我揣著相機(jī)與錄音妄荔,去河邊找鬼。 笑死畅哑,一個(gè)胖子當(dāng)著我的面吹牛蒙保,可吹牛的內(nèi)容都是我干的龄糊。 我是一名探鬼主播玩焰,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼由驹,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了昔园?” 一聲冷哼從身側(cè)響起蔓榄,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎默刚,沒想到半個(gè)月后甥郑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荤西,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年澜搅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邪锌。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡勉躺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秃流,到底是詐尸還是另有隱情赂蕴,我是刑警寧澤柳弄,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布舶胀,位于F島的核電站,受9級(jí)特大地震影響碧注,放射性物質(zhì)發(fā)生泄漏嚣伐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一萍丐、第九天 我趴在偏房一處隱蔽的房頂上張望轩端。 院中可真熱鬧,春花似錦逝变、人聲如沸基茵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拱层。三九已至,卻和暖如春宴咧,著一層夾襖步出監(jiān)牢的瞬間根灯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烙肺,地道東北人纳猪。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像桃笙,于是被迫代替她去往敵國和親氏堤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

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