成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

開發(fā) 架構(gòu)
這段時(shí)間自己在看一些Java中BIO和NIO之類的東西,看了很多博客,發(fā)現(xiàn)各種關(guān)于NIO的概念說的天花亂墜頭頭是道,可以說是非常的完整,但是整個(gè)看下來之后,自己對(duì)NIO還是一知半解的狀態(tài),所以這篇文章不會(huì)提到很多的概念,而是站在一個(gè)實(shí)踐的角度,寫一些我自己關(guān)于NIO的見解,站在實(shí)踐過后的高度下再回去看概念,應(yīng)該對(duì)概念會(huì)有一個(gè)更好的理解。

 01 前言

這段時(shí)間自己在看一些Java中BIO和NIO之類的東西,看了很多博客,發(fā)現(xiàn)各種關(guān)于NIO的概念說的天花亂墜頭頭是道,可以說是非常的完整,但是整個(gè)看下來之后,自己對(duì)NIO還是一知半解的狀態(tài),所以這篇文章不會(huì)提到很多的概念,而是站在一個(gè)實(shí)踐的角度,寫一些我自己關(guān)于NIO的見解,站在實(shí)踐過后的高度下再回去看概念,應(yīng)該對(duì)概念會(huì)有一個(gè)更好的理解。

[[279950]]

02 實(shí)現(xiàn)一個(gè)簡易單線程服務(wù)器

要講明白BIO和NIO,首先我們應(yīng)該自己實(shí)現(xiàn)一個(gè)簡易的服務(wù)器,不用太復(fù)雜,單線程即可。

2.1 為什么使用單線程作為演示?

因?yàn)樵趩尉€程環(huán)境下可以很好地對(duì)比出BIO和NIO的一個(gè)區(qū)別,當(dāng)然我也會(huì)演示在實(shí)際環(huán)境中BIO的所謂一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)線程的狀況。

