1. 程式人生 > >BIO和NIO

BIO和NIO

zedosu

關於BIO和NIO的理解

摘要: 關於BIO和NIO的理解

最近大概看了ZooKeeper和Mina的原始碼發現都是用Java NIO實現的,所以有必要搞清楚什麼是NIO。下面是我結合網路資料自己總結的,為了節約時間圖示隨便畫的,能達意就行。

 

簡介:

BIO:同步阻塞式IO,伺服器實現模式為一個連線一個執行緒,即客戶端有連線請求時伺服器端就需要啟動一個執行緒進行處理,如果這個連線不做任何事情會造成不必要的執行緒開銷,當然可以通過執行緒池機制改善。 
NIO:同步非阻塞式IO,伺服器實現模式為一個請求一個執行緒,即客戶端傳送的連線請求都會註冊到多路複用器上,多路複用器輪詢到連線有I/O請求時才啟動一個執行緒進行處理。 
AIO(NIO.2):非同步非阻塞式IO,伺服器實現模式為一個有效請求一個執行緒,客戶端的I/O請求都是由OS先完成了再通知伺服器應用去啟動執行緒進行處理。 

BIO
 
同步阻塞式IO,相信每一個學習過作業系統網路程式設計或者任何語言的網路程式設計的人都很熟悉,在while迴圈中服務端會呼叫accept方法等待接收客戶端的連線請求,一旦接收到一個連線請求,就可以建立通訊套接字在這個通訊套接字上進行讀寫操作,此時不能再接收其他客戶端連線請求,只能等待同當前連線的客戶端的操作執行完成。 
如果BIO要能夠同時處理多個客戶端請求,就必須使用多執行緒,即每次accept阻塞等待來自客戶端請求,一旦受到連線請求就建立通訊套接字同時開啟一個新的執行緒來處理這個套接字的資料讀寫請求,然後立刻又繼續accept等待其他客戶端連線請求,即為每一個客戶端連線請求都建立一個執行緒來單獨處理,大概原理圖就像這樣: 
 

雖然此時伺服器具備了高併發能力,即能夠同時處理多個客戶端請求了,但是卻帶來了一個問題,隨著開啟的執行緒數目增多,將會消耗過多的記憶體資源,導致伺服器變慢甚至崩潰,NIO可以一定程度解決這個問題。


NIO 
同步非阻塞式IO,關鍵是採用了事件驅動的思想來實現了一個多路轉換器。 
NIO與BIO最大的區別就是隻需要開啟一個執行緒就可以處理來自多個客戶端的IO事件,這是怎麼做到的呢? 
就是多路複用器,可以監聽來自多個客戶端的IO事件: 
A. 若服務端監聽到客戶端連線請求,便為其建立通訊套接字(java中就是通道),然後返回繼續監聽,若同時有多個客戶端連線請求到來也可以全部收到,依次為它們都建立通訊套接字。 
B. 若服務端監聽到來自已經建立了通訊套接字的客戶端傳送來的資料,就會呼叫對應介面處理接收到的資料,若同時有多個客戶端發來資料也可以依次進行處理。 
C. 監聽多個客戶端的連線請求和接收資料請求同時還能監聽自己時候有資料要傳送。 
 

總之就是在一個執行緒中就可以呼叫多路複用介面(java中是select)阻塞同時監聽來自多個客戶端的IO請求,一旦有收到IO請求就呼叫對應函式處理。 

各自應用場景 

到這裡你也許已經發現,一旦有請求到來(不管是幾個同時到還是隻有一個到),都會呼叫對應IO處理函式處理,所以:

(1)NIO適合處理連線數目特別多,但是連線比較短(輕操作)的場景,Jetty,Mina,ZooKeeper等都是基於java nio實現。

(2)BIO方式適用於連線數目比較小且固定的場景,這種方式對伺服器資源要求比較高,併發侷限於應用中。


