本次我們主要來說一下我們的IO阻塞模型,只是不多,但是一定要理解,對(duì)于后面理解netty很重要的

          IO模型精講?

            IO模型就是說用什么樣的通道進(jìn)行數(shù)據(jù)的發(fā)送和接收,Java共支持3種網(wǎng)絡(luò)編程IO模式:BIO,NIO,AIO。

          BIO

            BIO(Blocking IO)
          同步阻塞模型,一個(gè)客戶端連接對(duì)應(yīng)一個(gè)處理線程。也是我們熟悉的同步阻塞模型,先別管那個(gè)同步的概念,我們先來看一下什么是阻塞,簡(jiǎn)單來一段代碼。

            服務(wù)端:
          package com.xiaocai.bio; import java.io.IOException; import
          java.net.ServerSocket;import java.net.Socket; public class SocketServer { public
          static void main(String[] args) throws IOException { ServerSocket serverSocket =
          new ServerSocket(9000); while (true) { System.out.println("等待連接。。"); //阻塞方法
          Socket socket = serverSocket.accept(); System.out.println("有客戶端連接了。。");
          handler(socket); } }private static void handler(Socket socket) throws
          IOException { System.out.println("thread id = " +
          Thread.currentThread().getId());byte[] bytes = new byte[1024];
          System.out.println("準(zhǔn)備read。。"); //接收客戶端的數(shù)據(jù),阻塞方法,沒有數(shù)據(jù)可讀時(shí)就阻塞 int read =
          socket.getInputStream().read(bytes); System.out.println("read完畢。。"); if (read
          != -1) { System.out.println("接收到客戶端的數(shù)據(jù):" + new String(bytes, 0, read));
          System.out.println("thread id = " + Thread.currentThread().getId()); }
          socket.getOutputStream().write("HelloClient".getBytes());
          socket.getOutputStream().flush(); } }
            客戶端
          package com.xiaocai.bio; import java.io.IOException; import java.net.Socket;
          public class SocketClient { public static void main(String[] args) throws
          IOException { Socket socket= new Socket("127.0.0.1", 9000); //向服務(wù)端發(fā)送數(shù)據(jù)
          socket.getOutputStream().write("HelloServer".getBytes());
          socket.getOutputStream().flush(); System.out.println("向服務(wù)端發(fā)送數(shù)據(jù)結(jié)束"); byte[]
          bytes =new byte[1024]; //接收服務(wù)端回傳的數(shù)據(jù) socket.getInputStream().read(bytes);
          System.out.println("接收到服務(wù)端的數(shù)據(jù):" + new String(bytes)); socket.close(); } }
            這個(gè)就是一個(gè)簡(jiǎn)單的BIO服務(wù)端代碼,就是要準(zhǔn)備接受線程訪問的代碼段。這一個(gè)單線程版本什么意思呢?

            我們先開啟一個(gè)端口為9000的socket服務(wù),然后運(yùn)行Socket socket =
          serverSocket.accept();意思就是等待線程的出現(xiàn),我們來接收客戶端的請(qǐng)求,這個(gè)方法時(shí)阻塞的,也是只有在阻塞狀態(tài)才可以接收到我們的請(qǐng)求。當(dāng)有請(qǐng)求進(jìn)來時(shí),運(yùn)行handler(socket);方法,中間是打印線程ID的方法不解釋,int
          read =
          socket.getInputStream().read(bytes);準(zhǔn)備讀取我們的客戶端發(fā)送數(shù)據(jù)。read和write可能會(huì)混淆,我畫個(gè)圖來說一下。



            我們也可以看到我們的客戶端也是先拿到socket連接(Socket socket = new Socket("127.0.0.1",
          9000)),然后要往服務(wù)端寫入數(shù)據(jù)(socket.getOutputStream().write("HelloServer".getBytes());)以byte字節(jié)形式寫入。這時(shí)我們的服務(wù)端等待read我們的客戶端weite的數(shù)據(jù),會(huì)進(jìn)入
          阻塞狀態(tài),如果我們的客戶端遲遲不寫數(shù)據(jù),我們的客戶端一直是阻塞狀態(tài),也就無法接收到新的請(qǐng)求,因?yàn)樽枞?,沒法回到我們的Socket socket =
          serverSocket.accept();去等待客戶端請(qǐng)求,只要在serverSocket.accept阻塞時(shí)才可以接收新的請(qǐng)求。于是我們采取了多線程的方式來解決這個(gè)問題,我們來看一下代碼。
          package com.xiaocai.bio; import java.io.IOException; import
          java.net.ServerSocket;import java.net.Socket; public class SocketServer { public
          static void main(String[] args) throws IOException { ServerSocket serverSocket =
          new ServerSocket(9000); while (true) { System.out.println("等待連接。。"); //阻塞方法
          Socket socket = serverSocket.accept(); System.out.println("有客戶端連接了。。"); new
          Thread(new Runnable() { @Override public void run() { try { handler(socket); }
          catch (IOException e) { e.printStackTrace(); } } }).start(); } } private static
          void handler(Socket socket) throws IOException { System.out.println("thread id
          = " + Thread.currentThread().getId()); byte[] bytes = new byte[1024];
          System.out.println("準(zhǔn)備read。。"); //接收客戶端的數(shù)據(jù),阻塞方法,沒有數(shù)據(jù)可讀時(shí)就阻塞 int read =
          socket.getInputStream().read(bytes); System.out.println("read完畢。。"); if (read
          != -1) { System.out.println("接收到客戶端的數(shù)據(jù):" + new String(bytes, 0, read));
          System.out.println("thread id = " + Thread.currentThread().getId()); }
          socket.getOutputStream().write("HelloClient".getBytes());
          socket.getOutputStream().flush(); } }

            我們這時(shí)每次有客戶端來新的請(qǐng)求時(shí),我們就會(huì)開啟一個(gè)線程來處理這個(gè)請(qǐng)求,及時(shí)你的客戶端沒有及時(shí)的write數(shù)據(jù),雖然我們的服務(wù)端read進(jìn)行了阻塞,也只是阻塞了你自己的線程,不會(huì)造成其它請(qǐng)求無法接收到。




          ?  這樣的處理方式貌似好了很多很多,其實(shí)不然,想一個(gè)實(shí)例,我們的看小妹直播時(shí),一句歡迎榜一大哥,彈幕很多,加入一次性來了100彈幕還好,我們開啟100個(gè)線程來處理,如果一起來了十萬彈幕呢?難道你要開啟十萬個(gè)線程來處理這些彈幕嘛?很顯然BIO還是有弊端的,BIO還是有優(yōu)點(diǎn)的(代碼少,不容易出錯(cuò))。

          NIO

            NIO(Non Blocking IO)
          同步非阻塞,服務(wù)器實(shí)現(xiàn)模式為一個(gè)線程可以處理多個(gè)請(qǐng)求(連接),客戶端發(fā)送的連接請(qǐng)求都會(huì)注冊(cè)到多路復(fù)用器selector上,多路復(fù)用器輪詢到連接有IO請(qǐng)求就進(jìn)行處理。
          可能概念太抽象了,我來舉個(gè)例子吧,現(xiàn)在有兩個(gè)小區(qū)都有很多的房子出租,BIO小區(qū)和NIO小區(qū),都有一個(gè)門衛(wèi),BIO小區(qū),來了一個(gè)租客,門衛(wèi)大爺就拿著鑰匙,帶這個(gè)租客去看房子了,后面來的租客都暫時(shí)無法看房子了,尷尬...想同時(shí)多人看房子,必須增加門衛(wèi)大爺?shù)臄?shù)量,而我們的NIO小區(qū)就很聰明,還是一個(gè)門衛(wèi)大媽,來了一個(gè)租客要看房子,門衛(wèi)大媽,給了那個(gè)租客一把
          鑰匙
          ,并且告訴他哪房間是空的,你自己進(jìn)去看吧,及時(shí)這個(gè)租客看房子慢,耽誤了很多時(shí)間也不怕了,因?yàn)殚T衛(wèi)大媽一直在門衛(wèi)室,即使又來了新的租客,門衛(wèi)大媽也是如此,只給鑰匙和空房間地址就可以了。這個(gè)例子反正我記得很清楚,也覺得很貼切,這里提到了一個(gè)鑰匙的概念,一會(huì)告訴你們是做什么的,我們先看一下代碼。

            服務(wù)端
          package com.xiaocai.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.ServerSocketChannel;import java.nio.channels.SocketChannel;
          import java.util.Iterator; public class NIOServer { //public static
          ExecutorService pool = Executors.newFixedThreadPool(10); public static void
          main(String[] args)throws IOException { // 創(chuàng)建一個(gè)在本地端口進(jìn)行監(jiān)聽的服務(wù)Socket通道.并設(shè)置為非阻塞方式
          ServerSocketChannel ssc = ServerSocketChannel.open(); //
          必須配置為非阻塞才能往selector上注冊(cè),否則會(huì)報(bào)錯(cuò),selector模式本身就是非阻塞模式 ssc.configureBlocking(false);
          ssc.socket().bind(new InetSocketAddress(9000)); // 創(chuàng)建一個(gè)選擇器selector Selector
          selector = Selector.open(); //
          把ServerSocketChannel注冊(cè)到selector上,并且selector對(duì)客戶端accept連接操作感興趣
          ssc.register(selector, SelectionKey.OP_ACCEPT);while (true) {
          System.out.println("等待事件發(fā)生。。"); // 輪詢監(jiān)聽channel里的key,select是阻塞的,accept()也是阻塞的 int
          select = selector.select(); System.out.println("有事件發(fā)生了。。"); // 有客戶端請(qǐng)求,被輪詢監(jiān)聽到
          Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while
          (it.hasNext()) { SelectionKey key= it.next(); //刪除本次已處理的key,防止下次select重復(fù)處理
          it.remove(); handle(key); } } }private static void handle(SelectionKey key)
          throws IOException { if (key.isAcceptable()) { System.out.println(
          "有客戶端連接事件發(fā)生了。。"); ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
          //NIO非阻塞體現(xiàn):此處accept方法是阻塞的,但是這里因?yàn)槭前l(fā)生了連接事件,所以這個(gè)方法會(huì)馬上執(zhí)行完,不會(huì)阻塞 //
          處理完連接請(qǐng)求不會(huì)繼續(xù)等待客戶端的數(shù)據(jù)發(fā)送 SocketChannel sc = ssc.accept(); sc.configureBlocking(
          false); //通過Selector監(jiān)聽Channel時(shí)對(duì)讀事件感興趣 sc.register(key.selector(),
          SelectionKey.OP_READ); }else if (key.isReadable()) { System.out.println(
          "有客戶端數(shù)據(jù)可讀事件發(fā)生了。。"); SocketChannel sc = (SocketChannel) key.channel();
          ByteBuffer buffer= ByteBuffer.allocate(1024); //
          NIO非阻塞體現(xiàn):首先read方法不會(huì)阻塞,其次這種事件響應(yīng)模型,當(dāng)調(diào)用到read方法時(shí)肯定是發(fā)生了客戶端發(fā)送數(shù)據(jù)的事件 int len =
          sc.read(buffer);if (len != -1) { System.out.println("讀取到客戶端發(fā)送的數(shù)據(jù):" + new
          String(buffer.array(), 0, len)); } ByteBuffer bufferToWrite =
          ByteBuffer.wrap("HelloClient".getBytes()); sc.write(bufferToWrite);
          key.interestOps(SelectionKey.OP_READ| SelectionKey.OP_WRITE); } else if
          (key.isWritable()) { SocketChannel sc= (SocketChannel) key.channel();
          System.out.println("write事件"); // NIO事件觸發(fā)是水平觸發(fā) //
          使用Java的NIO編程的時(shí)候,在沒有數(shù)據(jù)可以往外寫的時(shí)候要取消寫事件,// 在有數(shù)據(jù)往外寫的時(shí)候再注冊(cè)寫事件
          key.interestOps(SelectionKey.OP_READ);//sc.close(); } } }
            客戶端
          package com.xiaocai.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; public class
          NioClient {//通道管理器 private Selector selector; /** * 啟動(dòng)客戶端測(cè)試 * * @throws
          IOException*/ public static void main(String[] args) throws IOException {
          NioClient client= new NioClient(); client.initClient("127.0.0.1", 9000);
          client.connect(); }/** * 獲得一個(gè)Socket通道,并對(duì)該通道做一些初始化的工作 * * @param ip 連接的服務(wù)器的ip *
          @param port 連接的服務(wù)器的端口號(hào) * @throws IOException */ public void initClient(String
          ip,int port) throws IOException { // 獲得一個(gè)Socket通道 SocketChannel channel =
          SocketChannel.open();// 設(shè)置通道為非阻塞 channel.configureBlocking(false); // 獲得一個(gè)通道管理器
          this.selector = Selector.open(); // 客戶端連接服務(wù)器,其實(shí)方法執(zhí)行并沒有實(shí)現(xiàn)連接,需要在listen()方法中調(diào) //
          用channel.finishConnect() 才能完成連接 channel.connect(new InetSocketAddress(ip,
          port));//將通道管理器和該通道綁定,并為該通道注冊(cè)SelectionKey.OP_CONNECT事件。
          channel.register(selector, SelectionKey.OP_CONNECT); }/** *
          采用輪詢的方式監(jiān)聽selector上是否有需要處理的事件,如果有,則進(jìn)行處理 * *@throws IOException */ public void
          connect()throws IOException { // 輪詢?cè)L問selector while (true) { selector.select();
          // 獲得selector中選中的項(xiàng)的迭代器 Iterator<SelectionKey> it = this
          .selector.selectedKeys().iterator();while (it.hasNext()) { SelectionKey key =
          (SelectionKey) it.next();// 刪除已選的key,以防重復(fù)處理 it.remove(); // 連接事件發(fā)生 if
          (key.isConnectable()) { SocketChannel channel= (SocketChannel) key.channel(); //
          如果正在連接,則完成連接 if (channel.isConnectionPending()) { channel.finishConnect(); } //
          設(shè)置成非阻塞 channel.configureBlocking(false); //在這里可以給服務(wù)端發(fā)送信息哦 ByteBuffer buffer =
          ByteBuffer.wrap("HelloServer".getBytes()); channel.write(buffer); //
          在和服務(wù)端連接成功之后,為了可以接收到服務(wù)端的信息,需要給通道設(shè)置讀的權(quán)限。 channel.register(this.selector,
          SelectionKey.OP_READ);// 獲得了可讀的事件 } else if (key.isReadable()) { read(key); } }
          } }/** * 處理讀取服務(wù)端發(fā)來的信息 的事件 * * @param key * @throws IOException */ public void
          read(SelectionKey key)throws IOException { //和服務(wù)端的read方法一樣 //
          服務(wù)器可讀取消息:得到事件發(fā)生的Socket通道 SocketChannel channel = (SocketChannel) key.channel();
          // 創(chuàng)建讀取的緩沖區(qū) ByteBuffer buffer = ByteBuffer.allocate(1024); int len =
          channel.read(buffer);if (len != -1) { System.out.println("客戶端收到信息:" + new
          String(buffer.array(), 0, len)); } } }

            代碼看到了很多很多,我來解釋一下大概什么意思吧,這個(gè)NIO超級(jí)重要后面的netty就是基于這個(gè)寫的,一定要搞懂,首先我們創(chuàng)建了一個(gè)ServerSocketChannel和一個(gè)選擇器selector,設(shè)置為非阻塞的(固定寫法,沒有為什么),將我們的?selector綁定到我們的ServerSocketChannel上,然后運(yùn)行selector.select();進(jìn)入阻塞狀態(tài),別擔(dān)心,這個(gè)阻塞沒影響,為我們提供了接收客戶端的請(qǐng)求,你沒有請(qǐng)求,我阻塞著,不會(huì)耽誤你們什么的。


            回到我們的客戶端,還是差不多的樣子,拿到我們的NioClient開始連接我們的服務(wù)端,這個(gè)時(shí)候,我們的服務(wù)端接收到了我們的客戶端請(qǐng)求,阻塞狀態(tài)的selector.select()繼續(xù)運(yùn)行,并且給予了一個(gè)SelectionKey(Iterator<SelectionKey>
          it =
          selector.selectedKeys().iterator())也就是我們剛才的小例子中提到的鑰匙,key=鑰匙,還算是靠譜吧~!開始運(yùn)行我們的handle方法,有個(gè)if
          else,這個(gè)是說,你是第一次請(qǐng)求要建立通道,還是要寫數(shù)據(jù),還是要讀取數(shù)據(jù),記住啊,讀寫都是相對(duì)的,自己多琢磨幾次就可以轉(zhuǎn)過圈來了,就是我上面畫圖說的read和write。拿我們的建立通道來說,通過我們的鑰匙key你就可以得到ServerSocketChannel,然后進(jìn)行設(shè)置下次可能會(huì)發(fā)生的讀寫事件,然后看我們的讀事件,我們看到了int
          len =
          sc.read(buffer)這個(gè)讀在我們的BIO中是阻塞的,而我們的NIO這個(gè)方法不是阻塞的,這也就體現(xiàn)出來了我們的BIO同步阻塞和NIO同步非阻塞,阻塞和非阻塞的區(qū)別也就說完了。畫個(gè)圖,我們來看一下我們的NIO模型。

            NIO?有三大核心組件:?Channel(通道),?Buffer(緩沖區(qū)),Selector(選擇器)




          ?  這里我們的Buffer沒有去說,到netty會(huì)說的,?Channel(通道),?Buffer(緩沖區(qū))都是雙向的,現(xiàn)在回過頭來想想我舉的小例子,selector門衛(wèi)大媽,SelectionKey鑰匙。對(duì)于NIO有了一些理解了吧,NIO看著很棒的,但是你有想過寫上述代碼的痛苦嗎?

          AIO

            AIO(NIO 2.0) 異步非阻塞, 由操作系統(tǒng)完成后回調(diào)通知服務(wù)端程序啟動(dòng)線程去處理,
          一般適用于連接數(shù)較多且連接時(shí)間較長(zhǎng)的應(yīng)用。其實(shí)AIO就是對(duì)于NIO的二次封裝,要不怎么叫做NIO2.0呢,我們來簡(jiǎn)單看一下代碼。

            服務(wù)端:
          package com.xiaocai.aio; import java.io.IOException; import
          java.net.InetSocketAddress;import java.nio.ByteBuffer; import
          java.nio.channels.AsynchronousServerSocketChannel;import
          java.nio.channels.AsynchronousSocketChannel;import
          java.nio.channels.CompletionHandler;public class AIOServer { public static void
          main(String[] args)throws Exception { final AsynchronousServerSocketChannel
          serverChannel = AsynchronousServerSocketChannel.open().bind(new
          InetSocketAddress(9000)); serverChannel.accept(null, new
          CompletionHandler<AsynchronousSocketChannel, Object>() { @Override public void
          completed(AsynchronousSocketChannel socketChannel, Object attachment) {try { //
          再此接收客戶端連接,如果不寫這行代碼后面的客戶端連接連不上服務(wù)端 serverChannel.accept(attachment, this);
          System.out.println(socketChannel.getRemoteAddress()); ByteBuffer buffer=
          ByteBuffer.allocate(1024); socketChannel.read(buffer, buffer, new
          CompletionHandler<Integer, ByteBuffer>() { @Override public void
          completed(Integer result, ByteBuffer buffer) { buffer.flip();
          System.out.println(new String(buffer.array(), 0, result));
          socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes())); } @Override
          public void failed(Throwable exc, ByteBuffer buffer) { exc.printStackTrace(); }
          }); }catch (IOException e) { e.printStackTrace(); } } @Override public void
          failed(Throwable exc, Object attachment) { exc.printStackTrace(); } });
          Thread.sleep(Integer.MAX_VALUE); } }
            客戶端:
          package com.xiaocai.aio; import java.net.InetSocketAddress; import
          java.nio.ByteBuffer;import java.nio.channels.AsynchronousSocketChannel; public
          class AIOClient { public static void main(String... args) throws Exception {
          AsynchronousSocketChannel socketChannel= AsynchronousSocketChannel.open();
          socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000)).get();
          socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes())); ByteBuffer
          buffer= ByteBuffer.allocate(512); Integer len =
          socketChannel.read(buffer).get();if (len != -1) { System.out.println("客戶端收到信息:"
          +new String(buffer.array(), 0, len)); } } }
            阻塞非阻塞都明白了,這里來解釋一下同步,我們看到我們的AIO在serverChannel.accept(null, new
          CompletionHandler<AsynchronousSocketChannel, Object>()
          {}直接開啟了線程,也就是說accept直接以后,我不再需要考慮阻塞情況,可以繼續(xù)運(yùn)行下面的代碼了,也就是我們說到的異步執(zhí)行,內(nèi)部還是我們的NIO,不要覺得AIO多么的6B,內(nèi)部就是封裝了我們的NIO,性能和NIO其實(shí)差不多的,可能有些時(shí)候還不如NIO(未實(shí)測(cè))。


            遺漏一個(gè)知識(shí)點(diǎn),NIO的多路復(fù)用器是如何工作的,在我們的JDK1.5以前的,多路復(fù)用器是數(shù)組和鏈表的方式來遍歷的,到了我們的JDK1.5采用hash來回調(diào)的。



          ?總結(jié):

            我們這次主要說了BIO、NIO、AIO三個(gè)網(wǎng)絡(luò)編程IO模式,最重要的就是我們的NIO,一張圖來總結(jié)一下三個(gè)IO的差別吧。

          ?

          ?

          最進(jìn)弄了一個(gè)公眾號(hào),小菜技術(shù),歡迎大家的加入



          ?

          友情鏈接
          ioDraw流程圖
          API參考文檔
          OK工具箱
          云服務(wù)器優(yōu)惠
          阿里云優(yōu)惠券
          騰訊云優(yōu)惠券
          京東云優(yōu)惠券
          站點(diǎn)信息
          問題反饋
          郵箱:[email protected]
          QQ群:637538335
          關(guān)注微信

                性xxxx老妇sveio | 免费观看的操逼视频 | 成人免费视频 观看 | 日本三a级 | 美女张开腿让男人操 |