傳統(tǒng)IO與NIO的區(qū)別

本篇內(nèi)容主要講解“傳統(tǒng)IO與NIO的區(qū)別”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“傳統(tǒng)IO與NIO的區(qū)別”吧!

成都創(chuàng)新互聯(lián)成立與2013年,先為江達(dá)等服務(wù)建站,江達(dá)等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢(xún)服務(wù)。為江達(dá)企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。

我們先來(lái)看一段傳統(tǒng)IO的代碼

public class OioServer {public static void main(String[] args) throws IOException {//這里可以直接寫(xiě)成ServerSocket server = new ServerSocket(10101);        ServerSocket server = new ServerSocket();        server.bind(new InetSocketAddress(10101));        System.out.println("服務(wù)器啟動(dòng)");        while (true) {//此處會(huì)阻塞            Socket socket = server.accept();            System.out.println("來(lái)了一個(gè)新客戶(hù)端");            handler(socket);        }
    }public static void handler(Socket socket) {try {byte[] bytes = new byte[1024];            InputStream inputStream = socket.getInputStream();            while (true) {int read = inputStream.read(bytes);                if (read != -1) {
                    System.out.println(new String(bytes,0,read));                }else {break;                }
            }
        } catch (IOException e) {
            e.printStackTrace();        }finally {try {
                System.out.println("socket關(guān)閉");                socket.close();            } catch (IOException e) {
                e.printStackTrace();            }
        }
    }
}

使用telnet連接

admindeMacBook-Pro:~ admin$ telnet 127.0.0.1 10101
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

我們會(huì)看到OioServer的運(yùn)行情況

服務(wù)器啟動(dòng)
來(lái)了一個(gè)新客戶(hù)端

但是當(dāng)我們又使用一個(gè)telnet連接進(jìn)來(lái)的時(shí)候,OioServer的運(yùn)行情況沒(méi)變,說(shuō)明一個(gè)服務(wù)端只能接收一個(gè)客戶(hù)端點(diǎn)連接,原因在于Socket socket = server.accept();發(fā)生了堵塞,現(xiàn)在我們將其改寫(xiě)成多線程

public class OioServerThread {public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(10101);        ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);        System.out.println("服務(wù)器啟動(dòng)");        while (true) {
            Socket socket = server.accept();            System.out.println("來(lái)了一個(gè)新客戶(hù)端");            service.execute(() -> handler(socket));        }
    }public static void handler(Socket socket) {try {byte[] bytes = new byte[1024];            InputStream inputStream = socket.getInputStream();            while (true) {int read = inputStream.read(bytes);                if (read != -1) {
                    System.out.println(new String(bytes,0,read));                }else {break;                }
            }
        } catch (IOException e) {
            e.printStackTrace();        }finally {try {
                System.out.println("socket關(guān)閉");                socket.close();            } catch (IOException e) {
                e.printStackTrace();            }
        }
    }
}

運(yùn)行可知,當(dāng)我們啟動(dòng)了多個(gè)telnet進(jìn)行連接的時(shí)候,它是可以一起連接進(jìn)來(lái)的

服務(wù)器啟動(dòng)
來(lái)了一個(gè)新客戶(hù)端
來(lái)了一個(gè)新客戶(hù)端

但是這里有一個(gè)問(wèn)題,我們線程池的可用線程是有限的,不可能無(wú)限提供線程來(lái)接收大量客戶(hù)端的連接,遲早它會(huì)無(wú)響應(yīng)被堵塞的。

我們現(xiàn)在來(lái)看一下NIO,NIO其實(shí)是使用傳統(tǒng)IO的特性創(chuàng)建一個(gè)channel(通道),通過(guò)該通道來(lái)注冊(cè)事件SelectionKey

SelectionKey有四種事件

  • SelectionKey.OP_ACCEPT —— 接收連接繼續(xù)事件,表示服務(wù)器監(jiān)聽(tīng)到了客戶(hù)連接,服務(wù)器可以接收這個(gè)連接了

  • SelectionKey.OP_CONNECT —— 連接就緒事件,表示客戶(hù)與服務(wù)器的連接已經(jīng)建立成功

  • SelectionKey.OP_READ —— 讀就緒事件,表示通道中已經(jīng)有了可讀的數(shù)據(jù),可以執(zhí)行讀操作了(通道目前有數(shù)據(jù),可以進(jìn)行讀操作了)

  • SelectionKey.OP_WRITE —— 寫(xiě)就緒事件,表示已經(jīng)可以向通道寫(xiě)數(shù)據(jù)了(通道目前可以用于寫(xiě)操作)

 這里 注意,下面兩種,SelectionKey.OP_READ ,SelectionKey.OP_WRITE ,

1.當(dāng)向通道中注冊(cè)SelectionKey.OP_READ事件后,如果客戶(hù)端有向緩存中write數(shù)據(jù),下次輪詢(xún)時(shí),則會(huì) isReadable()=true;

2.當(dāng)向通道中注冊(cè)SelectionKey.OP_WRITE事件后,這時(shí)你會(huì)發(fā)現(xiàn)當(dāng)前輪詢(xún)線程中isWritable()一直為ture,如果不設(shè)置為其他事件

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(10101);      server.listen();   }

}