附錄:下面附上一個別人寫的java NIO的例子。 
服務端:  

  1. 1. package cn.nio;    
  2. 2.    
  3. 3. import java.io.IOException;    
  4. 4. import java.net.InetSocketAddress;    
  5. 5. import java.nio.ByteBuffer;    
  6. 6. import java.nio.channels.SelectionKey;    
  7. 7. import java.nio.channels.Selector;    
  8. 8. import java.nio.channels.ServerSocketChannel;    
  9. 9. import java.nio.channels.SocketChannel;    
  10. 10. import java.util.Iterator;    
  11. 11.    
  12. 12. /**  
  13. 13. * NIO服務端  
  14. 14. *  
  15. 15. */    
  16. 16. public class NIOServer {    
  17. 17.    //通道管理器    
  18. 18.    private Selector selector;    
  19. 19.    
  20. 20.    /**  
  21. 21.     * 獲得一個ServerSocket通道,並對該通道做一些初始化的工作  
  22. 22.     * @param port  繫結的埠號  
  23. 23.     * @throws IOException  
  24. 24.     */    
  25. 25.    public void initServer(int port) throws IOException {    
  26. 26.        // 獲得一個ServerSocket通道    
  27. 27.        ServerSocketChannel serverChannel = ServerSocketChannel.open();    
  28. 28.        // 設定通道為非阻塞    
  29. 29.        serverChannel.configureBlocking(false);    
  30. 30.        // 將該通道對應的ServerSocket繫結到port埠    
  31. 31.        serverChannel.socket().bind(new InetSocketAddress(port));    
  32. 32.        // 獲得一個通道管理器    
  33. 33.        this.selector = Selector.open();    
  34. 34.        //將通道管理器和該通道繫結,併為該通道註冊SelectionKey.OP_ACCEPT事件,註冊該事件後,    
  35. 35.        //當該事件到達時,selector.select()會返回,如果該事件沒到達selector.select()會一直阻塞。    
  36. 36.        serverChannel.register(selector, SelectionKey.OP_ACCEPT);    
  37. 37.    }    
  38. 38.    
  39. 39.    /**  
  40. 40.     * 採用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則進行處理  
  41. 41.     * @throws IOException  
  42. 42.     */    
  43. 43.    @SuppressWarnings("unchecked")    
  44. 44.    public void listen() throws IOException {    
  45. 45.        System.out.println("服務端啟動成功!");    
  46. 46.        // 輪詢訪問selector    
  47. 47.        while (true) {    
  48. 48.            //當註冊的事件到達時,方法返回;否則,該方法會一直阻塞    
  49. 49.            selector.select();    
  50. 50.            // 獲得selector中選中的項的迭代器,選中的項為註冊的事件    
  51. 51.            Iterator ite = this.selector.selectedKeys().iterator();    
  52. 52.            while (ite.hasNext()) {    
  53. 53.                SelectionKey key = (SelectionKey) ite.next();    
  54. 54.                // 刪除已選的key,以防重複處理    
  55. 55.                ite.remove();    
  56. 56.                // 客戶端請求連線事件    
  57. 57.                if (key.isAcceptable()) {    
  58. 58.                    ServerSocketChannel server = (ServerSocketChannel) key    
  59. 59.                            .channel();    
  60. 60.                    // 獲得和客戶端連線的通道    
  61. 61.                    SocketChannel channel = server.accept();    
  62. 62.                    // 設定成非阻塞    
  63. 63.                    channel.configureBlocking(false);    
  64. 64.    
  65. 65.                    //在這裡可以給客戶端傳送資訊哦    
  66. 66.                    channel.write(ByteBuffer.wrap(new String("向客戶端傳送了一條資訊").getBytes()));    
  67. 67.                    //在和客戶端連線成功之後,為了可以接收到客戶端的資訊,需要給通道設定讀的許可權。    
  68. 68.                    channel.register(this.selector, SelectionKey.OP_READ);    
  69. 69.                        
  70. 70.                    // 獲得了可讀的事件    
  71. 71.                } else if (key.isReadable()) {    
  72. 72.                        read(key);    
  73. 73.                }    
  74. 74.    
  75. 75.            }    
  76. 76.    
  77. 77.        }    
  78. 78.    }    
  79. 79.    /**  
  80. 80.     * 處理讀取客戶端發來的資訊 的事件  
  81. 81.     * @param key  
  82. 82.     * @throws IOException   
  83. 83.     */    
  84. 84.    public void read(SelectionKey key) throws IOException{    
  85. 85.        // 伺服器可讀取訊息:得到事件發生的Socket通道    
  86. 86.        SocketChannel channel = (SocketChannel) key.channel();    
  87. 87.        // 建立讀取的緩衝區    
  88. 88.        ByteBuffer buffer = ByteBuffer.allocate(10);    
  89. 89.        channel.read(buffer);    
  90. 90.        byte[] data = buffer.array();    
  91. 91.        String msg = new String(data).trim();    
  92. 92.        System.out.println("服務端收到資訊:"+msg);    
  93. 93.        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());    
  94. 94.        channel.write(outBuffer);// 將訊息回送給客戶端    
  95. 95.    }    
  96. 96.        
  97. 97.    /**  
  98. 98.     * 啟動服務端測試  
  99. 99.     * @throws IOException   
  100. 100.     */    
  101. 101.    public static void main(String[] args) throws IOException {    
  102. 102.        NIOServer server = new NIOServer();    
  103. 103.        server.initServer(8000);    
  104. 104.        server.listen();    
  105. 105.    }    
  106. 106.    
  107. 107. }    

  
