Abstract
在本博客當中我們主要會分爲如下幾點:
- Java中的BIO NIO AIO是啥
- BIO NIO AIO實現的簡單Echo client和server
- NIO AIO 深入之內部實現
- NIO AIO的設計模式: Reactor 以及 Proactor
本文所有的代碼都可以在最後的github找到.
IO 的分類
BIO
BIO 是 blocking io, 在jdk1.0的時候引入的. 它更多的是表現爲1對1的請求-線程的處理方式.
如下圖:
或者 爲了提高線程的利用率而採用的線程池版本.
這樣的架構有明顯的缺點:
1.線程切換帶來的開銷
2.編程時需要考慮同步.
3.需要考慮線程池大小,隊列大小,拒絕策略等等等等.
BIO echo server:
package io.bio;
import io.common.ServerInfo;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 最基礎的BIO服務器端
*/
public class BioServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(ServerInfo.PORT);
System.out.println("Server started on " + ServerInfo.PORT);
ExecutorService es = Executors.newCachedThreadPool();
while (true) {
Socket socket = ss.accept();
es.submit(new TaskHandler(socket));
}
}
static class TaskHandler implements Runnable {
private Socket socket;
private Scanner scanner;
private BufferedWriter out;
public TaskHandler(Socket socket) {
this.socket = socket;
try {
scanner = new Scanner(socket.getInputStream());
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
}
catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
boolean flag = true;
while (flag) {
String line = scanner.nextLine();
System.out.println("Read from client - " + line);
if (line != null) {
String writeMessage = "[Echo] " + line + "\n";
if (line.equalsIgnoreCase("bye")) {
flag = false;
writeMessage = "[Exit] byebye " + "\n";
}
try {
out.write(writeMessage);
out.flush();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
scanner.close();
try {
out.close();
}
catch (IOException e) {
e.printStackTrace();
}
try {
socket.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
BIO echo client:
package io.bio;
import io.common.InputUtil;
import io.common.ServerInfo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
/**
* 最基礎的BIO 客戶端
*/
public class BioClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket(InetAddress.getByName(ServerInfo.HOST), ServerInfo.PORT);
Scanner scanner = new Scanner(new BufferedReader(new InputStreamReader(socket.getInputStream())));
scanner.useDelimiter("\n");
while (true) {
String line = InputUtil.getLine("Input something:").trim();
socket.getOutputStream().write((line + "\n").getBytes());
if (line.equalsIgnoreCase("bye")) {
break;
}
System.out.println("Read resp from remote:" + scanner.nextLine());
}
socket.close();
}
}
NIO
同步非阻塞的IO, 在JDK 1.4的時候引入. 主要邏輯就是基於Selector模式來註冊自己關注的事件, 然後當關注的事件發生時,進一步處理IO請求.
NIO裏面有2個很重要的組件就是Reactor和Handler
Reactor: 一個單線程的程序, 用於不斷收集和分發關心的IO事件.
Handler:讀取或者處理IO請求, 可以在Reactor的線程裏面做,或者爲了提高性能在單獨的線程池裏面做.
NIO Echo server:
package io.nio;
import io.common.ServerInfo;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NioServer {
private static class TaskHandler implements Runnable {
private SocketChannel clientChannel;
public TaskHandler(SocketChannel clientChannel) {
this.clientChannel = clientChannel;
}
@Override
public void run() {
ByteBuffer buf = ByteBuffer.allocate(50);
try {
boolean flag = true;
while (flag) {
buf.clear();
int read = clientChannel.read(buf);
String readMessage = new String(buf.array(), 0, read);
String writeMessage = "[Echo] " + readMessage + "\n";
if ("bye".equalsIgnoreCase(readMessage)) {
writeMessage = "[Exit] byebye" + "\n";
flag = true;
}
// 寫返回數據
buf.clear();
buf.put(writeMessage.getBytes());
buf.flip(); // 重置緩衝區讓其輸出
clientChannel.write(buf);
}
clientChannel.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
ExecutorService es = Executors.newFixedThreadPool(10);
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(ServerInfo.PORT));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服務啓動在-" + ServerInfo.PORT);
while (selector.select() > 0) {
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
// 提交一個任務去處理
es.submit(new TaskHandler(socketChannel));
}
}
}
}
}
}
NIO Echo client
package io.nio;
import io.common.InputUtil;
import io.common.ServerInfo;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NioClient {
public static void main(String[] args) throws Exception {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(ServerInfo.HOST, ServerInfo.PORT));
ByteBuffer buf = ByteBuffer.allocate(50);
boolean flag = true;
while (flag) {
buf.clear();
String input = InputUtil.getLine("Input something:");
if (input.equalsIgnoreCase("bye")) {
flag = false;
}
input += "\n";
buf.put(input.getBytes());
buf.flip();
socketChannel.write(buf);
buf.clear();
int read = socketChannel.read(buf);
String readMessage = new String(buf.array(), 0, read);
System.out.println("Read resp - " + readMessage);
}
socketChannel.close();
}
}
AIO
AIO 異步非阻塞IO, 前面的NIO在讀和寫時實際上都是發生在當前的業務線程中. 而AIO不同, AIO是在IO完成後,通知你說,你可以接着做剩下的事了. 注意體會這裏與NIO的不同之處. NIO是告訴你說 有東西可以讀取了, 然後你得自己去負責讀取.
這就是NIO和AIO的最大的區別.
AIO怎麼實現的?
實際上AIO與NIO都是基於同一套IO框架實現的.
在Linux是EPOLL或者KQueue (kqueue在mac,bsd上有).
具體代碼在:UnixAsynchronousSocketChannelImpl
以及不同的實現:
在Windows上是基於Windows內核提供的IOCP 實現的(IOCP: windows 內核提供的一種異步IO操作 通過消息隊列的方式來通知IO消息)
具體代碼在: WindowsAsynchronousSocketChannelImpl
當我們深入查看AIO的實現時,可以發現實際上AIO只是一種API層級的更新(內部有一個While true 循環)當有數據讀取時,自動調用對應的Callback. 正是因爲如此, 他們的性能並沒有太大的差距.
AIO Echo Server:
package io.aio;
import io.common.ServerInfo;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;
public class AioServer {
public static void main(String[] args) throws Exception {
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(ServerInfo.PORT));
serverSocketChannel.accept(null, new AcceptFinishHandler());
System.out.println("Server 啓動了 - " + ServerInfo.PORT);
CountDownLatch running = new CountDownLatch(1);
running.await();
}
static class ReadFinishHandler implements CompletionHandler<Integer, AsynchronousSocketChannel> {
private ByteBuffer buf;
public ReadFinishHandler(ByteBuffer buf) {
this.buf = buf;
}
@Override
public void completed(Integer result, AsynchronousSocketChannel attachment) {
System.out.println("Read resp from client is:");
String readStr = new String(buf.array(), 0, result);
System.out.println(readStr);
ByteBuffer buf = ByteBuffer.allocate(512);
buf.put(("[ECHO] " + readStr.trim() + "\n").getBytes());
buf.flip();
attachment.write(buf, attachment, new WriteFinishHandler());
}
@Override
public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
System.out.println("Error when reading");
exc.printStackTrace();
}
}
static class WriteFinishHandler implements CompletionHandler<Integer, AsynchronousSocketChannel> {
@Override
public void completed(Integer result, AsynchronousSocketChannel attachment) {
// 數據已經寫完了 等待下一次的數據讀取
System.out.println("write has finished and write bytes - " + result);
ByteBuffer buf = ByteBuffer.allocate(512);
attachment.read(buf, attachment, new ReadFinishHandler(buf));
}
@Override
public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
System.out.println("Error during writing");
exc.printStackTrace();
}
}
static class AcceptFinishHandler implements CompletionHandler<AsynchronousSocketChannel, Object> {
@Override
public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
// 因爲我們是服務端 那麼此時我們應該要準備讀取數據了
ByteBuffer buf = ByteBuffer.allocate(512);
socketChannel.read(buf, socketChannel, new ReadFinishHandler(buf));
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("Fail to accept");
exc.printStackTrace();
}
}
}
AIO Echo client:
package io.aio;
import io.common.InputUtil;
import io.common.ServerInfo;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;
/**
* AIO的client 可以看到 大多數時候 它都是註冊的一個個的Callback/handler
* 而且是完全異步的.
*/
public class AioClient {
public static void main(String[] args) throws Exception {
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
socketChannel.connect(new InetSocketAddress(ServerInfo.PORT), socketChannel,new ConnectFinishHandler());
CountDownLatch running = new CountDownLatch(1);
running.await();
}
static class ConnectFinishHandler implements CompletionHandler<Void, AsynchronousSocketChannel> {
@Override
public void completed(Void result, AsynchronousSocketChannel socket) {
// 當連接完成
// 讓我們從準備讀取數據並寫入到socket
String readStr = InputUtil.getLine("Input something:");
ByteBuffer buf = ByteBuffer.allocate(512);
buf.put((readStr.trim() + "\n").getBytes());
buf.flip();
socket.write(buf, socket, new WriteFinishHandler());
}
@Override
public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
System.out.println("Error");
exc.printStackTrace();
}
}
static class ReadFinishHandler implements CompletionHandler<Integer, AsynchronousSocketChannel> {
private ByteBuffer buf;
public ReadFinishHandler(ByteBuffer buf) {
this.buf = buf;
}
@Override
public void completed(Integer result, AsynchronousSocketChannel attachment) {
System.out.println("Read resp from remote is:");
System.out.println(new String(buf.array(), 0, result));
// let's get a new string from cmd line
String readStr = InputUtil.getLine("Input something:");
ByteBuffer buf = ByteBuffer.allocate(512);
buf.put((readStr.trim() + "\n").getBytes());
buf.flip();
attachment.write(buf, attachment, new WriteFinishHandler());
}
@Override
public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
System.out.println("Error when reading");
exc.printStackTrace();
}
}
static class WriteFinishHandler implements CompletionHandler<Integer, AsynchronousSocketChannel> {
@Override
public void completed(Integer result, AsynchronousSocketChannel attachment) {
// already finish writing
// let's waiting the resp
ByteBuffer buf = ByteBuffer.allocate(512);
attachment.read(buf, attachment, new ReadFinishHandler(buf));
}
@Override
public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
System.out.println("Error when writing");
exc.printStackTrace();
}
}
}
Reactor 和 Proactor
他們兩個都是event-driven 的IO模型.
不同的是:
比較: Reactor: 程序一直等,直到收到Event說, 這個Socket可以讀取了, 然後業務線程從它裏面讀取.
Proactor: 程序等待, 直到一個socket讀取操作完成. (此時數據應該已在buffer中)
reference
1.github code
2.2種IO設計模式
3.3種IO