1. 程式人生 > >阿裏面試題BIO和NIO數量問題附答案和代碼

阿裏面試題BIO和NIO數量問題附答案和代碼

hello config 試題 代碼 stream accept figure 我們 客戶端請求

一、問題

BIO 和 NIO 作為 Server 端,當建立了 10 個連接時,分別產生多少個線程?

答案: 因為傳統的 IO 也就是 BIO 是同步線程堵塞的,所以每個連接都要分配一個專用線程來處理請求,這樣 10 個連接就會創建 10 個線程去處理。而 NIO 是一種同步非阻塞的 I/O 模型,它的核心技術是多路復用,可以使用一個鏈接上的不同通道來處理不同的請求,所以即使有 10 個連接,對於 NIO 來說,開啟 1 個線程就夠了。

二、BIO 代碼實現

public class DemoServer extends Thread {
private ServerSocket serverSocket;

public int getPort() {
return serverSocket.getLocalPort();
}
public void run() {
try {
serverSocket = new ServerSocket(0);
while (true) {
Socket socket = serverSocket.accept();
RequestHandler requestHandler = new RequestHandler(socket);
requestHandler.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws IOException {
DemoServer server = new DemoServer();
server.start();
try (Socket client = new Socket(InetAddress.getLocalHost(), server.getPort())) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
bufferedReader.lines().forEach(s -> System.out.println(s));
}
}
}
// 簡化實現,不做讀取,直接發送字符串
class RequestHandler extends Thread {
private Socket socket;
RequestHandler(Socket socket) {
this.socket = socket;}
@Override
br/>}
@Override
try (PrintWriter out = new PrintWriter(socket.getOutputStream());) {
out.println("Hello world!");
out.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}

  • 服務器端啟動 ServerSocket,端口 0 表示自動綁定一個空閑端口。
  • 調用 accept 方法,阻塞等待客戶端連接。
  • 利用 Socket 模擬了一個簡單的客戶端,只進行連接、讀取、打印。
  • 當連接建立後,啟動一個單獨線程負責回復客戶端請求。

這樣,一個簡單的 Socket 服務器就被實現出來了。
技術分享圖片

三、NIO 代碼實現

public class NIOServer extends Thread {
public void run() {
try (Selector selector = Selector.open();
ServerSocketChannel serverSocket = ServerSocketChannel.open();) {// 創建 Selector 和 Channel
serverSocket.bind(new InetSocketAddress(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();
}
}
private void sayHelloWorld(ServerSocketChannel server) throws IOException {
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 階段是阻塞的,可以有效避免大量客戶端連接時,頻繁線程切換帶來的問題,應用的擴展能力有了非常大的提高。下面這張圖對這種實現思路進行了形象地說明。
技術分享圖片

阿裏面試題BIO和NIO數量問題附答案和代碼