本篇內(nèi)容主要講解“傳統(tǒng)IO與NIO的區(qū)別”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“傳統(tǒng)IO與NIO的區(qū)別”吧!
成都創(chuàng)新互聯(lián)成立與2013年,先為江達(dá)等服務(wù)建站,江達(dá)等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為江達(dá)企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
我們先來看一段傳統(tǒng)IO的代碼
public class OioServer {public static void main(String[] args) throws IOException {//這里可以直接寫成ServerSocket server = new ServerSocket(10101); ServerSocket server = new ServerSocket(); server.bind(new InetSocketAddress(10101)); System.out.println("使用telnet連接
admindeMacBook-Pro:~ admin$ telnet 127.0.0.1 10101
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.我們會看到OioServer的運(yùn)行情況
服務(wù)器啟動
來了一個新客戶端但是當(dāng)我們又使用一個telnet連接進(jìn)來的時候,OioServer的運(yùn)行情況沒變,說明一個服務(wù)端只能接收一個客戶端點(diǎn)連接,原因在于Socket socket = server.accept();發(fā)生了堵塞,現(xiàn)在我們將其改寫成多線程
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ù)器啟動"); while (true) { Socket socket = server.accept(); System.out.println("來了一個新客戶端"); 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)我們啟動了多個telnet進(jìn)行連接的時候,它是可以一起連接進(jìn)來的
服務(wù)器啟動
來了一個新客戶端
來了一個新客戶端但是這里有一個問題,我們線程池的可用線程是有限的,不可能無限提供線程來接收大量客戶端的連接,遲早它會無響應(yīng)被堵塞的。
我們現(xiàn)在來看一下NIO,NIO其實(shí)是使用傳統(tǒng)IO的特性創(chuàng)建一個channel(通道),通過該通道來注冊事件SelectionKey
SelectionKey有四種事件
SelectionKey.OP_ACCEPT —— 接收連接繼續(xù)事件,表示服務(wù)器監(jiān)聽到了客戶連接,服務(wù)器可以接收這個連接了
SelectionKey.OP_CONNECT —— 連接就緒事件,表示客戶與服務(wù)器的連接已經(jīng)建立成功
SelectionKey.OP_READ —— 讀就緒事件,表示通道中已經(jīng)有了可讀的數(shù)據(jù),可以執(zhí)行讀操作了(通道目前有數(shù)據(jù),可以進(jìn)行讀操作了)
SelectionKey.OP_WRITE —— 寫就緒事件,表示已經(jīng)可以向通道寫數(shù)據(jù)了(通道目前可以用于寫操作)
這里 注意,下面兩種,SelectionKey.OP_READ ,SelectionKey.OP_WRITE ,
1.當(dāng)向通道中注冊SelectionKey.OP_READ事件后,如果客戶端有向緩存中write數(shù)據(jù),下次輪詢時,則會 isReadable()=true;
2.當(dāng)向通道中注冊SelectionKey.OP_WRITE事件后,這時你會發(fā)現(xiàn)當(dāng)前輪詢線程中isWritable()一直為ture,如果不設(shè)置為其他事件
public class NIOServer { // 通道管理器 private Selector selector; /** * 獲得一個ServerSocket通道,并對該通道做一些初始化的工作 * * @param port * 綁定的端口號 * @throws IOException */ public void initServer(int port) throws IOException { // 獲得一個ServerSocket通道 ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 設(shè)置通道為非阻塞 serverChannel.configureBlocking(false); // 將該通道對應(yīng)的ServerSocket綁定到port端口 serverChannel.socket().bind(new InetSocketAddress(port)); // 獲得一個通道管理器 this.selector = Selector.open(); // 將通道管理器和該通道綁定,并為該通道注冊SelectionKey.OP_ACCEPT事件,注冊該事件后, // 當(dāng)該事件到達(dá)時,selector.select()會返回,如果該事件沒到達(dá)selector.select()會一直阻塞。 serverChannel.register(selector, SelectionKey.OP_ACCEPT); } /** * 采用輪詢的方式監(jiān)聽selector上是否有需要處理的事件,如果有,則進(jìn)行處理 * * @throws IOException */ public void listen() throws IOException { System.out.println("服務(wù)端啟動成功!"); // 輪詢訪問selector while (true) { // 當(dāng)注冊的事件到達(dá)時,方法返回;否則,該方法會一直阻塞 selector.select(); // 獲得selector中選中的項(xiàng)的迭代器,選中的項(xiàng)為注冊的事件 Iterator> ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 刪除已選的key,以防重復(fù)處理 ite.remove(); handler(key); } } } /** * 處理請求 * * @param key * @throws IOException */ public void handler(SelectionKey key) throws IOException { // 客戶端請求連接事件 if (key.isAcceptable()) { handlerAccept(key); // 獲得了可讀的事件 } else if (key.isReadable()) { handelerRead(key); } } /** * 處理連接請求 * * @param key * @throws IOException */ public void handlerAccept(SelectionKey key) throws IOException { ServerSocketChannel server = (ServerSocketChannel) key.channel(); // 獲得和客戶端連接的通道 SocketChannel channel = server.accept(); // 設(shè)置成非阻塞 channel.configureBlocking(false); // 在這里可以給客戶端發(fā)送信息哦 System.out.println("新的客戶端連接"); // 在和客戶端連接成功之后,為了可以接收到客戶端的信息,需要給通道設(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); //回寫數(shù)據(jù) ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes()); channel.write(outBuffer);// 將消息回送給客戶端 }else{ System.out.println("客戶端關(guān)閉"); key.cancel(); } } /** * 啟動服務(wù)端測試 * * @throws IOException */ public static void main(String[] args) throws IOException { NIOServer server = new NIOServer(); server.initServer(10101); server.listen(); } }NIO與傳統(tǒng)IO最大的不同
NIO有通道的概念,傳統(tǒng)IO沒有這個概念,但通道的概念是基于傳統(tǒng)IO的
傳統(tǒng)IO的字符接受處理是也是實(shí)用的Java原生的序列化流的方式,而NIO是使用ByteBuffer的緩沖區(qū)機(jī)制。
使用telnet測試,NIO是肯定支持多個客戶端同時操作的,但很重要的一點(diǎn)是NIO是單線程的,傳統(tǒng)IO和NIO的邏輯如下
傳統(tǒng)IO
NIO
至于NIO如何多線程,可以參考NIO如何多線程操作 ,這其實(shí)也是Netty的原理。
分別用兩個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ù)端啟動成功!
新的客戶端連接
服務(wù)端收到信息:dsfds
新的客戶端連接
服務(wù)端收到信息:22222當(dāng)我們退出其中一個的時候
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ù)端啟動成功!
新的客戶端連接
服務(wù)端收到信息:dsfds
新的客戶端連接
服務(wù)端收到信息:22222
客戶端關(guān)閉如果我們使用telnet連接進(jìn)去以后,直接關(guān)閉shell,則服務(wù)端會拋出異常
服務(wù)端啟動成功!
新的客戶端連接
服務(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)說明在讀取Buffer緩沖區(qū)的時候,拋出了異常,所以我們應(yīng)該在讀取的時候捕獲異常,而不是拋出異常
/** * 處理讀的事件 * * @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); //回寫數(shù)據(jù) ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes()); channel.write(outBuffer);// 將消息回送給客戶端 }else{ System.out.println("客戶端關(guān)閉"); key.cancel(); } } catch (IOException e) { e.printStackTrace(); } }我們現(xiàn)在來證明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); //回寫數(shù)據(jù) ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes()); channel.write(outBuffer);// 將消息回送給客戶端 }else{ System.out.println("客戶端關(guān)閉"); key.cancel(); } } catch (Exception e) { e.printStackTrace(); } }我們讓他發(fā)送消息的時候睡一分鐘。啟動服務(wù)端,連接第一個telnet進(jìn)來,并發(fā)幾個字符
此時我們連進(jìn)第二個telnet,會發(fā)現(xiàn)服務(wù)端沒反應(yīng),需要等到一分鐘之后,第一個telnet才會收到"好的",而服務(wù)端才會顯示"新的客戶端連接"。
說明服務(wù)端在處理發(fā)送字符的時候被阻塞,NIO為單線程。
到此,相信大家對“傳統(tǒng)IO與NIO的區(qū)別”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
當(dāng)前題目:傳統(tǒng)IO與NIO的區(qū)別
轉(zhuǎn)載來源:http://weahome.cn/article/ihjjso.html