• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • NIO與Netty-1-BIO

    標簽: NIO與Netty  java  socket  nio  netty

    NIO與Netty-1-BIO

    • BIO是同步阻塞IO,在服務端獲取等待連接時需要阻塞,且每個連接的socket去獲取客戶端傳來的數據也需要阻塞,因此需要為每個連接都需要建立一個線程來處理,所以線程過多性能較低。

    • 示例

      • //一個BIO的服務器
        public static void main(String[] args) throws Exception {
            ServerSocket serverSocket = new ServerSocket(6666);
            System.out.println("服務器啟動了");
            while (true) {
                Socket socket = serverSocket.accept();//此方法無連接會阻塞
                System.out.println("連接到一個客戶端");
                Thread connect=new Thread(new Runnable() {
                    //創建一個線程來處理此連接
                    public void run() {
                        handlerConnect(socket);//連接處理函數
                    }
                });
                connect.run();
            }
        }
        public static void handlerConnect(Socket socket) {
            try {
                byte[] bytes = new byte[1024];
                //通過socket 獲取輸入流
                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 (Exception e) {
                e.printStackTrace();
            }finally {
                System.out.println("關閉和client的連接");
                try {
                    socket.close();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    • BIO阻塞模型

      • 在這里插入圖片描述
      • BIO存在一個負責監聽建立連接事件的線程,在有新連接時便創建一個線程來處理此連接。
      • 因此連接數將等于處理連接的線程數。
      • 因為是阻塞式IO,單線程情況下,處理者線程可能阻塞在其中一個套接字的read上,導致另一個套接字即使準備好了數據也無法處理,這個時候解決的方法就只能是針對每一個套接字,都新建一個線程處理其數據讀取。
    • ServerSocket源碼解讀

      • ServerSocket創建流程

        • //以ServerSocket(int port)構造器為例
          ServerSocket serverSocket = new ServerSocket(6666);
        • //ServerSocket(int port)構造方法
          public ServerSocket(int port) throws IOException {
              this(port, 50, null);
              //調用ServerSocket(int port, int backlog, InetAddress bindAddr)
          }
          public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
              setImpl();//*設置serverSocket實例的SocketImpl屬性
              /**代碼省略-port、baklog校驗**/
              try {
                  bind(new InetSocketAddress(bindAddr, port), backlog);//*綁定端口
              } 
              /**代碼省略-異常處理**/
          }
        • ServerSocket.setImpl()

          • private void setImpl() {
                if (factory != null) {//此時factory為null
                    /**代碼省略-利用工廠創建impl**/
                } else {//執行此代碼塊
                    impl = new SocksSocketImpl();//創建一個新的SocketImpl設置上去
                }
                if (impl != null)
                    impl.setServerSocket(this);//將剛剛創建的impl與此serverSocket綁定
            }
        • ServerSocket.bind(SocketAddress endpoint, int backlog)

          • public void bind(SocketAddress endpoint, int backlog) throws IOException {
                /**代碼省略-校驗拋異常**/
                InetSocketAddress epoint = (InetSocketAddress) endpoint;
                /**代碼省略-endpoint校驗**/
                try {
                    SecurityManager security = System.getSecurityManager();
                    if (security != null)
                        security.checkListen(epoint.getPort());
                    //為此ServerSocket的SocketImpl綁定監聽的地址和端口
                    getImpl().bind(epoint.getAddress(), epoint.getPort());
                    //為impl設置為監聽模式
                    getImpl().listen(backlog);
                    bound = true;
                }
                /**代碼省略-異常處理**/
            }
        • 注意盡管調用了setImpl但第一次調用getImpl()方法時還會對impl進行一些設置,例如獲取文件描述符fd。getImpl->createImpl->impl.create->獲取文件描述符。

        • impl的bind與listen分別在jvm層調用了操作系統的**bind()listen()**函數。

      • ServerSocket.accept()解讀

        • //通過此方法建立連接,并返回連接對應的socket
          Socket socket = serverSocket.accept();
        • 此方法為阻塞的,若沒有連接到來則一直阻塞,有連接到來才返回新建立的socket。

        • //ServerSocket.accept()
          public Socket accept() throws IOException {
              /**代碼省略-校驗拋異常**/
              Socket s = new Socket((SocketImpl) null);//創建新的socket對象
              implAccept(s);//!!!!!!!!!!!!
              return s;
          }
          • ServerSocket.implAccept(Socket s)

            • protected final void implAccept(Socket s) throws IOException {
                  SocketImpl si = null;
                  try {
                      if (s.impl == null)//此時s.impl為null
                          s.setImpl();//為s設置一個新的impl
                      else {
                          s.impl.reset();
                      }
                      si = s.impl;
                      s.impl = null;
                      si.address = new InetAddress();
                      si.fd = new FileDescriptor();
                      getImpl().accept(si);//通過s的新impl去執行具體的accpet邏輯
                      SocketCleanable.register(si.fd);
                     	/**代碼省略-權限檢查**/
                  }
                  /**代碼省略-異常處理**/
                  s.impl = si;
                  s.postAccept();
              }
            • getImpl().accept(SocketImpl s)

              • protected void accept(SocketImpl s) throws IOException {
                    acquireFD();
                    try {
                        socketAccept(s);//具體的accept方法
                    } finally {
                        releaseFD();
                    }
                }
              • PlainSocketImpl.socketAccept(SocketImpl s)

                • void socketAccept(SocketImpl s) throws IOException {
                      int nativefd = checkAndReturnNativeFD();
                      /**代碼省略**/
                      int newfd = -1;
                      InetSocketAddress[] isaa = new InetSocketAddress[1];
                      if (timeout <= 0) {//無超時時間的accept
                          newfd = accept0(nativefd, isaa);
                          //調用jvm中的accept方法
                          //jvm調用操作系統的accept方法獲取連接
                          //返回新連接對應的文件描述符
                      } else {//含有超時時間的accept
                          configureBlocking(nativefd, false);
                          try {
                              waitForNewConnection(nativefd, timeout);
                              newfd = accept0(nativefd, isaa);
                              //調用jvm中的accept方法
                              if (newfd != -1) {
                                  configureBlocking(newfd, true);
                              }
                          } finally {
                              configureBlocking(nativefd, true);
                          }
                      }
                      /* 將s的fd設置為獲取到的連接的文件描述符 */
                      fdAccess.set(s.fd, newfd);
                      /* 設置建立的新連接的端口號,ip地址等 */
                      InetSocketAddress isa = isaa[0];
                      s.port = isa.getPort();
                      s.address = isa.getAddress();
                      s.localport = localport;
                      /**代碼省略**/
                  }
                • accept0方法調用的jvm本地方法最終是通過調用操作系統的**accept()**函數實現的。

    • Socket中InputStream解讀

      • 每個連接的Socket是由ServerSocket.accpet()返回來的,若想讀取連接中客戶端發送的數據,則需要獲取InputStream,并調用其read方法。

      • InputStream獲取流程

        • protected synchronized InputStream getInputStream() throws IOException {
              synchronized (fdLock) {
                  /**代碼省略-校驗拋異常**/
                  if (socketInputStream == null)
                      socketInputStream = new SocketInputStream(this);
              }
              return socketInputStream;
          }
          • new SocketInputStream(this)

            • SocketInputStream(AbstractPlainSocketImpl impl) throws IOException {
                  super(impl.getFileDescriptor());//設置文件描述符
                  this.impl = impl;//設置此流對應的impl
                  socket = impl.getSocket();//設置對應的socket
              }
      • SocketInputStream.read(byte b[])

        • public int read(byte b[]) throws IOException {
              return read(b, 0, b.length);
          }
          public int read(byte b[], int off, int length) throws IOException {
              return read(b, off, length, impl.getTimeout());
          }
          int read(byte b[], int off, int length, int timeout) throws IOException {
              int n;
              if (eof) {//連接已經結束
                  return -1;
              }
          	/**代碼省略-校驗拋異常**/
              FileDescriptor fd = impl.acquireFD();//獲取對應的impl的文件描述符
              try {
                  n = socketRead(fd, b, off, length, timeout);//真正的read函數
                  if (n > 0) {
                      return n;
                  }
              } catch (ConnectionResetException rstExc) {
                  impl.setConnectionReset();
              } finally {
                  impl.releaseFD();
              }
              /**代碼省略-校驗拋異常**/
              eof = true;
              return -1;
          }
          • socketRead(fd, b, off, length, timeout)

            • private int socketRead(FileDescriptor fd,
                                     byte b[], int off, int len,
                                     int timeout) throws IOException {
                  return socketRead0(fd, b, off, len, timeout);//調用jvm本地方法
              }
            • 調用的jvm本地方法最終也是通過操作系統的**recv()**函數來實現read的。

    • 對應的操作系統方法

      • java的socket編程其實底層也是對應的c/c++對應的那套操作系統的socket編程方法。

      • 作用 java方法 操作系統方法
        創建socket獲得fd impl.socketCreate(boolean isServer) socket()
        將host,port綁定到socket上 impl.bind(InetAddress host, int port) bind()
        將socket設置為監聽狀態 impl.listen(int backlog) listen()
        服務端socket獲取連接 impl.accept(SocketImpl s) accept()
        連接socket讀取收到的內容 socketInputStream.read(byte b[]) recv()
      • 操作系統方法詳解

        • socket() https://baike.baidu.com/item/socket%28%29
          bind() https://baike.baidu.com/item/bind%28%29
          listen() https://baike.baidu.com/item/listen%28%29
          accept() https://baike.baidu.com/item/accept%28%29
          recv() https://baike.baidu.com/item/recv%28%29
    版權聲明:本文為hu853996234原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/hu853996234/article/details/104253639

    智能推薦

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    統計學習方法 - 樸素貝葉斯

    引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...

    styled-components —— React 中的 CSS 最佳實踐

    https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...

    基于TCP/IP的網絡聊天室用Java來實現

    基于TCP/IP的網絡聊天室實現 開發工具:eclipse 開發環境:jdk1.8 發送端 接收端 工具類 運行截圖...

    猜你喜歡

    19.vue中封裝echarts組件

    19.vue中封裝echarts組件 1.效果圖 2.echarts組件 3.使用組件 按照組件格式整理好數據格式 傳入組件 home.vue 4.接口返回數據格式...

    劍指Offer39-調整數組順序使奇數位于偶數前面

    一開始想著用冒泡排序的方法來做,但是bug還是很多,后來看了評論區答案,發現直接空間換時間是最簡單的,而且和快排的寫法是類似的。...

    【一只蒟蒻的刷題歷程】【藍橋杯】歷屆試題 九宮重排 (八數碼問題:BFS+集合set)

    資源限制 時間限制:1.0s 內存限制:256.0MB 問題描述 如下面第一個圖的九宮格中,放著 1~8 的數字卡片,還有一個格子空著。與空格子相鄰的格子中的卡片可以移動到空格中。經過若干次移動,可以形成第二個圖所示的局面。 我們把第一個圖的局面記為:12345678. 把第二個圖的局面記為:123.46758 顯然是按從上到下,從左到右的順序記錄數字,空格記為句點。 本題目的任務是已知九宮的初態...

    dataV組件容器寬高發生變化后,組件不會自適應解決方法

    項目中需要大屏幕數據展示,于是使用了dataV組件,但是使用是發現拖動瀏覽器邊框,dataV組件顯示異常,如圖: 于是查了官網,官網的解釋如下:   于是按照官網的意思編寫代碼: 于是可以自適應了...

    CSS3干貨10:如何做一個板塊標題水平線左邊帶顏色效果

    很多網站在設計欄目標題的時候,喜歡用下劃線分開欄目標題和內容部分。 而且線條左邊的部分往往還有顏色,且這個顏色跟標題的文字長短保持一致。效果如圖所示: 這種效果其實很簡單。 我這里給大家推薦兩種方式: 假定我們的標題部分 HTML 結構如下: 方式一:利用下邊框。灰色部分是 h1 的下邊框,藍色部分是 span 標簽的下邊框。 h1 的高度為 40px,span 也設置它的高度為 40px。這樣,...

    精品国产乱码久久久久久蜜桃不卡