<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Netty 源碼解析 Part 0——第1篇:BIO vs NIO

          共 41632字,需瀏覽 84分鐘

           ·

          2021-04-22 08:11


          -     前言    -


          本文源碼地址:
          https://gitee.com/wangjianxin199003/netty-source-code-analysis.git

          使用過(guò)java的同學(xué)想必對(duì)BIO和NIO這兩個(gè)詞匯并不陌生,即便平時(shí)工作中沒(méi)有接觸過(guò),也會(huì)在招聘需求里見過(guò),或者面試被問(wèn)到過(guò)。那么BIO和NIO到底表示什么意思呢,BIO即blocking io,NIO有的解釋為new io,有的解釋為none blocking io,個(gè)人認(rèn)為none blocking io更為準(zhǔn)確,至于為什么,咱們接下來(lái)看。


          -     BIO hello word    -


          1.1 BIO服務(wù)端
          /**
           * 歡迎關(guān)注公眾號(hào)“種代碼“,獲取博主微信深入交流
           *
           * @author wangjianxin
           */

          public class HelloBioServer {
              public static void main(String[] args) throws IOException {
                  //創(chuàng)建ServerSocket
                  ServerSocket serverSocket = new ServerSocket();
                  //綁定到8000端口
                  serverSocket.bind(new InetSocketAddress(8000));
                  new BioServerConnector(serverSocket).start();
              }
          }
          /**
           * 歡迎關(guān)注公眾號(hào)“種代碼“,獲取博主微信深入交流
           *
           * @author wangjianxin
           */

          public class BioServerConnector {
              private final ServerSocket serverSocket;

              public BioServerConnector(ServerSocket serverSocket) {
                  this.serverSocket = serverSocket;
              }

              public void start() {
                  Thread thread = new Thread(new Runnable() {
                      @Override
                      public void run() {
                          while (true) {
                              Socket newSocket = null;
                              try {
                                  //阻塞在這里等待新連接,返回值為一條新的連接
                                  newSocket = serverSocket.accept();
                              } catch (IOException e) {
                              }
                              //將新連接交給handler處理
                              new BioServerHandler(newSocket).start();
                          }
                      }
                  });
                  thread.start();
              }
          }
          /**
           * 歡迎關(guān)注公眾號(hào)“種代碼“,獲取博主微信深入交流
           *
           * @author wangjianxin
           */

          public class BioServerHandler {
              private final Socket socket;

              public BioServerHandler(Socket socket) {
                  this.socket = socket;
              }

              public void start() {
                  Thread thread = new Thread(new Runnable() {
                      @Override
                      public void run() {
                          while (true) {
                              try {
                                  InputStream inputStream = socket.getInputStream();
                                  byte[] buffer = new byte[1024];
                                  //阻塞操作,因?yàn)椴恢纈nputStream中什么時(shí)候會(huì)有數(shù)據(jù)可讀,只能阻塞在這里等待
                                  //每一個(gè)連接都要消耗一個(gè)線程
                                  int readLength = inputStream.read(buffer);
                                  if (readLength != -1) {
                                      String name = new String(buffer, 0, readLength);
                                      System.out.println(name);
                                      //打印客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù)
                                      socket.getOutputStream().write(("hello, " + name).getBytes());
                                  }
                              } catch (Throwable e) {
                                  try {
                                      socket.close();
                                  } catch (IOException ioException) {
                                  }
                              }
                          }
                      }
                  });
                  thread.start();
              }
          }

          該bio服務(wù)端共有3個(gè)類,HelloBioServer、BioServerConnector、BioServerHandler,麻雀雖小,五臟俱全,是典型的BIO服務(wù)最小架構(gòu)。

          • HelloBioServer:?jiǎn)?dòng)類,創(chuàng)建一個(gè)ServerSocket,并交給BioServerConnetor處理
          • BioServerConnector:接受新連接的類,其中創(chuàng)建一個(gè)線程,循環(huán)阻塞在ServerSocket上等待新連接的到來(lái),每建立一條新連接,就創(chuàng)建一個(gè)BioServerHandler,并將該連接交給BioServerHandler處理
          • BioServerHandler:處理連接上數(shù)據(jù)的類,每個(gè)BioServerHandler都創(chuàng)建一個(gè)線程循環(huán)阻塞在Socket的InputStream上,讀取數(shù)據(jù),再為該數(shù)據(jù)拼上“Hello, ”發(fā)送回去。

          1.2 BIO客戶端

          /**
           * 歡迎關(guān)注公眾號(hào)“種代碼“,獲取博主微信深入交流
           *
           * @author wangjianxin
           */
          public class HelloBioClient {
              //創(chuàng)建多少個(gè)客戶端
              private static final int CLIENTS = 2;

              public static void main(String[] args) throws IOException {
                  for (int i = 0; i < CLIENTS; i++) {
                      final int clientIndex = i;
                      Thread client = new Thread(new Runnable() {
                          @Override
                          public void run() {
                              try {
                                  //創(chuàng)建socket
                                  Socket socket = new Socket();
                                  //連接8000端口
                                  socket.connect(new InetSocketAddress(8000));
                                  while (true) {
                                      OutputStream outputStream = socket.getOutputStream();
                                      //向服務(wù)端發(fā)送”zhongdaima" + 客戶端編號(hào)
                                      outputStream.write(("
          zhongdaima" + clientIndex).getBytes());
                                      InputStream inputStream = socket.getInputStream();
                                      byte[] buffer = new byte[1024];
                                      int readLength = inputStream.read(buffer);
                                      //打印服務(wù)端返回?cái)?shù)據(jù)
                                      System.out.println(new String(buffer, 0, readLength));
                                      Thread.sleep(1000);
                                  }
                              } catch (Throwable e) {

                              }
                          }
                      });
                      client.start();
                  }
              }
          }


          BIO客戶端共1個(gè)類,HelloBioClient,在該客戶端中創(chuàng)建了兩個(gè)線程、兩條連接,每個(gè)線程處理一條連接,循環(huán)向服務(wù)端發(fā)送“zhongdaima" + 客戶端編號(hào),并打印服務(wù)端返回的數(shù)據(jù)。



          -     NIO hello word    -


          2.1 NIO服務(wù)端


          /**
           * 歡迎關(guān)注公眾號(hào)“種代碼“,獲取博主微信深入交流
           * @author wangjianxin
           */

          public class HelloNioServer {
              public static void main(String[] args) throws IOException {
                  //創(chuàng)建ServerSocketChannel
                  ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
                  //設(shè)置Channel為非阻塞
                  serverSocketChannel.configureBlocking(false);
                  //綁定到8000端口
                  serverSocketChannel.bind(new InetSocketAddress(8000));
                  //交給Connector
                  new NioServerConnector(serverSocketChannel).start();
              }
          }
          /**
           * 歡迎關(guān)注公眾號(hào)“種代碼“,獲取博主微信深入交流
           * @author wangjianxin
           */

          public class NioServerConnector {
              private final ServerSocketChannel serverSocketChannel;

              private final Selector selector;

              private final NioServerHandler nioServerHandler;

              public NioServerConnector(ServerSocketChannel serverSocketChannel) throws IOException {
                  this.selector = Selector.open();
                  this.serverSocketChannel = serverSocketChannel;
                  //向selector注冊(cè)Channel,感興趣事件為OP_ACCEPT(即新連接接入)
                  this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, serverSocketChannel);
                  this.nioServerHandler = new NioServerHandler();
                  this.nioServerHandler.start();
              }

              public void start() {
                  Thread serverConnector = new Thread(new Runnable() {
                      @Override
                      public void run() {
                          while (true) {
                              try {
                                  if (NioServerConnector.this.selector.select() > 0) {
                                      Set<SelectionKey> selectionKeys = NioServerConnector.this.selector.selectedKeys();
                                      Iterator<SelectionKey> iterator = selectionKeys.iterator();
                                      while (iterator.hasNext()) {
                                          SelectionKey key = iterator.next();
                                          try {
                                              if (key.isAcceptable()) {
                                                  //新連接接入
                                                  SocketChannel socketChannel = ((ServerSocketChannel) key.attachment()).accept();
                                                  socketChannel.configureBlocking(false);
                                                  //把新連接交給serverHandler
                                                  NioServerConnector.this.nioServerHandler.register(socketChannel);
                                              }
                                          } finally {
                                              iterator.remove();
                                          }
                                      }
                                  }
                              } catch (IOException e) {

                              }
                          }
                      }
                  });
                  serverConnector.start();
              }
          }
          /**
           * 歡迎關(guān)注公眾號(hào)“種代碼“,獲取博主微信深入交流
           * @author wangjianxin
           */

          public class NioServerHandler {
              private final Selector selector;

              private final BlockingQueue<SocketChannel> prepareForRegister = new LinkedBlockingDeque<>();

              public NioServerHandler() throws IOException {
                  this.selector = Selector.open();
              }

              public void register(SocketChannel socketChannel) {
                  //這里為什么不直接注冊(cè)呢,因?yàn)楫?dāng)有線程在selector上select時(shí),register操作會(huì)阻塞
                  //從未注冊(cè)過(guò)channel時(shí),start方法中的線程會(huì)一直阻塞,在這里調(diào)用register的線程也會(huì)一直阻塞
                  //所以我們把待注冊(cè)的channel放入隊(duì)列中,并且換醒start方法中的線程,讓start方法中的線程去注冊(cè)

                  //放入待注冊(cè)隊(duì)列
                  try {
                      this.prepareForRegister.put(socketChannel);
                  } catch (InterruptedException e) {

                  }
                  //喚醒阻塞在selector上的線程(即下面start方法中創(chuàng)建的線程)
                  this.selector.wakeup();

              }

              public void start() {
                  Thread serverHandler = new Thread(new Runnable() {
                      @Override
                      public void run() {
                          try {
                              while (true) {
                                  //只需要1個(gè)線程就可以監(jiān)視所有連接
                                  //當(dāng)select方法返回值大于0時(shí),說(shuō)明注冊(cè)到selector的Channels有我們感興趣的事件發(fā)生
                                  //返回值代表有多少Channel發(fā)生了我們感興趣的事件
                                  if (NioServerHandler.this.selector.select() > 0) {
                                      //緊接著調(diào)用selectedKeys方法獲取發(fā)生事件的Key集合
                                      Set<SelectionKey> selectionKeys = NioServerHandler.this.selector.selectedKeys();
                                      Iterator<SelectionKey> iterator = selectionKeys.iterator();
                                      //遍歷Key集合,處理Channel io事件
                                      while (iterator.hasNext()) {
                                          SelectionKey key = iterator.next();
                                          try {
                                              if (key.isReadable()) {
                                                  SocketChannel socketChannel = (SocketChannel) key.attachment();
                                                  ByteBuffer buffer = ByteBuffer.allocate(1024);
                                                  int readLength = socketChannel.read(buffer);
                                                  buffer.flip();
                                                  System.out.println(new String(buffer.array(), 0, readLength));
                                                  socketChannel.write(ByteBuffer.wrap(("hello, " + new String(buffer.array(), 0, readLength)).getBytes()));
                                              }
                                          } finally {
                                              iterator.remove();
                                          }
                                      }
                                  }
                                  SocketChannel socketChannel;
                                  while ((socketChannel = NioServerHandler.this.prepareForRegister.poll()) != null) {
                                      //注冊(cè)待注冊(cè)的channel,感興趣事件為OP_READ(即可讀事件)
                                      socketChannel.register(NioServerHandler.this.selector, SelectionKey.OP_READ, socketChannel);
                                  }
                              }
                          } catch (IOException e) {

                          }
                      }
                  });
                  serverHandler.start();
              }
          }


          和BIO服務(wù)端類似,NIO服務(wù)端也有3個(gè)類,分別是HelloNioServer、NioServerConnector和NioServerHandler。


          • HelloNioServer:?jiǎn)?dòng)類,創(chuàng)建一個(gè)ServerSocketChannel,將Channel設(shè)置為非阻塞的,綁定到8000端口,交給Connector處理。到這里我們應(yīng)該明白了為什么NIO是none blocking io,這里比BIO多了一步操作,即將Channel設(shè)置為非阻塞的。具體哪里體現(xiàn)出了非阻塞,我們繼續(xù)往下看。

          • NioServerConnector:處理新連接的類,該類接收一個(gè)ServerSocketChannel,創(chuàng)建一個(gè)Selector,并向Selector注冊(cè)Channel,感興趣事件為OP_ACCEPT(即新連接接入),并創(chuàng)一個(gè)NioServerHandler的實(shí)例。NioServerConnector的start方法中創(chuàng)建一個(gè)線程,循環(huán)向selector詢問(wèn)是否有新連接接入,一旦發(fā)現(xiàn)有新連接接入,就把新連接交給NioServerHandler處理。這里與BIOServerConnector中不同的是,有新連接接入時(shí),不必再創(chuàng)建一個(gè)新的Handler,而是所有連接共用一個(gè)Handler。

          • NioServerHandler:處理連接數(shù)據(jù)上類,該類start方法中創(chuàng)建一個(gè)線程循環(huán)向Selector詢問(wèn)是否有可讀事件發(fā)生。一旦某些連接上有可讀事件發(fā)生,就讀取這些連接上的數(shù)據(jù),并為該數(shù)據(jù)添加上“Hello, ”再發(fā)送回去。然后再處理新的連接注冊(cè),將新連接注冊(cè)到Selector上,感興趣事件為OP_READ(即可讀事件)。與BioServerHandler不同的是BioServerHandler中一個(gè)線程只能處理一條連接,而NioServerHandler中一個(gè)線程可以處理多條連接。


          好了,至此我們已經(jīng)看到了NIO的非阻塞體現(xiàn)在socketChannel.read()方法是非阻塞的,而BIO的阻塞體現(xiàn)在inputstream.read()方法是阻塞的。


          2.2 NIO客戶端


          /**
           * 歡迎關(guān)注公眾號(hào)“種代碼“,獲取博主微信深入交流
           *
           * @author wangjianxin
           */

          public class HelloNioClient {
              private static final int CLIENTS = 2;

              public static void main(String[] args) throws IOException {
                  Thread client = new Thread(new Runnable() {
                      final Selector selector = Selector.open();
                      final SocketChannel[] clients = new SocketChannel[CLIENTS];

                      @Override
                      public void run() {
                          //創(chuàng)建兩個(gè)客戶端
                          for (int i = 0; i < CLIENTS; i++) {
                              try {
                                  //連接8000端口
                                  SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(8000));
                                  //將channel設(shè)置為非阻塞的
                                  socketChannel.configureBlocking(false);
                                  //注冊(cè)到selector
                                  socketChannel.register(this.selector, SelectionKey.OP_READ, socketChannel);
                                  //保存channel
                                  clients[i] = socketChannel;
                              }catch (Throwable e){

                              }
                          }
                          for (int i = 0; i < Integer.MAX_VALUE; i++) {
                              try {
                                  //向服務(wù)端發(fā)送“zhongdaima" + 客戶端編號(hào)
                                  for (int j = 0; j < clients.length; j++) {
                                      this.clients[j].write(ByteBuffer.wrap(("zhongdaima" + j).getBytes()));
                                  }
                                  //監(jiān)視Channel是否有可讀事件發(fā)生
                                  if (this.selector.select() > 0) {
                                      Set<SelectionKey> selectionKeys = this.selector.selectedKeys();
                                      Iterator<SelectionKey> iterator = selectionKeys.iterator();
                                      while (iterator.hasNext()){
                                          SelectionKey key = iterator.next();
                                          try {
                                              SocketChannel channel = (SocketChannel) key.attachment();
                                              ByteBuffer buffer = ByteBuffer.allocate(1024);
                                              int read = channel.read(buffer);
                                              buffer.flip();
                                              //打印服務(wù)端返回?cái)?shù)據(jù)
                                              System.out.println(new String(buffer.array(), 0, read));
                                          }finally {
                                              iterator.remove();
                                          }
                                      }
                                  }
                                  Thread.sleep(1000);
                              }catch (Throwable e){

                              }
                          }
                      }
                  });
                  client.start();

              }
          }


          NIO客戶端只有一個(gè)類HelloNioClient,其中創(chuàng)建一個(gè)線程、兩條連接,循環(huán)向服務(wù)端發(fā)送“zhongdaima" + 客戶端編號(hào),然后向Selctor循問(wèn)是否有可讀事件發(fā)生,一旦有可讀事件發(fā)生,就讀取數(shù)據(jù),并打印。與HelloBioClient不同的是HelloNioClient創(chuàng)建了兩條連接,卻只使用了一個(gè)線程,而HelloBioClient中創(chuàng)建了兩條連接,使用了兩個(gè)線程。



          -      相比 BIO,NIO 有哪些優(yōu)勢(shì)?    -


          讀到這里,大家仔細(xì)品品NIO比BIO的優(yōu)勢(shì)在哪里。優(yōu)勢(shì)在哪里呢,要首先看看有什么區(qū)別,上面的代碼具體有什么區(qū)別我都加粗表示了,看完之后第一感覺應(yīng)該就是NIO比BIO節(jié)省線程。


          3.1 BIO模型

          這是BIO的示意圖,比較簡(jiǎn)單,每條連接都需要一個(gè)線程來(lái)處理,因?yàn)闊o(wú)法得得連接中什么時(shí)候有數(shù)據(jù)可以讀取,只能傻傻等待。


          3.2 NIO模型


          這是NIO的示意圖,與BIO相比,其中多了一個(gè)組件Selector。正是由于Selector的存在讓NIO與BIO產(chǎn)生了本質(zhì)的不同。BIO中線程直接阻塞在1條連接上,直到有數(shù)據(jù)可讀取才返回,而且NIO中線程首先阻塞在Selector上,而Selector上可以注冊(cè)多條連接。


          線程調(diào)用select方法向Selector詢問(wèn)是否有感興趣的事件發(fā)生,阻塞在select方法上,直接到1條或者多條連接上有事件發(fā)生才返回。此時(shí)線程已經(jīng)知道哪些連接上有事件發(fā)生了,于是去處理這些連接上的事件。處理完成之后再次阻塞在Selector的select方法上,如此往復(fù)。


          至此我們已經(jīng)發(fā)現(xiàn),BIO和NIO的本質(zhì)不同在于中間多了一層代理Selector,而Selector具備監(jiān)視多條連接的能力。


          3.3 舉個(gè)例子


          開一家BIO模型的飯店,飯店里只有1個(gè)廚師(相當(dāng)于Thread),有1位顧客(相當(dāng)于連接)來(lái)吃飯,廚師就一直為這1位顧客做飯,直到這個(gè)顧客結(jié)賬走了(連接關(guān)閉),廚師才開始為下1位顧客做飯。如果需要同時(shí)滿足10個(gè)顧客吃飯,就要10個(gè)廚師


          開一家NIO模型的飯店,飯店里有1個(gè)廚師(相當(dāng)于Thread),還有1個(gè)服務(wù)員(相當(dāng)于Selector),有10位顧客來(lái)吃飯,服務(wù)員就為這10位顧客點(diǎn)餐(向Selector注冊(cè)),并且需要知道顧客你們都點(diǎn)什么菜(向Selector注冊(cè)時(shí)的興趣事件)。廚師問(wèn)服務(wù)員顧客都點(diǎn)了什么菜(Selector.select()),開始做菜,做完菜之后再問(wèn)服務(wù)員顧客們又點(diǎn)了什么菜,如此往復(fù)。只需要1個(gè)廚師、1個(gè)服務(wù)員就可以為多個(gè)顧客提供服務(wù)。


          很顯然,如果你開飯店,你是開BIO飯店呢,還是NIO飯店呢。



          -      總結(jié)    -


          NIO的非阻塞體現(xiàn)在socketChannel.read()方法是非阻塞的,而BIO的阻塞體現(xiàn)在inputstream.read()方法是阻塞的。


          NIO一個(gè)線程可以處理多條連接,而BIO一個(gè)線程只能處理一條連接,NIO更節(jié)省線程資源。


          作者:王建新,轉(zhuǎn)轉(zhuǎn)架構(gòu)部資深Java工程師,主要負(fù)責(zé)服務(wù)治理、RPC框架、分布式調(diào)用跟蹤、監(jiān)控系統(tǒng)等。

          來(lái)源公眾號(hào):種代碼

          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中文人妻av | 免费国产污网站 | 想看操逼视频 | 亚洲成人影片在线免费看 | 亚洲成人免费高清无码 |