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