客戶端:

  1.  1. package cn.nio;    
  2. 2.    
  3. 3. import java.io.IOException;    
  4. 4. import java.net.InetSocketAddress;    
  5. 5. import java.nio.ByteBuffer;    
  6. 6. import java.nio.channels.SelectionKey;    
  7. 7. import java.nio.channels.Selector;    
  8. 8. import java.nio.channels.SocketChannel;    
  9. 9. import java.util.Iterator;    
  10. 10.    
  11. 11. /**  
  12. 12. * NIO客戶端  
  13. 13. *  
  14. 14. */    
  15. 15. public class NIOClient {    
  16. 16.    //通道管理器    
  17. 17.    private Selector selector;    
  18. 18.    
  19. 19.    /**  
  20. 20.     * 獲得一個Socket通道,並對該通道做一些初始化的工作  
  21. 21.     * @param ip 連線的伺服器的ip  
  22. 22.     * @param port  連線的伺服器的埠號           
  23. 23.     * @throws IOException  
  24. 24.     */    
  25. 25.    public void initClient(String ip,int port) throws IOException {    
  26. 26.        // 獲得一個Socket通道    
  27. 27.        SocketChannel channel = SocketChannel.open();    
  28. 28.        // 設定通道為非阻塞    
  29. 29.        channel.configureBlocking(false);    
  30. 30.        // 獲得一個通道管理器    
  31. 31.        this.selector = Selector.open();    
  32. 32.            
  33. 33.        // 客戶端連線伺服器,其實方法執行並沒有實現連線,需要在listen()方法中調    
  34. 34.        //用channel.finishConnect();才能完成連線    
  35. 35.        channel.connect(new InetSocketAddress(ip,port));    
  36. 36.        //將通道管理器和該通道繫結,併為該通道註冊SelectionKey.OP_CONNECT事件。    
  37. 37.        channel.register(selector, SelectionKey.OP_CONNECT);    
  38. 38.    }    
  39. 39.    
  40. 40.    /**  
  41. 41.     * 採用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則進行處理  
  42. 42.     * @throws IOException  
  43. 43.     */    
  44. 44.    @SuppressWarnings("unchecked")    
  45. 45.    public void listen() throws IOException {    
  46. 46.        // 輪詢訪問selector    
  47. 47.        while (true) {    
  48. 48.            selector.select();    
  49. 49.            // 獲得selector中選中的項的迭代器    
  50. 50.            Iterator ite = this.selector.selectedKeys().iterator();    
  51. 51.            while (ite.hasNext()) {    
  52. 52.                SelectionKey key = (SelectionKey) ite.next();    
  53. 53.                // 刪除已選的key,以防重複處理    
  54. 54.                ite.remove();    
  55. 55.                // 連線事件發生    
  56. 56.                if (key.isConnectable()) {    
  57. 57.                    SocketChannel channel = (SocketChannel) key    
  58. 58.                            .channel();    
  59. 59.                    // 如果正在連線,則完成連線    
  60. 60.                    if(channel.isConnectionPending()){    
  61. 61.                        channel.finishConnect();    
  62. 62.                            
  63. 63.                    }    
  64. 64.                    // 設定成非阻塞    
  65. 65.                    channel.configureBlocking(false);    
  66. 66.    
  67. 67.                    //在這裡可以給服務端傳送資訊哦    
  68. 68.                    channel.write(ByteBuffer.wrap(new String("向服務端傳送了一條資訊").getBytes()));    
  69. 69.                    //在和服務端連線成功之後,為了可以接收到服務端的資訊,需要給通道設定讀的許可權。    
  70. 70.                    channel.register(this.selector, SelectionKey.OP_READ);    
  71. 71.                        
  72. 72.                    // 獲得了可讀的事件    
  73. 73.                } else if (key.isReadable()) {    
  74. 74.                        read(key);    
  75. 75.                }    
  76. 76.    
  77. 77.            }    
  78. 78.    
  79. 79.        }    
  80. 80.    }    
  81. 81.    /**  
  82. 82.     * 處理讀取服務端發來的資訊 的事件  
  83. 83.     * @param key  
  84. 84.     * @throws IOException   
  85. 85.     */    
  86. 86.    public void read(SelectionKey key) throws IOException{    
  87. 87.        //和服務端的read方法一樣    
  88. 88.    }    
  89. 89.        
  90. 90.        
  91. 91.    /**  
  92. 92.     * 啟動客戶端測試  
  93. 93.     * @throws IOException   
  94. 94.     */    
  95. 95.    public static void main(String[] args) throws IOException {    
  96. 96.        NIOClient client = new NIOClient();    
  97. 97.        client.initClient("localhost",8000);    
  98. 98.        client.listen();    
  99. 99.    }    
  100. 100.    
  101. 101. }    
  102.    


