jdk7:淺談 AIO NIO2.0

1、概述

JDK在1.4引入NIO(同步非阻塞)包以後,終於在1.7版本加入了異步IO的AIO。
同步異步阻塞和非阻塞等概念,建議參考 《Unix網絡編程》 卷1. ,這裏只談AIO的api。編程

2、主要的類

  • AsynchronousSocketChannelapi

  • AsynchronousServerSocketChannel網絡

  • AsynchronousFileChannel異步

  • AsynchronousDatagramChannelsocket

3、經過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。this

public interface CompletionHandler<V,A> {
    //調用結果,附件
    void completed(V result, A attachment);
    //異常對象、附件
    void failed(Throwable exc, A attachment);
}

AioAcceptHandlercode

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();
    }
}

AioReadHandlerserver

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.");
    }
}
相關文章
相關標籤/搜索