Java AIO 编程
轉(zhuǎn)載自?java aio 編程
Java NIO (JSR 51)定義了Java new I/O API,提案2000年提出,2002年正式發(fā)布。 JDK 1.4起包含了相應(yīng)的API實(shí)現(xiàn)。
JAVA NIO2 (JSR 203)定義了更多的 New I/O APIs, 提案2003提出,直到2011年才發(fā)布, 最終在JDK 7中才實(shí)現(xiàn)。
JSR 203除了提供更多的文件系統(tǒng)操作API(包括可插拔的自定義的文件系統(tǒng)), 還提供了對socket和文件的異步 I/O操作。 同時實(shí)現(xiàn)了JSR-51提案中的socket channel全部功能,包括對綁定, option配置的支持以及多播multicast的實(shí)現(xiàn)。
當(dāng)前很多的項(xiàng)目還停留在JAVA NIO的實(shí)現(xiàn)上, 對JAVA AIO(asynchronous I/O)著墨不多。 本文整理了一些關(guān)于JAVA AIO的介紹,以及netty對AIO的支持。
以下內(nèi)容只針對socket的I/O操作, 不涉及對文件的處理。
JDK AIO API
首先介紹以下I/O模型。
Unix定義了五種I/O模型, 下圖是五種I/O模型的比較。
- 阻塞I/O
- 非阻塞I/O
- I/O復(fù)用(select、poll、linux 2.6種改進(jìn)的epoll)
- 信號驅(qū)動IO(SIGIO)
- 異步I/O(POSIX的aio_系列函數(shù))
UNP Ch6.2 I/O models
POSIX把I/O操作劃分成兩類:
- 同步I/O: 同步I/O操作導(dǎo)致請求進(jìn)程阻塞,直至操作完成
- 異步I/O: 異步I/O操作不導(dǎo)致請求阻塞
所以Unix的前四種I/O模型都是同步I/O, 只有最后一種才是異步I/O。
傳統(tǒng)的Java BIO (blocking I/O)是Unix I/O模型中的第一種。
Java NIO中如果不使用select模式,而只把channel配置成nonblocking則是第二種模型。
Java NIO select實(shí)現(xiàn)的是一種多路復(fù)用I/O。 底層使用epoll或者相應(yīng)的poll系統(tǒng)調(diào)用, 參看我以前整理的一篇文章:?java 和netty epoll實(shí)現(xiàn)?
第四種模型JDK應(yīng)該是沒有實(shí)現(xiàn)。
Java NIO2增加了對第五種模型的支持,也就是AIO。
OpenJDK在不同平臺上的AIO實(shí)現(xiàn)
在不同的操作系統(tǒng)上,AIO由不同的技術(shù)實(shí)現(xiàn)。
通用實(shí)現(xiàn)可以查看這里。
Windows上是使用完成接口(IOCP)實(shí)現(xiàn),可以參看WindowsAsynchronousServerSocketChannelImpl,
其它平臺上使用aio調(diào)用UnixAsynchronousServerSocketChannelImpl,?UnixAsynchronousSocketChannelImpl,?SolarisAsynchronousChannelProvider
常用類
- AsynchronousSocketChannel
- Asynchronous connect
- Asynchronous read/write
- Asynchronous scatter/gather (multiple buffers)
- Read/write operations support timeout
- failed method invoked with timeout exception
- Implements NetworkChannel for binding, setting socket options, etc
AsynchronousServerSocketChannel
還實(shí)現(xiàn)了Asynchronous accept
AsynchronousDatagramChannel
- Asynchronous read/write (connected)
- Asynchronous receive/send (unconnected)
- Implements NetworkChannel for binding, setting socket options, etc.
- Implements MulticastChannel
- CompletionHandler
Java AIO 例子
異步channel API提供了兩種方式監(jiān)控/控制異步操作(connect,accept, read,write等)。第一種方式是返回java.util.concurrent.Future對象, 檢查Future的狀態(tài)可以得到操作是否完成還是失敗,還是進(jìn)行中, future.get阻塞當(dāng)前進(jìn)程。
第二種方式為操作提供一個回調(diào)參數(shù)java.nio.channels.CompletionHandler,這個回調(diào)類包含completed,failed兩個方法。
channel的每個I/O操作都為這兩種方式提供了相應(yīng)的方法, 你可以根據(jù)自己的需要選擇合適的方式編程。
下面以一個最簡單的Time服務(wù)的例子演示如何使用異步I/O。 客戶端連接到服務(wù)器后服務(wù)器就發(fā)送一個當(dāng)前的時間字符串給客戶端。 客戶端毋須發(fā)送請求。 邏輯很簡單。
Server實(shí)現(xiàn)
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.AsynchronousChannelGroup; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.util.Date; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; public class Server {private static Charset charset = Charset.forName("US-ASCII");private static CharsetEncoder encoder = charset.newEncoder();public static void main(String[] args) throws Exception {AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(4));AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress("0.0.0.0", 8013));server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {@Overridepublic void completed(AsynchronousSocketChannel result, Void attachment) {server.accept(null, this); // 接受下一個連接try {String now = new Date().toString();ByteBuffer buffer = encoder.encode(CharBuffer.wrap(now + "\r\n"));//result.write(buffer, null, new CompletionHandler<Integer,Void>(){...}); //callback orFuture<Integer> f = result.write(buffer);f.get();System.out.println("sent to client: " + now);result.close();} catch (IOException | InterruptedException | ExecutionException e) {e.printStackTrace();}}@Overridepublic void failed(Throwable exc, Void attachment) {exc.printStackTrace();}});group.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);} }Client實(shí)現(xiàn)這個例子使用了兩種方式。?accept使用了回調(diào)的方式, 而發(fā)送數(shù)據(jù)使用了future的方式。
public class Client {public static void main(String[] args) throws Exception {AsynchronousSocketChannel client = AsynchronousSocketChannel.open();Future<Void> future = client.connect(new InetSocketAddress("127.0.0.1", 8013));future.get();ByteBuffer buffer = ByteBuffer.allocate(100);client.read(buffer, null, new CompletionHandler<Integer, Void>() {@Overridepublic void completed(Integer result, Void attachment) {System.out.println("client received: " + new String(buffer.array()));}@Overridepublic void failed(Throwable exc, Void attachment) {exc.printStackTrace();try {client.close();} catch (IOException e) {e.printStackTrace();}}});Thread.sleep(10000);} }Netty AIO客戶端也使用了兩種方式,?connect使用了future方式,而接收數(shù)據(jù)使用了回調(diào)的方式。
Netty也支持AIO并提供了相應(yīng)的類:?AioEventLoopGroup,AioCompletionHandler,?AioServerSocketChannel,?AioSocketChannel,?AioSocketChannelConfig。
其它使用方法和NIO類似。
io.netty.buffer和java.nio.ByteBuffer的區(qū)別
官方文檔Using as a generic library描述了兩者的區(qū)別,主要還是友好性,擴(kuò)展和性能的考慮。
http://zizihaier.iteye.com/blog/1767409也提到:
ByteBuffer主要有兩個繼承的類分別是:HeapByteBuffer和MappedByteBuffer。他們的不同之處在于HeapByteBuffer會在JVM的堆上分配內(nèi)存資源,而MappedByteBuffer的資源則會由JVM之外的操作系統(tǒng)內(nèi)核來分配。DirectByteBuffer繼承了MappedByteBuffer,采用了直接內(nèi)存映射的方式,將文件直接映射到虛擬內(nèi)存,同時減少在內(nèi)核緩沖區(qū)和用戶緩沖區(qū)之間的調(diào)用,尤其在處理大文件方面有很大的性能優(yōu)勢。但是在使用內(nèi)存映射的時候會造成文件句柄一直被占用而無法刪除的情況,網(wǎng)上也有很多介紹。
Netty中使用ChannelBuffer來處理讀寫,之所以廢棄ByteBuffer,官方說法是ChannelBuffer簡單易用并且有性能方面的優(yōu)勢。在ChannelBuffer中使用ByteBuffer或者byte[]來存儲數(shù)據(jù)。同樣的,ChannelBuffer也提供了幾個標(biāo)記來控制讀寫并以此取代ByteBuffer的position和limit,分別是:
0 <= readerIndex <= writerIndex <= capacity,同時也有類似于mark的markedReaderIndex和markedWriterIndex。當(dāng)寫入buffer時,writerIndex增加,從buffer中讀取數(shù)據(jù)時readerIndex增加,而不能超過writerIndex。有了這兩個變量后,就不用每次寫入buffer后調(diào)用flip()方法,方便了很多。
Netty的零拷貝(zero copy)
Netty的“零拷貝”主要體現(xiàn)在如下三個方面:
1) Netty的接收和發(fā)送ByteBuffer采用DIRECT BUFFERS,使用堆外直接內(nèi)存進(jìn)行Socket讀寫,不需要進(jìn)行字節(jié)緩沖區(qū)的二次拷貝。如果使用傳統(tǒng)的堆內(nèi)存(HEAP BUFFERS)進(jìn)行Socket讀寫,JVM會將堆內(nèi)存Buffer拷貝一份到直接內(nèi)存中,然后才寫入Socket中。相比于堆外直接內(nèi)存,消息在發(fā)送過程中多了一次緩沖區(qū)的內(nèi)存拷貝。
2) Netty提供了組合Buffer對象,可以聚合多個ByteBuffer對象,用戶可以像操作一個Buffer那樣方便的對組合Buffer進(jìn)行操作,避免了傳統(tǒng)通過內(nèi)存拷貝的方式將幾個小Buffer合并成一個大的Buffer。
3) Netty的文件傳輸采用了transferTo方法,它可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo)Channel,避免了傳統(tǒng)通過循環(huán)write方式導(dǎo)致的內(nèi)存拷貝問題。
參考
總結(jié)
以上是生活随笔為你收集整理的Java AIO 编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 初一饺子初二面什么意思 怎么理解初一饺子
- 下一篇: Java面试,如何在短时间内做突击