深入理解BIO與NIO
BIO
BIO 為 Blocked-IO(阻塞 IO),在 JDK1.4 之前建立網絡連接時,只能使用 BIO
使用 BIO 時,服務端會對客戶端的每個請求都建立一個線程進行處理,客戶端向服務端發送請求后,先咨詢服務端是否有線程響應,如果沒有就會等待或者被拒絕
BIO 基本使用代碼:
服務端:
public class TCPServer {public static void main(String[] args) throws Exception {// 1.創建ServerSocket對象System.out.println("服務端 啟動....");System.out.println("初始化端口 7777 ");ServerSocket ss = new ServerSocket(7777); //端口號while (true) {// 2.監聽客戶端Socket s = ss.accept(); //阻塞// 3.從連接中取出輸入流來接收消息InputStream is = s.getInputStream(); //阻塞byte[] b = new byte[10];is.read(b);String clientIP = s.getInetAddress().getHostAddress();System.out.println(clientIP + "說:" + new String(b).trim());// 4.從連接中取出輸出流并回話OutputStream os = s.getOutputStream();os.write("服務端回復".getBytes());// 5.關閉s.close();}}
}
客戶端:
public class TCPClient {public static void main(String[] args) throws Exception {while (true) {// 1.創建Socket對象Socket s = new Socket("127.0.0.1", 7777);// 2.從連接中取出輸出流并發消息OutputStream os = s.getOutputStream();System.out.println("請輸入:");Scanner sc = new Scanner(System.in);String msg = sc.nextLine();os.write(msg.getBytes());// 3.從連接中取出輸入流并接收回話InputStream is = s.getInputStream(); //阻塞byte[] b = new byte[20];is.read(b);System.out.println("客戶端發送消息:" + new String(b).trim());// 4.關閉s.close();}}
}
BIO 缺點:
- Server 端會為客戶端的每一個連接請求都創建一個新的線程進行處理,如果客戶端連接請求數量太多,則會創建大量線程
NIO
從 JDK1.4 開始,Java 提供了一系列改進的輸入/輸出的新特性,被統稱為 NIO(即 New IO),NIO 彌補了 BIO 的不足,在服務端不需要為客戶端大量的請求而建立大量的處理線程,只需要用很少的線程就可以處理很多客戶端請求
NIO 和 BIO 有著相同的目的和作用,但是它們的實現方式完全不同;
- BIO 以流的方式處理數據,而 NIO 以塊的方式處理數據,塊 IO 的效率比流 IO 高很多。
- NIO 是非阻塞式的,這一點跟 BIO 也很不相同,使用它可以提供非阻塞式的高伸縮性網絡。
NIO 有三大核心部分
:
- Channel通道
- Buffer緩沖區
- Selector選擇器
使用 NIO 時,數據是基于 Channel
和 Buffer
進行操作的,數據從 Channel
被讀取到 Buffer
或者相反,Selector 用于監聽多個 Channel
通道的事件(連接事件、讀寫事件),通過 Selector 就可以實現單個線程來監聽多個客戶端通道
NIO 中的 Channel 用來建立到目標的一個連接,在 BIO 中流是單向的,例如 FileInputStream 只能進行讀取操作,而在 NIO 中 Channel 是雙向的,既可以讀也可以寫
NIO 工作流程圖如下:Server 端通過單線程來監聽多個客戶端 Channel 通道中的事件并進行處理
NIO 使用示例
服務端:
public class NIOServer {public static void main(String[] args) throws Exception {// 1. 開啟一個ServerSocketChannel通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 2. 開啟一個Selector選擇器Selector selector = Selector.open();// 3. 綁定端口號8888System.out.println("服務端 啟動....");System.out.println("初始化端口 8888 ");serverSocketChannel.bind(new InetSocketAddress(8888));// 4. 配置非阻塞方式serverSocketChannel.configureBlocking(false);// 5. Selector選擇器注冊ServerSocketChannel通道,綁定連接操作serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// 6. 循環執行:監聽連接事件及讀取數據操作while (true) {// 6.1 監控客戶端連接:selecto.select()方法返回的是客戶端的通道數,如果為0,則說明沒有客戶端連接。if (selector.select(2000) == 0) {System.out.println("服務端等待客戶端連接中~");continue;}// 6.2 得到SelectionKey,判斷通道里的事件Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();// 遍歷所有SelectionKeywhile (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();// 客戶端連接請求事件if (key.isAcceptable()) {System.out.println("服務端處理客戶端連接事件:OP_ACCEPT");SocketChannel socketChannel = serverSocketChannel.accept();socketChannel.configureBlocking(false);// 服務端建立與客戶端之間的連接通道 SocketChannel,并且將該通道注冊到 Selector 中,監聽該通道的讀事件socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));}// 讀取客戶端數據事件if (key.isReadable()) {// 數據在通道中,先拿到通道SocketChannel channel = (SocketChannel) key.channel();// 取到一個緩沖區,nio讀寫數據都是基于緩沖區。ByteBuffer buffer = (ByteBuffer) key.attachment();// 從通道中將客戶端發來的數據讀到緩沖區channel.read(buffer);System.out.println("客戶端數據長度:" + buffer.array().length);System.out.println("客戶端發來數據:" + new String(buffer.array()));}// 6.3 手動從集合中移除當前key,防止重復處理keyIterator.remove();}}}
}
客戶端:
public class NIOClient {public static void main(String[] args) throws Exception {// 1. 得到一個網絡通道SocketChannel channel = SocketChannel.open();// 2. 設置非阻塞方式channel.configureBlocking(false);// 3. 提供服務器端的IP地址和端口號InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);// 4. 連接服務器端,如果用connect()方法連接服務器不成功,則用finishConnect()方法進行連接if (!channel.connect(address)) {// 因為接需要花時間,所以用while一直去嘗試連接。在連接服務端時還可以做別的事,體現非阻塞。while (!channel.finishConnect()) {// nio 作為非阻塞式的優勢,如果服務器沒有響應(不啟動服務端),客戶端不會阻塞,最后會報錯,客戶端嘗試連接服務器連不上。System.out.println("客戶端等待連接建立時,執行其他任務~");}}// 5. 得到一個緩沖區并存入數據String msg = "客戶端發送消息:hello";ByteBuffer writeBuf = ByteBuffer.wrap(msg.getBytes());// 6. 發送數據channel.write(writeBuf);// 阻止客戶端停止,否則服務端也會停止。System.in.read();}
}
AIO
JDK 7 引入了 Asynchronous IO,即 AIO,叫做異步不阻塞的 IO,也可以叫做 NIO2
在進行 IO 編程中,常用到兩種模式:Reactor模式 和 Proactor 模式
- NIO采用 Reactor 模式,當有事件觸發時,服務器端得到通知,進行相應的處理
- AIO采用 Proactor 模式,引入異步通道的概念,簡化了程序編寫,一個有效的請求才啟動一個線程,它的特點是先由操作系統完成后,才通知服務端程序啟動線程去處理,一般適用于連接數較多且連接時間較長的應用