2.2 服務(wù)端

  1. public class Server { 
  2.     public static void main(String[] args) { 
  3.         byte[] buffer = new byte[1024]; 
  4.         try { 
  5.             ServerSocket serverSocket = new ServerSocket(8080); 
  6.             System.out.println("服務(wù)器已啟動(dòng)并監(jiān)聽8080端口"); 
  7.             while (true) { 
  8.                 System.out.println(); 
  9.                 System.out.println("服務(wù)器正在等待連接..."); 
  10.                 Socket socket = serverSocket.accept(); 
  11.                 System.out.println("服務(wù)器已接收到連接請(qǐng)求..."); 
  12.                 System.out.println(); 
  13.                 System.out.println("服務(wù)器正在等待數(shù)據(jù)..."); 
  14.                 socket.getInputStream().read(buffer); 
  15.                 System.out.println("服務(wù)器已經(jīng)接收到數(shù)據(jù)"); 
  16.                 System.out.println(); 
  17.                 String content = new String(buffer); 
  18.                 System.out.println("接收到的數(shù)據(jù):" + content); 
  19.             } 
  20.         } catch (IOException e) { 
  21.             // TODO Auto-generated catch block 
  22.             e.printStackTrace(); 
  23.         } 
  24.     } 

2.3 客戶端

  1. public class Consumer { 
  2.     public static void main(String[] args) { 
  3.         try { 
  4.             Socket socket = new Socket("127.0.0.1",8080); 
  5.             socket.getOutputStream().write("向服務(wù)器發(fā)數(shù)據(jù)".getBytes()); 
  6.             socket.close(); 
  7.         } catch (IOException e) { 
  8.             // TODO Auto-generated catch block 
  9.             e.printStackTrace(); 
  10.         } 
  11.     } 

2.4 代碼解析

我們首先創(chuàng)建了一個(gè)服務(wù)端類,在類中實(shí)現(xiàn)實(shí)例化了一個(gè)SocketServer并綁定了8080端口。之后調(diào)用accept方法來接收連接請(qǐng)求,并且調(diào)用read方法來接收客戶端發(fā)送的數(shù)據(jù)。最后將接收到的數(shù)據(jù)打印。

完成了服務(wù)端的設(shè)計(jì)后,我們來實(shí)現(xiàn)一個(gè)客戶端,首先實(shí)例化Socket對(duì)象,并且綁定ip為127.0.0.1(本機(jī)),端口號(hào)為8080,調(diào)用write方法向服務(wù)器發(fā)送數(shù)據(jù)。

 

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

 

2.5 運(yùn)行結(jié)果

當(dāng)我們啟動(dòng)服務(wù)器,但客戶端還沒有向服務(wù)器發(fā)起連接時(shí),控制臺(tái)結(jié)果如下:

 

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

 

當(dāng)客戶端啟動(dòng)并向服務(wù)器發(fā)送數(shù)據(jù)后,控制臺(tái)結(jié)果如下:

 

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

 

2.6 結(jié)論

從上面的運(yùn)行結(jié)果,首先我們至少可以看到,在服務(wù)器啟動(dòng)后,客戶端還沒有連接服務(wù)器時(shí),服務(wù)器由于調(diào)用了accept方法,將一直阻塞,直到有客戶端請(qǐng)求連接服務(wù)器。

03 對(duì)客戶端功能進(jìn)行擴(kuò)展

在上文中,我們實(shí)現(xiàn)的客戶端的邏輯主要是,建立Socket --> 連接服務(wù)器 --> 發(fā)送數(shù)據(jù),我們的數(shù)據(jù)是在連接服務(wù)器之后就立即發(fā)送的,現(xiàn)在我們來對(duì)客戶端進(jìn)行一次擴(kuò)展,當(dāng)我們連接服務(wù)器后,不立即發(fā)送數(shù)據(jù),而是等待控制臺(tái)手動(dòng)輸入數(shù)據(jù)后,再發(fā)送給服務(wù)端。(服務(wù)端代碼保持不變)

3.1 代碼

  1. public class Consumer { 
  2.     public static void main(String[] args) { 
  3.         try { 
  4.             Socket socket = new Socket("127.0.0.1",8080); 
  5.             String message = null
  6.             Scanner sc = new Scanner(System.in); 
  7.             message = sc.next(); 
  8.             socket.getOutputStream().write(message.getBytes()); 
  9.             socket.close(); 
  10.             sc.close(); 
  11.         } catch (IOException e) { 
  12.             // TODO Auto-generated catch block 
  13.             e.printStackTrace(); 
  14.         } 
  15.     } 

3.2 測試

當(dāng)服務(wù)端啟動(dòng),客戶端還沒有請(qǐng)求連接服務(wù)器時(shí),控制臺(tái)結(jié)果如下:

 

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

 

當(dāng)服務(wù)端啟動(dòng),客戶端連接服務(wù)端,但沒有發(fā)送數(shù)據(jù)時(shí),控制臺(tái)結(jié)果如下:

 

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

 

當(dāng)服務(wù)端啟動(dòng),客戶端連接服務(wù)端,并且發(fā)送數(shù)據(jù)時(shí),控制臺(tái)結(jié)果如下:

 

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

 

3.3 結(jié)論

從上文的運(yùn)行結(jié)果中我們可以看到,服務(wù)器端在啟動(dòng)后,首先需要等待客戶端的連接請(qǐng)求(第一次阻塞),如果沒有客戶端連接,服務(wù)端將一直阻塞等待,然后當(dāng)客戶端連接后,服務(wù)器會(huì)等待客戶端發(fā)送數(shù)據(jù)(第二次阻塞),如果客戶端沒有發(fā)送數(shù)據(jù),那么服務(wù)端將會(huì)一直阻塞等待客戶端發(fā)送數(shù)據(jù)。服務(wù)端從啟動(dòng)到收到客戶端數(shù)據(jù)的這個(gè)過程,將會(huì)有兩次阻塞的過程。這就是BIO的非常重要的一個(gè)特點(diǎn),BIO會(huì)產(chǎn)生兩次阻塞,第一次在等待連接時(shí)阻塞,第二次在等待數(shù)據(jù)時(shí)阻塞。

04 BIO

4.1 在單線程條件下BIO的弱點(diǎn)

在上文中,我們實(shí)現(xiàn)了一個(gè)簡易的服務(wù)器,這個(gè)簡易的服務(wù)器是以單線程運(yùn)行的,其實(shí)我們不難看出,當(dāng)我們的服務(wù)器接收到一個(gè)連接后,并且沒有接收到客戶端發(fā)送的數(shù)據(jù)時(shí),是會(huì)阻塞在read()方法中的,那么此時(shí)如果再來一個(gè)客戶端的請(qǐng)求,服務(wù)端是無法進(jìn)行響應(yīng)的。換言之,在不考慮多線程的情況下,BIO是無法處理多個(gè)客戶端請(qǐng)求的。

4.2 BIO如何處理并發(fā)

在剛才的服務(wù)器實(shí)現(xiàn)中,我們實(shí)現(xiàn)的是單線程版的BIO服務(wù)器,不難看出,單線程版的BIO并不能處理多個(gè)客戶端的請(qǐng)求,那么如何能使BIO處理多個(gè)客戶端請(qǐng)求呢。

其實(shí)不難想到,我們只需要在每一個(gè)連接請(qǐng)求到來時(shí),創(chuàng)建一個(gè)線程去執(zhí)行這個(gè)連接請(qǐng)求,就可以在BIO中處理多個(gè)客戶端請(qǐng)求了,這也就是為什么BIO的其中一條概念是服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接一個(gè)線程,即客戶端有連接請(qǐng)求時(shí)服務(wù)器端就需要啟動(dòng)一個(gè)線程進(jìn)行處理。

4.3 多線程BIO服務(wù)器簡易實(shí)現(xiàn)

  1. public class Server { 
  2.     public static void main(String[] args) { 
  3.         byte[] buffer = new byte[1024]; 
  4.         try { 
  5.             ServerSocket serverSocket = new ServerSocket(8080); 
  6.             System.out.println("服務(wù)器已啟動(dòng)并監(jiān)聽8080端口"); 
  7.             while (true) { 
  8.                 System.out.println(); 
  9.                 System.out.println("服務(wù)器正在等待連接..."); 
  10.                 Socket socket = serverSocket.accept(); 
  11.                 new Thread(new Runnable() { 
  12.                     @Override 
  13.                     public void run() { 
  14.                         System.out.println("服務(wù)器已接收到連接請(qǐng)求..."); 
  15.                         System.out.println(); 
  16.                         System.out.println("服務(wù)器正在等待數(shù)據(jù)..."); 
  17.                         try { 
  18.                             socket.getInputStream().read(buffer); 
  19.                         } catch (IOException e) { 
  20.                             // TODO Auto-generated catch block 
  21.                             e.printStackTrace(); 
  22.                         } 
  23.                         System.out.println("服務(wù)器已經(jīng)接收到數(shù)據(jù)"); 
  24.                         System.out.println(); 
  25.                         String content = new String(buffer); 
  26.                         System.out.println("接收到的數(shù)據(jù):" + content); 
  27.                     } 
  28.                 }).start(); 
  29.                  
  30.             } 
  31.         } catch (IOException e) { 
  32.             // TODO Auto-generated catch block 
  33.             e.printStackTrace(); 
  34.         } 
  35.     } 

4.4 運(yùn)行結(jié)果

 

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

 

 

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

 

很明顯,現(xiàn)在我們的服務(wù)器的狀態(tài)就是一個(gè)線程對(duì)應(yīng)一個(gè)請(qǐng)求,換言之,服務(wù)器為每一個(gè)連接請(qǐng)求都創(chuàng)建了一個(gè)線程來處理。

4.5 多線程BIO服務(wù)器的弊端

多線程BIO服務(wù)器雖然解決了單線程BIO無法處理并發(fā)的弱點(diǎn),但是也帶來一個(gè)問題:如果有大量的請(qǐng)求連接到我們的服務(wù)器上,但是卻不發(fā)送消息,那么我們的服務(wù)器也會(huì)為這些不發(fā)送消息的請(qǐng)求創(chuàng)建一個(gè)單獨(dú)的線程,那么如果連接數(shù)少還好,連接數(shù)一多就會(huì)對(duì)服務(wù)端造成極大的壓力。所以如果這種不活躍的線程比較多,我們應(yīng)該采取單線程的一個(gè)解決方案,但是單線程又無法處理并發(fā),這就陷入了一種很矛盾的狀態(tài),于是就有了NIO。

05 NIO

5.1 NIO的引入

我們先來看看單線程模式下BIO服務(wù)器的代碼,其實(shí)NIO需要解決的最根本的問題就是存在于BIO中的兩個(gè)阻塞,分別是等待連接時(shí)的阻塞和等待數(shù)據(jù)時(shí)的阻塞。

  1. public class Server { 
  2.     public static void main(String[] args) { 
  3.         byte[] buffer = new byte[1024]; 
  4.         try { 
  5.             ServerSocket serverSocket = new ServerSocket(8080); 
  6.             System.out.println("服務(wù)器已啟動(dòng)并監(jiān)聽8080端口"); 
  7.             while (true) { 
  8.                 System.out.println(); 
  9.                 System.out.println("服務(wù)器正在等待連接..."); 
  10.                 //阻塞1:等待連接時(shí)阻塞 
  11.                 Socket socket = serverSocket.accept(); 
  12.                 System.out.println("服務(wù)器已接收到連接請(qǐng)求..."); 
  13.                 System.out.println(); 
  14.                 System.out.println("服務(wù)器正在等待數(shù)據(jù)..."); 
  15.                 //阻塞2:等待數(shù)據(jù)時(shí)阻塞 
  16.                 socket.getInputStream().read(buffer); 
  17.                 System.out.println("服務(wù)器已經(jīng)接收到數(shù)據(jù)"); 
  18.                 System.out.println(); 
  19.                 String content = new String(buffer); 
  20.                 System.out.println("接收到的數(shù)據(jù):" + content); 
  21.             } 
  22.         } catch (IOException e) { 
  23.             // TODO Auto-generated catch block 
  24.             e.printStackTrace(); 
  25.         } 
  26.     } 

我們需要再老調(diào)重談的一點(diǎn)是,如果單線程服務(wù)器在等待數(shù)據(jù)時(shí)阻塞,那么第二個(gè)連接請(qǐng)求到來時(shí),服務(wù)器是無法響應(yīng)的。如果是多線程服務(wù)器,那么又會(huì)有為大量空閑請(qǐng)求產(chǎn)生新線程從而造成線程占用系統(tǒng)資源,線程浪費(fèi)的情況。

那么我們的問題就轉(zhuǎn)移到,如何讓單線程服務(wù)器在等待客戶端數(shù)據(jù)到來時(shí),依舊可以接收新的客戶端連接請(qǐng)求。

5.2 模擬NIO解決方案

如果要解決上文中提到的單線程服務(wù)器接收數(shù)據(jù)時(shí)阻塞,而無法接收新請(qǐng)求的問題,那么其實(shí)可以讓服務(wù)器在等待數(shù)據(jù)時(shí)不進(jìn)入阻塞狀態(tài),問題不就迎刃而解了嗎?

(1)第一種解決方案(等待連接時(shí)和等待數(shù)據(jù)時(shí)不阻塞)

  1. public class Server { 
  2.     public static void main(String[] args) throws InterruptedException { 
  3.         ByteBuffer byteBuffer = ByteBuffer.allocate(1024); 
  4.         try { 
  5.             //Java為非阻塞設(shè)置的類 
  6.             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 
  7.             serverSocketChannel.bind(new InetSocketAddress(8080)); 
  8.             //設(shè)置為非阻塞 
  9.             serverSocketChannel.configureBlocking(false); 
  10.             while(true) { 
  11.                 SocketChannel socketChannel = serverSocketChannel.accept(); 
  12.                 if(socketChannel==null) { 
  13.                     //表示沒人連接 
  14.                     System.out.println("正在等待客戶端請(qǐng)求連接..."); 
  15.                     Thread.sleep(5000); 
  16.                 }else { 
  17.                     System.out.println("當(dāng)前接收到客戶端請(qǐng)求連接..."); 
  18.                 } 
  19.                 if(socketChannel!=null) { 
  20.  //設(shè)置為非阻塞 
  21.                     socketChannel.configureBlocking(false); 
  22.                     byteBuffer.flip();//切換模式 寫-->讀 
  23.                     int effective = socketChannel.read(byteBuffer); 
  24.                     if(effective!=0) { 
  25.                         String content = Charset.forName("utf-8").decode(byteBuffer).toString(); 
  26.                         System.out.println(content); 
  27.                     }else { 
  28.                         System.out.println("當(dāng)前未收到客戶端消息"); 
  29.                     } 
  30.                 } 
  31.             } 
  32.         } catch (IOException e) { 
  33.             // TODO Auto-generated catch block 
  34.             e.printStackTrace(); 
  35.         } 
  36.     } 

運(yùn)行結(jié)果

 

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

 

不難看出,在這種解決方案下,雖然在接收客戶端消息時(shí)不會(huì)阻塞,但是又開始重新接收服務(wù)器請(qǐng)求,用戶根本來不及輸入消息,服務(wù)器就轉(zhuǎn)向接收別的客戶端請(qǐng)求了,換言之,服務(wù)器弄丟了當(dāng)前客戶端的請(qǐng)求。

(2)解決方案二(緩存Socket,輪詢數(shù)據(jù)是否準(zhǔn)備好)

  1. public class Server { 
  2.     public static void main(String[] args) throws InterruptedException { 
  3.         ByteBuffer byteBuffer = ByteBuffer.allocate(1024); 
  4.          
  5.         List<SocketChannel> socketList = new ArrayList<SocketChannel>(); 
  6.         try { 
  7.             //Java為非阻塞設(shè)置的類 
  8.             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 
  9.             serverSocketChannel.bind(new InetSocketAddress(8080)); 
  10.             //設(shè)置為非阻塞 
  11.             serverSocketChannel.configureBlocking(false); 
  12.             while(true) { 
  13.                 SocketChannel socketChannel = serverSocketChannel.accept(); 
  14.                 if(socketChannel==null) { 
  15.                     //表示沒人連接 
  16.                     System.out.println("正在等待客戶端請(qǐng)求連接..."); 
  17.                     Thread.sleep(5000); 
  18.                 }else { 
  19.                     System.out.println("當(dāng)前接收到客戶端請(qǐng)求連接..."); 
  20.                     socketList.add(socketChannel); 
  21.                 } 
  22.                 for(SocketChannel socket:socketList) { 
  23.                     socket.configureBlocking(false); 
  24.                     int effective = socket.read(byteBuffer); 
  25.                     if(effective!=0) { 
  26.                         byteBuffer.flip();//切換模式 寫-->讀 
  27.                         String content = Charset.forName("UTF-8").decode(byteBuffer).toString(); 
  28.                         System.out.println("接收到消息:"+content); 
  29.                         byteBuffer.clear(); 
  30.                     }else { 
  31.                         System.out.println("當(dāng)前未收到客戶端消息"); 
  32.                     } 
  33.                 } 
  34.             } 
  35.         } catch (IOException e) { 
  36.             // TODO Auto-generated catch block 
  37.             e.printStackTrace(); 
  38.         } 
  39.     } 

運(yùn)行結(jié)果

 

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

 

 

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

 

代碼解析

在解決方案一中,我們采用了非阻塞方式,但是發(fā)現(xiàn)一旦非阻塞,等待客戶端發(fā)送消息時(shí)就不會(huì)再阻塞了,而是直接重新去獲取新客戶端的連接請(qǐng)求,這就會(huì)造成客戶端連接丟失,而在解決方案二中,我們將連接存儲(chǔ)在一個(gè)list集合中,每次等待客戶端消息時(shí)都去輪詢,看看消息是否準(zhǔn)備好,如果準(zhǔn)備好則直接打印消息。可以看到,從頭到尾我們一直沒有開啟第二個(gè)線程,而是一直采用單線程來處理多個(gè)客戶端的連接,這樣的一個(gè)模式可以很完美地解決BIO在單線程模式下無法處理多客戶端請(qǐng)求的問題,并且解決了非阻塞狀態(tài)下連接丟失的問題。

(3)存在的問題(解決方案二)

從剛才的運(yùn)行結(jié)果中其實(shí)可以看出,消息沒有丟失,程序也沒有阻塞。但是,在接收消息的方式上可能有些許不妥,我們采用了一個(gè)輪詢的方式來接收消息,每次都輪詢所有的連接,看消息是否準(zhǔn)備好,測試用例中只是三個(gè)連接,所以看不出什么問題來,但是我們假設(shè)有1000萬連接,甚至更多,采用這種輪詢的方式效率是極低的。另外,1000萬連接中,我們可能只會(huì)有100萬會(huì)有消息,剩下的900萬并不會(huì)發(fā)送任何消息,那么這些連接程序依舊要每次都去輪詢,這顯然是不合適的。

真實(shí)NIO中如何解決

在真實(shí)NIO中,并不會(huì)在Java層上來進(jìn)行一個(gè)輪詢,而是將輪詢的這個(gè)步驟交給我們的操作系統(tǒng)來進(jìn)行,他將輪詢的那部分代碼改為操作系統(tǒng)級(jí)別的系統(tǒng)調(diào)用(select函數(shù),在linux環(huán)境中為epoll),在操作系統(tǒng)級(jí)別上調(diào)用select函數(shù),主動(dòng)地去感知有數(shù)據(jù)的socket。

06 關(guān)于使用select/epoll和直接在應(yīng)用層做輪詢的區(qū)別

我們?cè)谥皩?shí)現(xiàn)了一個(gè)使用Java做多個(gè)客戶端連接輪詢的邏輯,但是在真正的NIO源碼中其實(shí)并不是這么實(shí)現(xiàn)的,NIO使用了操作系統(tǒng)底層的輪詢系統(tǒng)調(diào)用 select/epoll(windows:select,linux:epoll),那么為什么不直接實(shí)現(xiàn)而要去調(diào)用系統(tǒng)來做輪詢呢?

6.1 select底層邏輯

 

BIO和NIO了解多少呢?一起從實(shí)踐角度重新理解下吧

 

假設(shè)有A、B、C、D、E五個(gè)連接同時(shí)連接服務(wù)器,那么根據(jù)我們上文中的設(shè)計(jì),程序?qū)?huì)遍歷這五個(gè)連接,輪詢每個(gè)連接,獲取各自數(shù)據(jù)準(zhǔn)備情況,那么和我們自己寫的程序有什么區(qū)別呢?

首先,我們寫的Java程序其本質(zhì)在輪詢每個(gè)Socket的時(shí)候也需要去調(diào)用系統(tǒng)函數(shù),那么輪詢一次調(diào)用一次,會(huì)造成不必要的上下文切換開銷。

而Select會(huì)將五個(gè)請(qǐng)求從用戶態(tài)空間全量復(fù)制一份到內(nèi)核態(tài)空間,在內(nèi)核態(tài)空間來判斷每個(gè)請(qǐng)求是否準(zhǔn)備好數(shù)據(jù),完全避免頻繁的上下文切換。所以效率是比我們直接在應(yīng)用層寫輪詢要高的。

如果select沒有查詢到到有數(shù)據(jù)的請(qǐng)求,那么將會(huì)一直阻塞(是的,select是一個(gè)阻塞函數(shù))。如果有一個(gè)或者多個(gè)請(qǐng)求已經(jīng)準(zhǔn)備好數(shù)據(jù)了,那么select將會(huì)先將有數(shù)據(jù)的文件描述符置位,然后select返回。返回后通過遍歷查看哪個(gè)請(qǐng)求有數(shù)據(jù)。

select的缺點(diǎn)

底層存儲(chǔ)依賴bitmap,處理的請(qǐng)求是有上限的,為1024。

文件描述符是會(huì)置位的,所以如果當(dāng)被置位的文件描述符需要重新使用時(shí),是需要重新賦空值的。

fd(文件描述符)從用戶態(tài)拷貝到內(nèi)核態(tài)仍然有一筆開銷。

select返回后還要再次遍歷,來獲知是哪一個(gè)請(qǐng)求有數(shù)據(jù)。

6.2 poll函數(shù)底層邏輯

poll的工作原理和select很像,先來看一段poll內(nèi)部使用的一個(gè)結(jié)構(gòu)體。

struct pollfd{ int fd; short events; short revents;}

poll同樣會(huì)將所有的請(qǐng)求拷貝到內(nèi)核態(tài),和select一樣,poll同樣是一個(gè)阻塞函數(shù),當(dāng)一個(gè)或多個(gè)請(qǐng)求有數(shù)據(jù)的時(shí)候,也同樣會(huì)進(jìn)行置位,但是它置位的是結(jié)構(gòu)體pollfd中的events或者revents置位,而不是對(duì)fd本身進(jìn)行置位,所以在下一次使用的時(shí)候不需要再進(jìn)行重新賦空值的操作。poll內(nèi)部存儲(chǔ)不依賴bitmap,而是使用pollfd數(shù)組的這樣一個(gè)數(shù)據(jù)結(jié)構(gòu),數(shù)組的大小肯定是大于1024的。解決了select 1、2兩點(diǎn)的缺點(diǎn)。

6.3 epoll

epoll是最新的一種多路IO復(fù)用的函數(shù)。這里只說說它的特點(diǎn)。

epoll和上述兩個(gè)函數(shù)最大的不同是,它的fd是共享在用戶態(tài)和內(nèi)核態(tài)之間的,所以可以不必進(jìn)行從用戶態(tài)到內(nèi)核態(tài)的一個(gè)拷貝,這樣可以節(jié)約系統(tǒng)資源;另外,在select和poll中,如果某個(gè)請(qǐng)求的數(shù)據(jù)已經(jīng)準(zhǔn)備好,它們會(huì)將所有的請(qǐng)求都返回,供程序去遍歷查看哪個(gè)請(qǐng)求存在數(shù)據(jù),但是epoll只會(huì)返回存在數(shù)據(jù)的請(qǐng)求,這是因?yàn)閑poll在發(fā)現(xiàn)某個(gè)請(qǐng)求存在數(shù)據(jù)時(shí),首先會(huì)進(jìn)行一個(gè)重排操作,將所有有數(shù)據(jù)的fd放到最前面的位置,然后返回(返回值為存在數(shù)據(jù)請(qǐng)求的個(gè)數(shù)N),那么我們的上層程序就可以不必將所有請(qǐng)求都輪詢,而是直接遍歷epoll返回的前N個(gè)請(qǐng)求,這些請(qǐng)求都是有數(shù)據(jù)的請(qǐng)求。

07 Java中BIO和NIO的概念

通常一些文章都是在開頭放上概念,但是我這次選擇將概念放在結(jié)尾,因?yàn)橥ㄟ^上面的實(shí)操,相信大家對(duì)Java中BIO和NIO都有了自己的一些理解,這時(shí)候再來看概念應(yīng)該會(huì)更好理解一些了。

7.1 先來個(gè)例子理解一下概念,以銀行取款為例

同步 : 自己親自出馬持銀行卡到銀行取錢(使用同步IO時(shí),Java自己處理IO讀寫)。

異步 : 委托一小弟拿銀行卡到銀行取錢,然后給你(使用異步IO時(shí),Java將IO讀寫委托給OS處理,需要將數(shù)據(jù)緩沖區(qū)地址和大小傳給OS(銀行卡和密碼),OS需要支持異步IO操作API)。

阻塞 : ATM排隊(duì)取款,你只能等待(使用阻塞IO時(shí),Java調(diào)用會(huì)一直阻塞到讀寫完成才返回)。

非阻塞 : 柜臺(tái)取款,取個(gè)號(hào),然后坐在椅子上做其它事,等號(hào)廣播會(huì)通知你辦理,沒到號(hào)你就不能去,你可以不斷問大堂經(jīng)理排到了沒有,大堂經(jīng)理如果說還沒到你就不能去(使用非阻塞IO時(shí),如果不能讀寫Java調(diào)用會(huì)馬上返回,當(dāng)IO事件分發(fā)器會(huì)通知可讀寫時(shí)再繼續(xù)進(jìn)行讀寫,不斷循環(huán)直到讀寫完成)。

7.2 Java對(duì)BIO、NIO的支持

Java BIO (blocking I/O): 同步并阻塞,服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接一個(gè)線程,即客戶端有連接請(qǐng)求時(shí)服務(wù)器端就需要啟動(dòng)一個(gè)線程進(jìn)行處理,如果這個(gè)連接不做任何事情會(huì)造成不必要的線程開銷,當(dāng)然可以通過線程池機(jī)制改善。

Java NIO (non-blocking I/O): 同步非阻塞,服務(wù)器實(shí)現(xiàn)模式為一個(gè)請(qǐng)求一個(gè)線程,即客戶端發(fā)送的連接請(qǐng)求都會(huì)注冊(cè)到多路復(fù)用器上,多路復(fù)用器輪詢到連接有I/O請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程進(jìn)行處理。

7.3 BIO、NIO適用場景分析

BIO方式適用于連接數(shù)目比較小且固定的架構(gòu),這種方式對(duì)服務(wù)器資源要求比較高,并發(fā)局限于應(yīng)用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。

NIO方式適用于連接數(shù)目多且連接比較短(輕操作)的架構(gòu),比如聊天服務(wù)器,并發(fā)局限于應(yīng)用中,編程比較復(fù)雜,JDK1.4開始支持。

08 結(jié)語

本文介紹了一些關(guān)于JavaBIO和NIO從自己實(shí)操的角度上的一些理解,我個(gè)人認(rèn)為這樣去理解BIO和NIO會(huì)比光看概念會(huì)有更深的理解,也希望各位同學(xué)可以自己去敲一遍,通過程序的運(yùn)行結(jié)果得出自己對(duì)JavaBIO和NIO的理解。

責(zé)任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2020-12-31 23:31:13

網(wǎng)絡(luò)安全網(wǎng)絡(luò)攻擊漏洞

2015-02-09 09:26:26

程序員

2015-02-09 10:55:50

編程女程序員

2022-07-29 11:06:47

架構(gòu)開發(fā)

2024-05-28 00:00:03

Java垃圾收集機(jī)制

2021-07-27 18:03:59

iOSSwift調(diào)度器

2024-03-28 08:50:58

Flink分配方式后端

2025-03-28 02:00:00

后臺(tái)配置鴻蒙

2017-11-15 08:50:59

數(shù)據(jù)庫MySQL執(zhí)

2024-01-24 07:24:18

物聯(lián)網(wǎng)通信協(xié)議IOT

2021-10-27 07:15:37

SpringAOP編程(

2022-05-07 07:43:07

Redis存儲(chǔ)系統(tǒng)數(shù)據(jù)庫

2022-04-17 09:56:41

cookiesame-party

2018-09-18 14:34:43

GIT系統(tǒng)實(shí)踐

2021-12-10 07:45:48

字節(jié)音頻視頻

2024-04-10 10:09:07

2020-11-04 07:32:01

AI技術(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产91久久久久蜜臀青青天草二 | 一区二区免费在线观看 | 精品伊人| 亚洲一区 中文字幕 | 91成人午夜性a一级毛片 | 天天干天天玩天天操 | 99久久精品免费看国产小宝寻花 | 在线欧美一区 | 国产精品污www一区二区三区 | 国产精品三级久久久久久电影 | 久久精品国产99国产精品 | 日本三级线观看 视频 | 欧美黄色大片在线观看 | 麻豆91av | 日韩亚洲欧美综合 | 超碰电影| 日韩精品一区二区三区第95 | 天天亚洲 | 日韩久久精品 | 99欧美精品 | www.成人免费视频 | 亚洲国产欧美国产综合一区 | 成人亚洲 | 免费久 | 九九久久免费视频 | 成人av在线播放 | 欧美精品一区二区在线观看 | 日韩网站在线观看 | 免费国产视频 | 91精品国产麻豆 | 欧美456| 久久网国产 | 久久成人精品视频 | 中文在线一区二区 | 能看的av| 国产91网站在线观看 | 综合一区| 欧美日韩在线国产 | 91精品久久久久久久久久入口 | 9191成人精品久久 | 日韩在线播放视频 |