NIO與傳統(tǒng)IO最大的不同

  1. NIO有通道的概念,傳統(tǒng)IO沒(méi)有這個(gè)概念,但通道的概念是基于傳統(tǒng)IO的

  2. 傳統(tǒng)IO的字符接受處理是也是實(shí)用的Java原生的序列化流的方式,而NIO是使用ByteBuffer的緩沖區(qū)機(jī)制。

使用telnet測(cè)試,NIO是肯定支持多個(gè)客戶(hù)端同時(shí)操作的,但很重要的一點(diǎn)是NIO是單線程的,傳統(tǒng)IO和NIO的邏輯如下

傳統(tǒng)IO

傳統(tǒng)IO與NIO的區(qū)別

NIO

傳統(tǒng)IO與NIO的區(qū)別

至于NIO如何多線程,可以參考NIO如何多線程操作 ,這其實(shí)也是Netty的原理。

分別用兩個(gè)telnet連接

admindeMacBook-Pro:IOServer admin$ telnet 127.0.0.1 10101
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
dsfds
好的

admindeMacBook-Pro:~ admin$ telnet 127.0.0.1 10101
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
22222
好的

服務(wù)端顯示如下

服務(wù)端啟動(dòng)成功!
新的客戶(hù)端連接
服務(wù)端收到信息:dsfds
新的客戶(hù)端連接
服務(wù)端收到信息:22222

當(dāng)我們退出其中一個(gè)的時(shí)候

admindeMacBook-Pro:~ admin$ telnet 127.0.0.1 10101
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
22222
好的^]
telnet> quit
Connection closed.

服務(wù)端顯示如下

服務(wù)端啟動(dòng)成功!
新的客戶(hù)端連接
服務(wù)端收到信息:dsfds
新的客戶(hù)端連接
服務(wù)端收到信息:22222
客戶(hù)端關(guān)閉

如果我們使用telnet連接進(jìn)去以后,直接關(guān)閉shell,則服務(wù)端會(huì)拋出異常

服務(wù)端啟動(dòng)成功!
新的客戶(hù)端連接
服務(wù)端收到信息:
Exception in thread "main" java.io.IOException: Connection reset by peer
    at sun.nio.ch.FileDispatcherImpl.read0(Native Method)
    at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39)
    at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
    at sun.nio.ch.IOUtil.read(IOUtil.java:197)
    at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:380)
    at com.guanjian.websocket.io.NIOServer.handelerRead(NIOServer.java:111)
    at com.guanjian.websocket.io.NIOServer.handler(NIOServer.java:77)
    at com.guanjian.websocket.io.NIOServer.listen(NIOServer.java:59)
    at com.guanjian.websocket.io.NIOServer.main(NIOServer.java:134)

說(shuō)明在讀取Buffer緩沖區(qū)的時(shí)候,拋出了異常,所以我們應(yīng)該在讀取的時(shí)候捕獲異常,而不是拋出異常

/** * 處理讀的事件 * 
 * @param key * @throws IOException */public void handelerRead(SelectionKey key) {   // 服務(wù)器可讀取消息:得到事件發(fā)生的Socket通道   SocketChannel channel = (SocketChannel) key.channel();   // 創(chuàng)建讀取的緩沖區(qū)   ByteBuffer buffer = ByteBuffer.allocate(1024);   try {      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();           }
   } catch (IOException e) {
      e.printStackTrace();   }
}

我們現(xiàn)在來(lái)證明NIO是單線程的,將以上代碼修改一下

/** * 處理讀的事件 * 
 * @param key * @throws IOException */public void handelerRead(SelectionKey key) {   // 服務(wù)器可讀取消息:得到事件發(fā)生的Socket通道   SocketChannel channel = (SocketChannel) key.channel();   // 創(chuàng)建讀取的緩沖區(qū)   ByteBuffer buffer = ByteBuffer.allocate(1024);   try {      int read = channel.read(buffer);      Thread.sleep(60000);      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();           }
   } catch (Exception e) {
      e.printStackTrace();   }
}

我們讓他發(fā)送消息的時(shí)候睡一分鐘。啟動(dòng)服務(wù)端,連接第一個(gè)telnet進(jìn)來(lái),并發(fā)幾個(gè)字符

此時(shí)我們連進(jìn)第二個(gè)telnet,會(huì)發(fā)現(xiàn)服務(wù)端沒(méi)反應(yīng),需要等到一分鐘之后,第一個(gè)telnet才會(huì)收到"好的",而服務(wù)端才會(huì)顯示"新的客戶(hù)端連接"。

說(shuō)明服務(wù)端在處理發(fā)送字符的時(shí)候被阻塞,NIO為單線程。

到此,相信大家對(duì)“傳統(tǒng)IO與NIO的區(qū)別”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!

網(wǎng)站名稱(chēng):傳統(tǒng)IO與NIO的區(qū)別
URL地址:http://muchs.cn/article30/ihjjso.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營(yíng)銷(xiāo)推廣、網(wǎng)站策劃、網(wǎng)站收錄品牌網(wǎng)站制作、手機(jī)網(wǎng)站建設(shè)動(dòng)態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站網(wǎng)頁(yè)設(shè)計(jì)