一、問題 BIO 和 NIO 作為 Server 端,當建立了 10 個連接時,分別產生多少個線程? 答案: 因為傳統的 IO 也就是 BIO 是同步線程堵塞的,所以每個連接都要分配一個專用線程來處理請求,這樣 10 個連接就會創建 10 個線程去處理。而 NIO 是一種同步非阻塞的 I/O 模型, ...
一、問題
BIO 和 NIO 作為 Server 端,當建立了 10 個連接時,分別產生多少個線程?
答案: 因為傳統的 IO 也就是 BIO 是同步線程堵塞的,所以每個連接都要分配一個專用線程來處理請求,這樣 10 個連接就會創建 10 個線程去處理。而 NIO 是一種同步非阻塞的 I/O 模型,它的核心技術是多路復用,可以使用一個鏈接上的不同通道來處理不同的請求,所以即使有 10 個連接,對於 NIO 來說,開啟 1 個線程就夠了。
二、BIO 代碼實現
publicclassDemoServerextendsThread{ privateServerSocket serverSocket; publicint getPort(){ return serverSocket.getLocalPort(); } publicvoid run(){ try{ serverSocket =newServerSocket(0); while(true){ Socket socket = serverSocket.accept(); RequestHandler requestHandler =newRequestHandler(socket); requestHandler.start(); } }catch(IOException e){ e.printStackTrace(); }finally{ if(serverSocket !=null){ try{ serverSocket.close(); }catch(IOException e){ e.printStackTrace(); } } } } publicstaticvoid main(String[] args)throwsIOException{ DemoServer server =newDemoServer(); server.start(); try(Socket client =newSocket(InetAddress.getLocalHost(), server.getPort())){ BufferedReader bufferedReader =newBufferedReader(newInputStreamReader(client.getInputStream())); bufferedReader.lines().forEach(s ->System.out.println(s)); } } } // 簡化實現,不做讀取,直接發送字元串 classRequestHandlerextendsThread{ privateSocket socket; RequestHandler(Socket socket){ this.socket = socket; } @Override publicvoid run(){ try(PrintWriter out =newPrintWriter(socket.getOutputStream());){ out.println("Hello world!"); out.flush(); }catch(Exception e){ e.printStackTrace(); } } }
- 伺服器端啟動 ServerSocket,埠 0 表示自動綁定一個空閑埠。
- 調用 accept 方法,阻塞等待客戶端連接。
- 利用 Socket 模擬了一個簡單的客戶端,只進行連接、讀取、列印。
- 當連接建立後,啟動一個單獨線程負責回覆客戶端請求。
這樣,一個簡單的 Socket 伺服器就被實現出來了。
(圖片來源於楊曉峰)
三、NIO 代碼實現
publicclassNIOServerextendsThread{ publicvoid run(){ try(Selector selector =Selector.open(); ServerSocketChannel serverSocket =ServerSocketChannel.open();){// 創建 Selector 和 Channel serverSocket.bind(newInetSocketAddress(InetAddress.getLocalHost(),8888)); serverSocket.configureBlocking(false); // 註冊到 Selector,並說明關註點 serverSocket.register(selector,SelectionKey.OP_ACCEPT); while(true){ selector.select();// 阻塞等待就緒的 Channel,這是關鍵點之一 Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> iter = selectedKeys.iterator(); while(iter.hasNext()){ SelectionKey key = iter.next(); // 生產系統中一般會額外進行就緒狀態檢查 sayHelloWorld((ServerSocketChannel) key.channel()); iter.remove(); } } }catch(IOException e){ e.printStackTrace(); } } privatevoid sayHelloWorld(ServerSocketChannel server)throwsIOException{ try(SocketChannel client = server.accept();){ client.write(Charset.defaultCharset().encode("Hello world!")); } } // 省略了與前面類似的 main }
- 首先,通過 Selector.open() 創建一個 Selector,作為類似調度員的角色。
- 然後,創建一個 ServerSocketChannel,並且向 Selector 註冊,通過指定 SelectionKey.OP_ACCEPT,告訴調度員,它關註的是新的連接請求。註意:為什麼我們要明確配置非阻塞模式呢?這是因為阻塞模式下,註冊操作是不允許的,會拋出 IllegalBlockingModeException 異常。
- Selector 阻塞在 select 操作,當有 Channel 發生接入請求,就會被喚醒。
- 在 sayHelloWorld 方法中,通過 SocketChannel 和 Buffer 進行數據操作,在本例中是發送了一段字元串。
可以看到,在前面兩個樣例中,IO 都是同步阻塞模式,所以需要多線程以實現多任務處理。而 NIO 則是利用了單線程輪詢事件的機制,通過高效地定位就緒的 Channel,來決定做什麼,僅僅 select 階段是阻塞的,可以有效避免大量客戶端連接時,頻繁線程切換帶來的問題,應用的擴展能力有了非常大的提高。下麵這張圖對這種實現思路進行了形象地說明。
作者: 王磊的博客
免費Java資料領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo/Kafka、Hadoop、Hbase、Flink等高併發分散式、大數據、機器學習等技術。
傳送門:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q