一、概述
JDK在1.4引入NIO(同步非阻塞)包之後,終於在1.7版本加入了非同步IO的AIO。
同步非同步阻塞和非阻塞等概念,建議參考 《Unix網路程式設計》 卷1. ,這裡只談AIO的api。
二、主要的類
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
三、通過aio實現server
先看下AsynchronousServerSocketChannel,它有兩個構造方法,選擇哪個構造方法,也就選擇了不同的程式設計模型,分別是Future機制和Handler回撥機制。Future機制適合需要同步等待獲取結果的,Handler機制則看上去則更像純非同步。當然,結果都是一樣的,看自己需要或者喜歡哪個模型吧。
public abstract Future<AsynchronousSocketChannel> accept();
public abstract <A> void accept(A attachment,
CompletionHandler<AsynchronousSocketChannel,? super A> handler);
先看看AsynchronousServerSocketChannel來啟動一個server,程式碼如下。注意accept,無論用future還是handler,這裡的呼叫都是非阻塞的立即返回。這裡選用handler的方式,如果有客戶端連線上來,handler的會被回撥。
public class AioServer {
public final int port = 8080;
public final int backlog = 2; //跟bio和nio的backlog其實是一樣的。指定accpet等待佇列的長度
private AioAcceptHandler acceptHandler;
private AsynchronousServerSocketChannel serverSocket;
public static void main(String[] args) throws Exception {
new AioServer().startup();
}
private void startup() throws Exception {
int availableProcessors = Runtime.getRuntime().availableProcessors();
ExecutorService channelWorkers = Executors.newFixedThreadPool(availableProcessors * 2);
AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withCachedThreadPool(channelWorkers , 1);
serverSocket = AsynchronousServerSocketChannel.open(channelGroup);
serverSocket.bind(new InetSocketAddress(port), backlog);
acceptHandler = new AioAcceptHandler();
accept();
}
public void accept() {
serverSocket.accept(this, acceptHandler); //非阻塞
}
}
接下來CompletionHandler的介面吧,程式碼如下。看到泛型的 V result 就說嘛,當我們使用CompletionHandler的時候,自己需要清楚的知道返回結果是什麼。
比如accept中指定的CompletionHandler。它是處理接受連線的,成功返回的話,結果就是套接字,那麼我們就要指定泛型V的實際型別為AsynchronousSocketChannel。
再比如read方法中指定CompletionHandler。read是將資料讀取到ByteBuffer,而回撥CompletionHandler的時候,結果V是讀取的數量。所以我們就要指定泛型V的實際型別為Integer。
具體可以看下面的實現程式碼 AioAcceptHandler 和 AioReadHandler。
public interface CompletionHandler<V,A> {
//呼叫結果,附件
void completed(V result, A attachment);
//異常物件、附件
void failed(Throwable exc, A attachment);
}
AioAcceptHandler
public class AioAcceptHandler implements CompletionHandler<AsynchronousSocketChannel, AioServer>{
public void completed(AsynchronousSocketChannel socket, AioServer aioServer) {
try {
System.out.printf("客戶端%s連線成功.\n", socket.getRemoteAddress().toString());
readData(socket);
} catch (Exception e) {
e.printStackTrace();
try {
socket.close();
} catch (IOException e1) {}
} finally {
aioServer.accept();
}
}
private void readData(AsynchronousSocketChannel socket) {
ByteBuffer buf = ByteBuffer.allocate(32); //測試時,可以不設定太大,觀察aio的多次read
socket.read(buf, buf, new AioReadHandler(socket));
}
public void failed(Throwable exc, AioServer aioServer) {
exc.printStackTrace();
}
}
AioReadHandler
public class AioReadHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel socket;
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
public AioReadHandler(AsynchronousSocketChannel socket){
this.socket = socket;
}
public void completed(Integer result, ByteBuffer buf) {
System.out.println("result = " + result + " buf = " + buf);
if (result > 0) {
buf.flip();
try {
baos.write(buf.array());
} catch (IOException e) {
e.printStackTrace();
}
buf.clear();
socket.read(buf, buf, this);
} else if (result == -1) { //result為-1的時候,客戶端的socket已經正常關閉。
try {
System.out.printf("客戶端%s已經斷開.\n", socket.getRemoteAddress().toString());
String info = new String(baos.toByteArray(), Charset.forName("UTF8"));
System.out.println(info);
} catch (Exception e) {
e.printStackTrace();
} finally {
buf = null;
try {
socket.close();
} catch (IOException e) {}
}
}
}
public void failed(Throwable exc, ByteBuffer buf) {
exc.printStackTrace();
}
}
基本上,一個簡單的AIOServer就上面這點程式碼拉。實際上我感覺程式碼比NIO用Selector的方式還是簡單清晰多了。至於aio的原理,實際上就是去看看epoll等資料就知道了。
這裡附上一個測試的客戶端程式碼:
public class BioClient {
public static void main(String[] args) throws Exception {
String txt = "美國在釋出的朝鮮軍力評估報告中也認為:“朝鮮發展航天運載工具對開發射程可達美國的遠端導彈意義重要,射程也許能覆蓋美國部分地區。不過,由於朝鮮還沒有研發出能夠重返大氣層的運載火箭,所以‘大浦洞2號’尚不具備搭載彈頭的攻擊能力。”";
Socket socket = new Socket("localhost", 8080);
PrintStream print = new PrintStream(socket.getOutputStream());
print.print(txt);
print.close();
socket.close();
System.out.println("ok.");
}
}