http://blog.csdn.net/jiyiqinlovexx/article/details/42619097 

	</div>
	<div class = "postDesc">posted @ <span id="post-date">2017-04-04 23:36</span> <a href='https://www.cnblogs.com/zedosu/'>zedosu</a> 閱讀(<span id="post_view_count">...</span>) 評論(<span id="post_comment_count">...</span>)  <a href ="https://i.cnblogs.com/EditPosts.aspx?postid=6666984" rel="nofollow">編輯</a> <a href="#" onclick="AddToWz(6666984);return false;">收藏</a></div>
</div>
<script type="text/javascript">var allowComments=true,cb_blogId=337523,cb_entryId=6666984,cb_blogApp=currentBlogApp,cb_blogUserGuid='e1dac851-2002-e711-845c-ac853d9f53ac',cb_entryCreatedDate='2017/4/4 23:36:00';loadViewCount(cb_entryId);var cb_postType=1;</script>
</div><!--end: forFlow -->
</div><!--end: mainContent 主體內容容器-->

<div id="sideBar">
	<div id="sideBarMain">

公告

		<div id="calendar"><div id="blog-calendar" style="display:none"></div><script type="text/javascript">loadBlogDefaultCalendar();</script></div>
		
		<DIV id="leftcontentcontainer">
			<div id="blog-sidecolumn"></div><script type="text/javascript">loadBlogSideColumn();</script>
		</DIV>
		
	</div><!--end: sideBarMain -->
</div><!--end: sideBar 側邊欄容器 -->
<div class="clear"></div>
</div><!--end: main -->
<div class="clear"></div>
<div id="footer">

Copyright ©2018 zedosu

原文地址:https://www.cnblogs.com/zedosu/p/6666984.html