<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>

          從bio到nio到netty實現(xiàn)原理淺析

          共 20513字,需瀏覽 42分鐘

           ·

          2021-05-23 13:23

          點擊上方藍色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時間送達

          今天記錄一下這一塊的演變歷史~

          首先是我們熟悉的bio,利用原生socket進行操作。
          BIO

          public class SocketServer {
              public static void main(String[] args) {
                  try {
                      ServerSocket server = new ServerSocket(8888);
                      System.out.println("服務(wù)器已經(jīng)啟動!");
                      // 接收客戶端發(fā)送的信息
                      Socket socket = server.accept();

                      InputStream is = socket.getInputStream();
                      BufferedReader br = new BufferedReader(new InputStreamReader(is));

                      String info = null;
                      while ((info = br.readLine()) != null) {
                          System.out.println(info);
                      }

                      // 向客戶端寫入信息
                      OutputStream os = socket.getOutputStream();
                      String str = "歡迎登陸到server服務(wù)器!";
                      os.write(str.getBytes());

                      // 關(guān)閉文件流
                      os.close();
                      br.close();
                      is.close();
                      socket.close();
                      server.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }

          上面是一個最簡單的socket單線程服務(wù)器實例沒,accept()方法阻塞等待請求,請求處理完之后結(jié)束程序。大致過程如下所示。

          我們可以進一步優(yōu)化代碼,accept()接收請求之后,不在當(dāng)前線程內(nèi)部進行io處理,另外開辟新的線程進行io操作

          public class Bio {
              public static void main(String[] args) {
                      ServerSocket server = new ServerSocket(8888);
                      System.out.println("服務(wù)器已經(jīng)啟動!");
                      // 接收客戶端發(fā)送的信息
                      while(true){
                          Socket socket = null;
                          try {
                              socket = server.accept();
                          } catch (IOException e) {
                              e.printStackTrace();
                          }
                          new Thread(() ->{
                              try {
                                  InputStream is = socket.getInputStream();
                                  BufferedReader br = new BufferedReader(new InputStreamReader(is));

                                  String info = null;
                                  while ((info = br.readLine()) != null) {
                                      System.out.println(info);
                                  }
                                  // 向客戶端寫入信息
                                  OutputStream os = socket.getOutputStream();
                                  String str = "歡迎登陸到server服務(wù)器!";
                                  os.write(str.getBytes());
                              } catch (IOException e) {
                                  e.printStackTrace();
                              }
                          }).start();
                      }
              }
          }

          上面這段代碼有了部分優(yōu)化,主線程進行循環(huán)accept(),負責(zé)請求接收,工作線程采用新線程或者線程池的方式處理。這種方式的好處是,將io工作委派給新的線程,可以較為及時接收新的請求。缺點也很明顯,耗費大量的線程資源,即使使用線程池來進行管理,當(dāng)大量請求到來,線程池的隊列爆滿,程序就會崩潰。

          NIO
          NIO出現(xiàn)解決的bio的缺點問題,我們先看一下網(wǎng)上找滴一個nio的簡單例子。

          public class Nio {
              // 本地字符集
              private static final String LocalCharSetName = "UTF-8";

              // 本地服務(wù)器監(jiān)聽的端口
              private static final int Listenning_Port = 8888;

              // 緩沖區(qū)大小
              private static final int Buffer_Size = 1024;

              // 超時時間,單位毫秒
              private static final int TimeOut = 3000;

              public static void main(String[] args) throws IOException {
                  // 創(chuàng)建一個在本地端口進行監(jiān)聽的服務(wù)Socket信道.并設(shè)置為非阻塞方式
                  ServerSocketChannel serverChannel = ServerSocketChannel.open();
                  serverChannel.socket().bind(new InetSocketAddress(Listenning_Port));
                  serverChannel.configureBlocking(false);
                  // 創(chuàng)建一個選擇器并將serverChannel注冊到它上面
                  Selector selector = Selector.open();
                  //設(shè)置為客戶端請求連接時,默認客戶端已經(jīng)連接上
                  serverChannel.register(selector, SelectionKey.OP_ACCEPT);
                  while (true) {
                      // 輪詢監(jiān)聽key,select是阻塞的,accept()也是阻塞的
                      if (selector.select(TimeOut) == 0) {
                          System.out.println(".");
                          continue;
                      }
                      // 有客戶端請求,被輪詢監(jiān)聽到
                      Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
                      while (keyIter.hasNext()) {
                          SelectionKey key = keyIter.next();
                          if (key.isAcceptable()) {
                              SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
                              clientChannel.configureBlocking(false);
                              //意思是在通過Selector監(jiān)聽Channel時對讀事件感興趣
                              clientChannel.register(selector, SelectionKey.OP_READ,
                                      ByteBuffer.allocate(Buffer_Size));
                          }
                          else if (key.isReadable()) {
                            
                              SocketChannel clientChannel = (SocketChannel) key.channel();
                              // 接下來是java緩沖區(qū)io操作,避免io堵塞
                              ByteBuffer buffer = (ByteBuffer) key.attachment();
                              buffer.clear();
                              long bytesRead = clientChannel.read(buffer);
                              if (bytesRead == -1) {
                                  // 沒有讀取到內(nèi)容的情況
                                  clientChannel.close();
                              } else {
                                  // 將緩沖區(qū)準(zhǔn)備為數(shù)據(jù)傳出狀態(tài)
                                  buffer.flip();
                                  // 將獲得字節(jié)字符串(使用Charset進行解碼)
                                  String receivedString = Charset
                                          .forName(LocalCharSetName).newDecoder().decode(buffer).toString();
                                  System.out.println("接收到信息:" + receivedString);
                                  String sendString = "你好,客戶端. 已經(jīng)收到你的信息" + receivedString;
                                  buffer = ByteBuffer.wrap(sendString.getBytes(LocalCharSetName));

                                  clientChannel.write(buffer);
                                  key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                              }
                          }

                          keyIter.remove();
                      }
                  }

              }
          }


          基本操作綁定監(jiān)聽端口,然后就是開啟一個selector輪詢監(jiān)聽客戶端請求。注意,這里當(dāng)客戶端請求上來了,是不會馬上和客戶端建立連接的(此時客戶端發(fā)送的請求已經(jīng)告知selector并注冊key,并且等待),用一個key來做記錄,在輪詢處理中,發(fā)現(xiàn)服務(wù)器通道有連接key,如果是可接受key事件,ServerSocketChannel.accept(),建立連接(這里就是基于事件通知機制,不再用阻塞accept()等待,而是基于事件通知,再進行accept()),建立連接通道后,通道注冊到selector并且關(guān)注可讀事件,客戶端發(fā)送數(shù)據(jù)可以寫入到接收端的TCP緩沖區(qū)。等到寫完之后,客戶端通道出現(xiàn)可讀事件,isReadable()為true,進行讀取TCP緩沖,寫入到應(yīng)用緩沖,使用的是java的緩沖io,程序讀取信息到buffer和寫返回結(jié)果到buffer都是在main線程,都是一個線程,并沒有new 新的線程處理io。在把信息寫入緩沖區(qū)后,才會新建線程進行io操作。大體流程如下。

          nio相對bio有啥好的?總結(jié)一下。
          是基于nio類似swing那種事件驅(qū)動機制來連接客戶端的。也就是說,bio時,客戶端來連接了就是直接連接懟到端口,然后阻塞后續(xù)請求,等待上一個連接處理完。nio呢,當(dāng)有客戶端來連接時,不好意思,服務(wù)器并不給你直接懟到端口創(chuàng)建連接,而是注冊一個key到selector說我是可處理的,然后selector輪詢發(fā)現(xiàn),這里有個key是客戶端要來的意思(類似于swing的事件源),于是處理客戶端的這個請求,設(shè)置為讀操作,然后建立連接進行io操作,io是利用緩沖區(qū)做橋梁,后續(xù)再進行new Thread io操作,不耽誤selector下一次輪詢操作,這種io方式效率很高,在這里不詳細說。
          這種方式客戶端請求來一個記錄一個,類似批量操作請求,不用為沒一個請求啟用新線程進行處理,在一個main里面統(tǒng)統(tǒng)搞定,同時采用非阻塞I/O的通信方式,不要求阻塞等待I/O 操作完成即可返回,從而減少了管理I/O 連接導(dǎo)致的系統(tǒng)開銷,大幅度提高了系統(tǒng)性能。
          總的來說就是事件驅(qū)動模型和非阻塞io的結(jié)合使用。

          更新:

          1. Nio是面向緩存區(qū)操作,在read或者wirte的時候可以不阻塞,例如上面的例子,建立了通道,分配了緩沖區(qū),然后進行read。這個read數(shù)據(jù)到緩沖區(qū)的過程由操作系統(tǒng)控制,相對java程序是異步的。如果java程序read的時候客戶端發(fā)送的數(shù)據(jù)還沒有完全寫入到緩沖區(qū),那么read得到的只是部分數(shù)據(jù),這個時候read不阻塞,線程可以處理別的事情,稍后繼續(xù)讀取緩沖區(qū)中的數(shù)據(jù)。

          2. Nio同時還使用到虛擬內(nèi)存技術(shù),從網(wǎng)卡讀取的數(shù)據(jù)由DMA處理寫入到虛擬內(nèi)存,java用戶空間內(nèi)存映射到虛擬內(nèi)存地址,相當(dāng)于可以直接操作讀取虛擬內(nèi)存的數(shù)據(jù),這樣的好處是在操作系統(tǒng)內(nèi)存不足的情況也可以讀取大文件、數(shù)據(jù)。同時java用戶程序直接操作虛擬內(nèi)存,也減少了一次數(shù)據(jù)拷貝【網(wǎng)卡->內(nèi)核空間->用戶空間】,直接操作虛擬內(nèi)存,相當(dāng)于零拷貝。如果給通道分配緩沖區(qū)時,使用直接內(nèi)存,還可以更快的讀取數(shù)據(jù),java程序相當(dāng)于直接操作內(nèi)核空間內(nèi)存,零拷貝且讀取快

          Netty
          終于說到netty了,為什么有netty,首要原因就是:nio使用起來太麻煩
          netty保留的nio的特性,進行了封裝優(yōu)化

          直接上nettydemo

          public class Netty {

              public void start(int port) throws Exception
              {
                  ServerBootstrap strap = new ServerBootstrap();
                  //主線程
                  EventLoopGroup bossGroup = new NioEventLoopGroup();
                  //從線程
                  EventLoopGroup workerGroup = new NioEventLoopGroup();
                  try {
                      strap.group(bossGroup, workerGroup).
                              //主線程監(jiān)聽通道
                              channel(NioServerSocketChannel.class).
                              option(ChannelOption.SO_BACKLOG, 1024).
                              //定義從線程的handler鏈,責(zé)任鏈模式
                              childHandler(new ChannelInitializer<SocketChannel>() {
                                  @Override
                                  protected void initChannel(SocketChannel ch) throws Exception {
                                      ch.pipeline().addLast(new NettyServerHandler());
                                  }
                              });
                      ChannelFuture future=strap.bind(port).sync();
                      future.channel().closeFuture().sync();
                  }finally {
                      bossGroup.shutdownGracefully();
                      workerGroup.shutdownGracefully();
                  }

              }

              public static void main(String[] args) throws Exception {
                  System.out.println("start server");
                  new Netty().start(8000);
              }
          }

          上面是一個netty的server,相比起nio的server簡潔了非常多,netty使用Reactor模型,代碼中的bossGroup 就是一個線程池,Reactor中扮演接收請求,并且分派任務(wù)到工作線程的角色。workerGroup 就是我們的苦力干活的。workerGroup 中的線程調(diào)用我們定義好的handler、鏈進行各種業(yè)務(wù)處理,bossGroup負責(zé)請求接收。處理過程大致如下。

          netty懟nio進行了封裝,使得我們使用起來極為方便,同時netty里面的selector采用了線程池的方式進行監(jiān)聽客戶端請求,就是好多個NioEventLoop,原則上,一個端口就只是用一個NioEventLoop線程來處理客戶端請求,線程池的話有什么用我還不知道(可以監(jiān)聽多個端口,在多個服務(wù)共用netty時可以起用處?)。。。設(shè)置多個boss會不會還是資源浪費?workerGroup中,一個客戶端過來的channel就綁定在一個NioEventLoop上,單線程,按順序執(zhí)行handler鏈,channel還綁定了一個selector來處理channel的各種key。


          版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。

          本文鏈接:

          https://blog.csdn.net/qq_20597727/article/details/80789272






          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ??????

          ??長按上方微信二維碼 2 秒


          感謝點贊支持下哈 

          瀏覽 91
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  强开小嫩苞一区二区三区网站 | 人人肏屄| 韩国一级毛片 | 亚洲成人情趣大香蕉视频 | 神马午夜精品96 |