• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • 網絡編程從BIO到NIO

    標簽: Java基礎

    ServerSocket是一個BIO操作,BIO,blocking I/O。即阻塞IO,為什么說是阻塞的呢?在哪里阻塞呢?阻塞了什么呢?

    BIO使用:

    • 單線程使用ServerSocket
    1. 創建服務端
      package bio;
      
      import java.io.InputStream;
      import java.net.ServerSocket;
      import java.net.Socket;
      
      /**
       * BIO服務器。
       *
       */
      public class BIOServer {
      
          public static void main(String[] args) throws Exception{
              // ServerSocket就是一個阻塞的IO
              ServerSocket serverSocket = new ServerSocket(8899);
      
              while (true) {
                  System.out.println("waiting connection");
                  Socket socket = serverSocket.accept();
                  System.out.println("one client connection");
                  InputStream socketInputStream = socket.getInputStream();
                  System.out.println("waiting receive");
                  byte[] bytes = new byte[1024];
                  socketInputStream.read(bytes);
                  System.out.println("receive:"+new String(bytes));
              }
      
          }
      }

       

    2. 創建兩個客戶端,用來連接服務端
      package bio;
      
      import java.io.OutputStream;
      import java.net.Socket;
      import java.util.Scanner;
      
      /**
       *
       * 客戶端1
       *
       */
      public class Client1 {
      
          public static void main(String[] args) throws Exception{
              // 連接上服務端
              Socket socket = new Socket("localhost", 8899);
              OutputStream outputStream = socket.getOutputStream();
              Scanner scanner = new Scanner(System.in);
              while (scanner.hasNext()) {
                  String nextLine = scanner.nextLine();
                  // 像服務端發送數據
                  outputStream.write(nextLine.getBytes());
              }
          }
      }
      package bio;
      
      import java.io.OutputStream;
      import java.net.Socket;
      import java.util.Scanner;
      
      /**
       *
       * 客戶端2
       *
       */
      public class Client2 {
      
          public static void main(String[] args) throws Exception{
              // 連接上服務端
              Socket socket = new Socket("localhost", 8899);
              OutputStream outputStream = socket.getOutputStream();
              Scanner scanner = new Scanner(System.in);
              while (scanner.hasNext()) {
                  String nextLine = scanner.nextLine();
                  // 像服務端發送數據
                  outputStream.write(nextLine.getBytes());
              }
          }
      }

       

    3. 啟動服務端,觀察服務端BIOServer控制臺輸出。(控制臺打印waiting connection,并沒有one client connection,此時程序阻塞在Socket socket = serverSocket.accept();這里,等待客戶端連接。)
    4. 啟動一個客戶端1,觀察服務端BIOServer控制臺輸出。(控制臺繼續打印,但沒有打印出receive:,此時程序阻塞在socketInputStream.read(bytes);等待客戶端1的輸出內容。)
    5. 在客戶端1的控制臺中輸入幾個內容,觀察服務端控制臺輸出。(我在客戶端1的控制臺輸入了hello world,此時服務端接收到內容,并打印到控制臺,此時。一個while循環結束,繼續下一個循環,所有繼續打印了waiting connection)
    6. 清空服務端的控制臺內容,關閉客戶端1的服務然后再將其啟動,啟動完成之后,再啟動客戶端2,在服務端2的控制臺輸入:hello world.

       

    7. 觀察服務端的控制臺,并沒有打印出hello world。為什么呢?其實,當客戶端1連接上來之后,當前循環的代碼已經執行到了socketInputStream.read(bytes);并阻塞在這里等待客戶端1發來內容。于是,客戶端2的連接和輸出都被阻塞了。在客戶端1控制臺輸入內容,此時再觀察服務端控制臺內容:

    發現問題:ServerSocket會在兩個方法阻塞,一個是accept(),一個是read()。從步驟6中可以發現,如果在單線程中使用ServerSocket,一次只能與一個客戶端完成連接,如果此時這個已經連接的客戶端沒有發送任何數據,就會一直阻塞在read方法,其他的客戶端的連接也會被阻塞了。

    解決問題:為了使ServerSocket能夠在一個客戶端連接上來之后,繼續連接上其他的客戶端,可以使用多線程解決問題。一個客戶端連接上來之后,開辟一個線程去等待該客戶端的內容,主線程還能繼續處理其他客戶端的連接。

    • 多線程使用ServerSocket
    package bio;
    
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * BIO服務器。使用多線程。一個連接對應一個線程
     *
     */
    public class BIOServer2 {
    
        public static void main(String[] args) throws Exception{
            // ServerSocket就是一個阻塞的IO
            ServerSocket serverSocket = new ServerSocket(8899);
    
            while (true) {
                System.out.println("waiting connection");
                Socket socket = serverSocket.accept();
    
                new Thread(()->{
                    try {
                        System.out.println("one client connection");
                        InputStream socketInputStream = socket.getInputStream();
                        System.out.println("waiting receive");
                        byte[] bytes = new byte[1024];
                        socketInputStream.read(bytes);
                        System.out.println("receive:"+new String(bytes));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }).start();
            }
    
        }
    }

    啟動服務端,先啟動客戶端1,再啟動客戶端2,再客戶端2的控制臺輸入數據,此時觀察服務端控制臺輸出:

    此時解決了使用單線程造成連接阻塞的問題。

    發現問題:多線程雖然能夠解決多個連接的問題,但每一個連接都會開啟一個線程,這樣不僅會產生太多的線程資源,即使使用線程池也會造成線程資源的浪費。一般客戶端連接是有兩個步驟的,一個是連接,一個是發送數據。假設有10000個連接上來了,但只有100個連接會發送數據,這樣就會造成線程去處理9900個連接的浪費。

    思考:多線程解決了連接阻塞的問題,但是增加了線程資源。單線程呢,沒有線程資源的浪費,但又會阻塞連接。要是能夠在單線程下,不產生阻塞就好了。即serverSocket.accept()和socketInputStream.read(bytes)兩個方法不會阻塞。

    // 提供一個api,設置這里不要阻塞
    Socket socket = serverSocket.accept();
    
    // 提供一個api,設置這里不要阻塞
    socketInputStream.read(bytes);

    解決問題:ServerSocket是JDK提供的類,我們不能修改源代碼。好在對于上面的問題,JDK的開發人員早已經發現了問題,并提供了SocketChannel,實現類不阻塞的功能。可以把SocketChannel就理解成是一個ServerSocket,在其基礎上解決了阻塞的問題。(畢竟ServerSocket已經被廣泛使用,不能直接修改它的源代碼,那就新寫一個類)

    NIO使用:

    概念:NIO不再使用ServerSocket、Socket,引出了ServerSocketChannel、SocketChannel。ServerSocketChannel對應ServerSocket,SocketChannel對應ServerSocket。

    package bio;
    
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    
    /**
     *
     * NIO服務器,ServerSocketChannel
     *
     */
    public class NIOServer {
    
        public static void main(String[] args) throws Exception{
            // 下面這兩個相當于ServerSocket serverSocket = new ServerSocket(8899);
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8899));
    
            // 設置不阻塞,相當于設置serverSocket.accept()不會阻塞
            // 不設置false,接收連接還是阻塞的
            serverSocketChannel.configureBlocking(false);
    
            while (true) {
                // serverSocket.accept()
                SocketChannel socketChannel = serverSocketChannel.accept();
    
                System.out.println("等待客戶端連接:"+socketChannel);
    
                if (socketChannel!=null) {
                    System.out.println("客戶端連接成功,等待接收數據...");
                    // 設置不阻塞,相當于設置socketInputStream.read(bytes)不會阻塞
                    // 不設置false,讀取數據還是阻塞的
                    socketChannel.configureBlocking(false);
    
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int read = socketChannel.read(byteBuffer);
    
                    if (read>0) {
                        byteBuffer.flip();
                        System.out.println("接收客戶端數據:"+byteBuffer.toString());
                    }
                }
            }
    
        }
    
    }

    (特別注意serverSocketChannel.configureBlocking(false); socketChannel.configureBlocking(false);)

    1. 運行服務端,觀察控制臺輸出:

      在沒有客戶端連接的時候,serverSocketChannel.accept()會返回null,不像serverSocket.accept()是一直阻塞等待。

    2. 注釋掉代碼:System.out.println("等待客戶端連接:"+socketChannel); (輸出太多,影響觀感),重啟服務端,然后運行客戶端1,觀察服務端控制臺輸出。

      服務端接收到客戶端的請求,其實此時的代碼并沒有阻塞在等待客戶端1的內容,而是一直在一次次循環中。

    3. 在客戶端1輸入內容發送給服務端,觀察服務端控制臺輸出。

      客戶端1發送的內容呢?服務端并沒有打印出來?這是為什么呢。其實,看看服務端代碼,就能發現,在接收到客戶端1的連接之后,代碼繼續運行,到了下一個循環里,此時serverSocketChannel.accept()==null了,相當于把客戶端1的連接信息覆蓋掉了,導致客戶端1的連接信息丟失。

    4. 解決問題:需要有一個集合能夠存儲每一次連接,這樣不會弄丟以前的連接信息。

      package bio;
      
      import java.net.InetSocketAddress;
      import java.nio.ByteBuffer;
      import java.nio.channels.ServerSocketChannel;
      import java.nio.channels.SocketChannel;
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       *
       * NIO服務器,ServerSocketChannel
       *
       */
      public class NIOServer2 {
      
          // 創建一個集合,用于記錄每一個連接信息
          private static List<SocketChannel> socketChannelList = new ArrayList<>();
      
          public static void main(String[] args) throws Exception{
              // 下面這兩個相當于ServerSocket serverSocket = new ServerSocket(8899);
              ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
              serverSocketChannel.bind(new InetSocketAddress(8899));
      
              // 設置不阻塞,相當于設置serverSocket.accept()不會阻塞
              // 不設置false,接收連接還是阻塞的
              serverSocketChannel.configureBlocking(false);
      
              while (true) {
                  // serverSocket.accept()
                  SocketChannel socketChannel = serverSocketChannel.accept();
      
                  if (socketChannel!=null) {
                      // 客戶端連接成功了,加入集合中
                      socketChannelList.add(socketChannel);
                  }
                  // 遍歷集合,看看各個客戶端是否發送了數據
                  for (SocketChannel channel : socketChannelList) {
                      // 設置不阻塞,相當于設置socketInputStream.read(bytes)不會阻塞
                      // 不設置false,讀取數據還是阻塞的
                      channel.configureBlocking(false);
      
                      ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                      int read = channel.read(byteBuffer);
      
                      if (read>0) {
                          byteBuffer.flip();
                          System.out.println("接收客戶端數據:"+new String(byteBuffer.array()));
                      }
                  }
              }
      
          }
      
      }

       

    5. 依次啟動服務端、客戶端1、客戶端2;用客戶端2發送數據,客戶端1發送數據,觀察服務端控制臺輸出。

      可以發現,解決了連接丟失的問題。

     

    發現問題:ServerSocketChannel解決了ServerSocket阻塞的問題,使用單線程也能處理了多個連接的問題。可是,上面的代碼就可以了嗎?并沒有。在上面的代碼里,我使用了一個集合去記錄所有的客戶端連接,然后一遍遍循環集合看看客戶端是否有內容。想想假設有10000個客戶端連接了,集合里面就有10000個客戶端數據,然而只有100個客戶端會發送數據,這樣在遍歷集合的時候,其實只有100個是有效的數據,其余的都是無效的遍歷。這樣是不是也會造成資源的浪費了。

    解決問題:這些問題,JDK的開發人員也幫我們解決了,于是就引出了“IO多路復用”的概念了,其中有包括“select”、“poll”、“epoll”,可以說多路復用解決的就是循環遍歷造成的資源浪費問題。關于多路復用,會另起一篇來寫的。

     

    關于BIO到NIO就到這里了吧!

    版權聲明:本文為weixin_50518271原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/weixin_50518271/article/details/108984343

    智能推薦

    從IO-BIO-NIO-AIO-到Netty

    文章目錄 IO 操作系統層面 IO的多路復用 epoll BIO NIO NIO單線程模型 NIO-reactor模式 AIO Netty 同步-異步-阻塞-非阻塞 IO 操作系統層面 一個應用程序進行IO時,需要系統內核的參與,發送syscall指令產生中斷。 發生中斷意味著需要操作系統介入,開展管理工作。由于操作系統的管理工作(比如切換線程、分配I/O設備等),需要使用特權指令,因此CPU要從...

    Java 性能優化之——從BIO 到 NIO,再到 AIO

    Netty 的高性能架構,是基于一個網絡編程設計模式 Reactor 進行設計的。現在,大多數與 I/O 相關的組件,都會使用 Reactor 模型,比如 Tomcat、Redis、Nginx 等,可見 Reactor 應用的廣泛性。 Reactor 是 NIO 的基礎。為什么 NIO 的性能就能夠比傳統的阻塞 I/O 性能高呢?首先來看一下傳統阻塞式 I/O 的一些特點   阻塞 I/...

    BIO/NIO/AIO編程

    BIO 編程 Blocking IO: 同步阻塞的編程方式。 BIO 編程方式通常是在 JDK1.4 版本之前常用的編程方式。編程實現過程為:首先在服務端啟動一個 ServerSocket 來監聽網絡請求,客戶端啟動 Socket 發起網絡請求,默認情況下ServerSocket 回建立一個線程來處理此請求,如果服務端沒有線程可用,客戶端則會阻塞等待或遭到拒絕。 且建立好的連接,在通訊過程中,是同...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    猜你喜歡

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    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 以上述例子,判斷一個生產出...

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