AIO入門實例
摘要:閱讀《Netty權威指南》筆記
AIO概念:
AIO即JDK7開始引入的NIO,提供了異步文件通道和異步到街子通道的實現。異步通道提供兩種方式操作結果:
- 通過java.util.concurrent.Future類來表示異步操作的結果;
- 在執行異步操作時傳入一個java.nio.channels.CompletionHandler接口的實現類作爲操作完成的回調(本例子主要使用這個方法)。
AIO創建的TimeServer
public class TimeServer {
public static void main(String[] args) {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
}
}
AsyncTimeServerHander timeServerHander = new AsyncTimeServerHander(port);
new Thread(timeServerHander, "AIO-AsyncTimeServerHandler-001").start();
}
}
很簡單,就是初始化一個線程,接下來看AsyncTimeServerHander 的實現代碼:
public class AsyncTimeServerHander implements Runnable {
private int port;
CountDownLatch latch;
AsynchronousServerSocketChannel asynchronousServerSocketChannel;
/**
* 構造方法,創建一個異步的服務端通道AsynchronousServerSocketChannel,調用它的bind方法綁定監
* 聽端口,綁定成功則打印提示到輸入臺
*
* @param port
*/
public AsyncTimeServerHander(int port) {
this.port = port;
try {
asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
System.out.println("The time server is start in port " + port);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//阻塞當前線程,防止服務端異常退出
latch = new CountDownLatch(1);
doAccept();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 連接客戶端的方法,採用的CompletionHandler方法,接收AcceptCompletionHandler作爲採用的CompletionHandler方
* 法實例
*/
public void doAccept() {
asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());
}
}
CompletionHandler接口有兩個方法:
public void completed(AsynchronousSocketChannel result, AsyncTimeServerHander attachment)
public void failed(Throwable exc, AsyncTimeServerHander attachment)
根據方法名很容易就能理解兩個方法的執行時間。
接下來看整個AcceptCompletionHandler
的代碼:
public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncTimeServerHander> {
@Override
public void completed(AsynchronousSocketChannel result, AsyncTimeServerHander attachment) {
/**
* 接收客戶端請求,因爲AsynchronousServerSocketChannel可以接收成千上萬的客戶端,
* 所以回調AsyncTimeServerHander中asynchronousServerSocketChannel.accept方法,
* 讓新的客戶端繼續接入,最終形成一個循環
*/
attachment.asynchronousServerSocketChannel.accept(attachment, this);
ByteBuffer buffer = ByteBuffer.allocate(1024);
/**異步讀操作
* 第一個buffer:接收緩衝區
* 第二個buffer:異步Channel攜帶的附件,通知回調的時候作爲入參使用(我的理解是當該Channel繼續被調用時的參數)
* CompletionHandler:接收通知回調的業務Handler,這裏的實現類是ReadCompletionHandler
*/
result.read(buffer, buffer, new ReadCompletionHandler(result));
}
@Override
public void failed(Throwable exc, AsyncTimeServerHander attachment) {
attachment.latch.countDown();
}
}
從AcceptCompletionHandler
就可以看出,主要是通過不斷的回調來實現非阻塞,同時不會對像NIO那樣需要不斷的判斷連接的狀態去根據具體的狀態分配Handler。
繼續往下看ReadCompletionHandler
public class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel channel;
/**
* 構造方法:將AsynchronousSocketChannel作爲構造方法的參數傳入,作爲成員變量使用,
* 主要用於半包消息和發送應答
*
* @param channel
*/
public ReadCompletionHandler(AsynchronousSocketChannel channel) {
if (channel != null) {
this.channel = channel;
}
}
/**
* 讀取到消息的處理
*
* @param result
* @param attachment
*/
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
byte[] body = new byte[attachment.remaining()];
attachment.get(body);
try {
String req = new String(body, "UTF-8");
System.out.println("The time server receive order : " + req);
//消息判斷,如果是正確的消息,調用doWrite發送當前消息到客戶端
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ?
new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
doWrite(currentTime);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
/**
* 發送消息到客戶端
*
* @param currentTime
*/
private void doWrite(String currentTime) {
if (currentTime != null && currentTime.trim().length() > 0) {
byte[] bytes = currentTime.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
//遞歸調用,如果沒有發送完成,繼續發送
if (attachment.hasRemaining()) {
channel.write(attachment, attachment, this);
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
channel.close();
} catch (IOException e) {
//ingore on close
}
}
});
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
this.channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ReadCompletionHandler
的主要功能已經在代碼裏註釋好了,理解起來也不是很難
總結
- AIO使用起來邏輯上很好理解,但是因爲有很多的匿名函數,導致代碼看起來很複雜;
- AIO非阻塞的思想其實和NIO一樣,都是通過不斷輪詢
AsynchronousServerSocketChannel
的accept
方法獲取連接,分配對應的Handler
去處理業務; - demo很簡單,重要的是對代碼的理解以及熟練使用API。