Java的BIO彻消,NIO,AIO

1. 前言

有一些概念總是Java I/O一塊出現(xiàn)宙拉,比如同步與異步,阻塞與非阻塞丙笋,這些概念往往也是非常難以區(qū)分谢澈。在介紹Java I/O之前,本文先通俗地介紹一下這兩組概念的區(qū)別:

  • 同步和異步: 同步和異步的區(qū)分點在消息的通知機制御板。
    如果是程序主動獲取消息锥忿,為同步,程序被動獲取消息怠肋,為異步敬鬓。例如燒水,如果我們時不時去看看水是否燒開,則為同步钉答。而如果水壺是會響笛的水壺础芍,我們聽見響笛則認為水燒開了,則為異步数尿。而對于程序仑性,如果是程序輪詢結(jié)果或者直接等待結(jié)果,為同步右蹦。如果程序調(diào)用了然后立刻返回诊杆,結(jié)果等待被調(diào)用方通知,或者回調(diào)何陆,則為異步晨汹。重點在獲取調(diào)用的結(jié)果的方式。
  • 阻塞和非阻塞: 區(qū)分點則在等待程序調(diào)用結(jié)果時贷盲,程序所處的狀態(tài)宰缤。
    例如燒水,如果在燒水的過程中晃洒,我們一直等著慨灭,啥事都不干,則為阻塞球及。如果我們在燒水的過程中氧骤,繼續(xù)干著別的事,則為非阻塞吃引。重點在獲取程序調(diào)用結(jié)果的筹陵,程序所處于的狀態(tài)。

總的來說镊尺,對于一個程序中調(diào)用過程來說朦佩,獲取調(diào)用結(jié)果的方式,決定了程序是同步(主動)還是異步(被動)庐氮。而在獲取調(diào)用結(jié)果的過程中语稠,程序所處的狀態(tài)途乃,決定了程序是阻塞(掛起)還是非阻塞(處理其他的事情)

2. Java I/O的發(fā)展歷程

Java I/O的發(fā)展一般來說主要是分為三個階段:

  • 第一個階段:在JDK 1.0到JDK 1.3中誊涯,Java的I/O類庫是非常簡單的羹与,很多UNIX網(wǎng)絡(luò)編程中的概念或者接口在Java I/O類庫中都沒有體現(xiàn)账胧。通常比藻,我們這種類型的I/O為BIO赔硫,即Blocking I/O伞梯。
  • 第二個階段:在JDK 1.4中蛛蒙,java 新增加了java.nio包衣式,正式引入了NIO(Non-blocking I/O)寸士,提供了異步開發(fā)I/O的API和類庫檐什。Java NIO主要由Selector,ByteBuffer和Channel三個核心部分組成弱卡。
  • 第三個階段:JDK1.7正式發(fā)布乃正,java對NIO進行了升級,被稱為NIO2.0谐宙,也稱為AIO烫葬,支持文件的異步I/O以及網(wǎng)絡(luò)的異步操作
3. BIO凡蜻、NIO以及AIO

網(wǎng)絡(luò)Socket編程的一般類型是Server/client類型的搭综,即兩個進程之間的通信。Server端通過綁定端口號建立Socket監(jiān)聽連接划栓,而Client端通過指定Ip地址和端口號通過三次握手建立雙方的連接兑巾,如果連接成功,雙方就通過Socket進行通信忠荞。
在第2部分蒋歌,簡單的介紹了BIO,NIO以及AIO的概念委煤,在本部分主要通過實例代碼來展示三個的關(guān)鍵點堂油。

1.BIO

當Server和Client端采用BIO形式,雙方通過輸入流和輸出流通過同步阻塞的方式進行通信碧绞。
采用BIO通信方式的Server端府框,一般由一個單獨的Acceptor線程來監(jiān)聽客戶端的連接請求,Server接收到Client端的連接后讥邻,就為每一個client建立新的線程迫靖,進行鏈路處理,通過輸入流發(fā)送響應(yīng)到客戶端兴使,銷毀線程系宜,整個socket通信流程結(jié)束。這是典型的一請求一應(yīng)答模式的发魄。

以下示例的socket通信流程盹牧,模擬客戶端發(fā)送http請求給服務(wù)器端,如果請求的路徑為登錄地址欠母,則返回已經(jīng)登錄欢策,如果請求路徑不為登錄地址,則返回還未登錄赏淌。

Server端監(jiān)聽8080端口通過輪詢的方式不斷監(jiān)聽client的連接請求,等待client的連接(serverSocket.accept())啄清。當沒有client連接server的時候六水,線程阻塞在accept()處俺孙。當Server接收到client的連接請求,新建一個線程進行鏈路業(yè)務(wù)處理掷贾。

服務(wù)器端Acceptor線程(主線程)代碼如下:

public class InfoServer {
    public static void main(String args[]){
        try{
            ServerSocket serverSocket = new ServerSocket(8080);
            Socket socket = null;
            while(true){
                System.out.println("socket listening");
                socket = serverSocket.accept(); //線程阻塞在此處
                new Thread(new LoginCheckThread(socket)).start();
                System.out.println("socket accepted");
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

在鏈路處理線程中睛榄,代碼流程為:

  • 首先根據(jù)socket的輸入流產(chǎn)生字符流BufferReader對象;
  • 通過BufferReader對象的readline()方法想帅,讀取client端傳過來的數(shù)據(jù)场靴。readline()方法當讀取到換行符'\n'或'\r'時才返回。
  • 最后港准,從socket的輸出流中產(chǎn)生了字符流PrintWriter對象旨剥,通過PrintWriter對象發(fā)送響應(yīng)內(nèi)容。

鏈路處理線程為:

public class LoginCheckThread implements Runnable {

    private Socket socket = null;
    public LoginCheckThread(Socket socket){
        this.socket = socket;
    }

    public void run(){
        try{
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter printWriter = new PrintWriter(socket.getOutputStream(),true);
            String content = bufferedReader.readLine();
            System.out.println(Thread.currentThread().getName()+"   "+content);
            //獲取訪問的域名,如果是登錄請求浅缸,則返回已經(jīng)登錄轨帜,否則提示沒有登錄
            if(content.split(" ")[1].equals("/a/login")){
                printWriter.println("you are not login in this system!");
            }else{
                printWriter.println("you have login in this system!");
            }
            socket.close();
        }catch(Exception e){
            e.printStackTrace();
        }

    }
}

客戶端的代碼:

客戶端通過指定IP地址和端口,嘗試連接server端口衩椒,連接上Server端后蚌父,通過PrinterWriter對象想Server端發(fā)送請求數(shù)據(jù)。發(fā)送完請求數(shù)據(jù)之后毛萌,client通過BufferReader的read(Char[])方法來讀取Server端的響應(yīng)數(shù)據(jù)苟弛。需要注意到是read(char[])會產(chǎn)生阻塞,read(char[])方法只有在以下三種情況下才會返回:

  • 讀取到足夠多的字節(jié)阁将;
  • 讀取輸入流的終止符膏秫;
  • 發(fā)生IO異常
public class Client {

    public static void main(String args[]){
        try{
            Socket socket = new Socket("127.0.0.1",8080);
            PrintWriter printWriter = new PrintWriter(socket.getOutputStream());
            printWriter.println();
            printWriter.flush();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            char[] reponseChar = new char[1024];
            //read方法是阻塞方法,程序在此處會產(chǎn)生阻塞
            bufferedReader.read(reponseChar); 
            System.out.println("repose:"+new String(reponseChar));
            socket.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

2.NIO

NIO即非阻塞I/O(Non-blocking I/O),NIO類庫提供了 對應(yīng)的ServerSocketChannel和SocketChannel兩種不同套接字的通道實現(xiàn)冀痕,ServerSocketChannel和Socketchannel分別于BIO中的ServerSocket和Socket對應(yīng)荔睹。這兩種新增的通道都支持阻塞和非阻塞模式,阻塞模式使用起來更加的簡單言蛇,但是性能和可靠性上都不好僻他,非阻塞模式卻正好相反。

Java NIO由Channel腊尚、Buffer吨拗、Selector三個核心部分組成。Channel和Buffer與操作系統(tǒng)的IO方式更加接近婿斥,所以性能上會比傳統(tǒng)的AIO要好劝篷。

在NIO中,基本上所有的IO中都是從一個Channel開始民宿。Channel譯為通道娇妓,與流不同的是,通道同時讀和寫活鹰。數(shù)據(jù)可以從Channel中讀到Buffer中哈恰,也可以從Buffer中寫到Channel中只估。Selector(選擇器)是能夠檢測一個到多個NIO通道,并能夠知曉通道是否為諸如讀寫事件做好準備的組件着绷。通過Selector蛔钙,一個單獨的線程可以管理多個Channel,從而管理多個網(wǎng)絡(luò)連接荠医。

如何使用NIO來進行網(wǎng)絡(luò)編程:

  • 創(chuàng)建Selector:通過Selector.open()可以創(chuàng)建Selector對象吁脱;
  • 創(chuàng)建Channel:Channel分為ServerSocketChannel和SocketChannel。在Server端彬向,通過ServerSocketChannel.open()可以創(chuàng)建Server端監(jiān)聽通道ServerSocketChannel兼贡,在Client端可以通過SocketChannel.open()可以打開連接通道SocketChannel對象;
  • 向Selector中注冊Channel及感興趣的事件(OP_READ,OP_WRITE,OP_CONNECT,OP_ACCEPT);
    • ServerSocketChannel可以向Selector注冊O(shè)P_ACCEPT幢泼,而SocketChannel可以向Seletor注冊O(shè)P_READ,OP_WRTIE,OP_CONNECT;
  • 輪詢Selector紧显,獲取就緒的Channel(通過Selector.select()及其他重載方法)
  • 針對特定的Channel進行業(yè)務(wù)上的處理

下面通過具體的代碼來說明如何進行NIO編程

InfoServer類為Server端的啟動類缕棵,通過新建線程的形式啟動Server端的監(jiān)聽線程孵班。

public class InfoServer {
    public static void main(String args[]){
        new Thread(new LoginCheckTask(8080)).start();
    }
}

LoginCheckTask類實現(xiàn)了網(wǎng)絡(luò)監(jiān)聽、網(wǎng)絡(luò)連接及請求處理的操作招驴。在構(gòu)造函數(shù)對Server端進行了網(wǎng)絡(luò)初始化篙程,包括獲得Selector對象、ServersocketChannel對象别厘、設(shè)置Socket參數(shù)虱饿,最后還向Selector注冊了當前ServerSocketChannel通道的OP_ACCERT事件。

在NIO中触趴,通道Channel要么從緩沖器獲得數(shù)據(jù)氮发,要么向緩沖器發(fā)送數(shù)據(jù)。唯一直接與通道交互的緩沖器是ByteBuffer冗懦。

當server端通道中監(jiān)聽到client端的連接后爽冕,建立與Client端連接的SocketChannel,并向該Socketchannel中注冊O(shè)P_READ事件。
當多路復(fù)用器Selector檢測到OP_READ事件就緒后披蕉,就從該SocketChannel的的緩沖器ByteBuffer中讀取請求內(nèi)容颈畸。當Server端有數(shù)據(jù)需要向client端發(fā)送響應(yīng)時,首先需要將響應(yīng)的字節(jié)數(shù)據(jù)寫入到ByteBuffer中没讲,然后通過SocketChannel的write方法向client端發(fā)送響應(yīng)的眯娱。

public class LoginCheckTask implements Runnable{
    private Selector selector;
    private ServerSocketChannel serverSocketChannel;
    private volatile  boolean stop;

    public LoginCheckTask(int port){
        try{
            //獲取Selector對象
            selector = Selector.open();
            //獲取ServerSocketChannel對象
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            //設(shè)置監(jiān)聽參數(shù)
            serverSocketChannel.socket().bind(new InetSocketAddress(port),1024);
            //向selector注冊O(shè)P_ACCEEPT事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println(" server listening");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void stop(){
        this.stop = true;
    }

    public void run(){
        while (!stop){
            try{
                //獲取通道就緒的網(wǎng)絡(luò)事件,該方法會阻塞1s
                selector.select(1000);
                Set<SelectionKey> selectionKeySet = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeySet.iterator();
                SelectionKey key = null;
                //分別處理就緒的網(wǎng)絡(luò)事件
                while(it.hasNext()){
                    key = it.next();
                    it.remove();
                    try{
                        if(key.isValid()){
                            //處理OP_ACCEPT事件
                            if(key.isAcceptable()){
                                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                                SocketChannel socketChannel = serverSocketChannel.accept();
                                socketChannel.configureBlocking(false);
                               //將與client端建立的socketChannel爬凑,向Selector注冊O(shè)P_READ事件徙缴;
                               socketChannel.register(selector,SelectionKey.OP_READ);
                                System.out.println("server accepted");
                            }
                            //處理OP_READ事件
                            if(key.isReadable()){
                                System.out.println("client is readable");
                                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                                SocketChannel sc = (SocketChannel) key.channel();
                                int readBytes = sc.read(readBuffer);
                                if(readBytes > 0){
                                    readBuffer.flip();
                                    byte[] bytes = new byte[readBytes];
                                    readBuffer.get(bytes);
                                    String body = new String(bytes,"UTF-8");
                                    String response;
                                    //獲取訪問的域名,如果是登錄請求,則返回已經(jīng)登錄嘁信,否則提示沒有登錄
                                    if(body.split(" ")[1].equals("/a/login")){
                                        response = "you are not login in this system!/n";
                                    }else{
                                        response = "you have login in this system!/n";
                                    }
                                    byte[] reponseBytes = response.getBytes();
                                    ByteBuffer reposeByteBuffer = ByteBuffer.allocate(1024);
                                    reposeByteBuffer.put(reponseBytes);
                                    reposeByteBuffer.flip();
                                    Thread.currentThread().sleep(1000);
                                    sc.write(reposeByteBuffer);
                                }
                            }

                        }
                    }catch(Exception e){
                        e.printStackTrace();
                        key.cancel();
                        if(key.channel()!= null){
                            key.channel().close();
                        }
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
        if(selector != null){
            try {
                selector.close();
            }catch(Exception e){
                e.printStackTrace();;
            }
        }

    }
}

在NIO的Client端娜搂,程序的基本執(zhí)行流程為:

  • 初始化時需要產(chǎn)生Selector對象和SocketChannel,配置SocketChannel的阻塞模式為非阻塞模式迁霎。
  • client嘗試與Server建立的Socket連接吱抚,如果直接連接成功百宇,則注冊O(shè)P_READ事件并發(fā)送請求數(shù)據(jù),否則注冊O(shè)P_CONNECT事件秘豹。
  • 與Server端類似携御,client也是通過循環(huán)不斷探測多路復(fù)用器Selector的就緒事件
    • 如果事件是連接成功事件,則注冊O(shè)P_READ事件既绕,并向Server服務(wù)端發(fā)送請求數(shù)據(jù)
    • 如果事件為可讀時間啄刹,則通過緩沖器ByteBuffer從SocketChannel中讀取Server端的響應(yīng)數(shù)據(jù)。
public class Client {

    private Selector selector;
    private SocketChannel socketChannel;

    public static void main(String args[]){
        new Client().connect();
    }
    public void connect(){
        try{
            selector = Selector.open();
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
        }catch(Exception e){
            e.printStackTrace();
        }
        try {
            //非阻塞模式凄贩,如果直接連接成功誓军,則注冊讀,否則注冊連接事件
            if(socketChannel.connect(new InetSocketAddress("127.0.0.1",8080))){
                socketChannel.register(selector, SelectionKey.OP_READ);
                doWrite(socketChannel);
            }else{
                socketChannel.register(selector,SelectionKey.OP_CONNECT);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        boolean loop= true;
        while(loop){
            try{
                selector.select(1000);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while(iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    if(key.isValid()){
                        if(key.isConnectable()){
                            if(socketChannel.finishConnect()){
                                socketChannel.register(selector, SelectionKey.OP_READ);
                                doWrite(socketChannel);
                            }else{
                                System.exit(1);
                            }
                        }
                        //讀取服務(wù)器端返回數(shù)據(jù)
                        if(key.isReadable()){
                            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                            int readBytes = socketChannel.read(byteBuffer);
                            if(readBytes > 0){
                                byteBuffer.flip();
                                byte[] responseByte = new byte[byteBuffer.remaining()];
                                byteBuffer.get(responseByte);
                                System.out.println(new String(responseByte,"UTF-8"));
                                loop= false;
                            }
                        }
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 如果連接成功疲扎,則向服務(wù)器發(fā)送數(shù)據(jù)
     * @param sc
     */
    public void doWrite(SocketChannel sc)throws Exception{
        String request = "GET /a/index HTTP/1.1 ";
        byte[] requestBytes = request.getBytes();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put(requestBytes);
        byteBuffer.flip();
        sc.write(byteBuffer);
    }
}

在整個NIOSocket通信流程中昵时,只有在Selector.select(1000)處阻塞1s,其他的讀寫操作由于都是通過緩沖器來操作Channel椒丧,所以均為非阻塞操作壹甥。

3.AIO

NIO 2.0(即AIO)引入了新的異步通道的概念,并提供了異步文件通道和異步套接字通道的實現(xiàn)壶熏。異步通道提供以下兩種方式獲取操作結(jié)果:

  • 通過java.util.concurrent.Future類表示異步操作的結(jié)果句柠;
  • 在執(zhí)行異步的時候傳入一個java.nio.channels;

CompletionHandler的實現(xiàn)類作為操作完成的回調(diào)。

NIO2.0的異步套接字通道是真正的異步非阻塞I/O,對應(yīng)于unix網(wǎng)絡(luò)編程的事件驅(qū)動I/O棒假。它不需要通過多路復(fù)用器Selector對注冊的通道進行輪詢溯职,即可實現(xiàn)異步讀寫,從而簡化了NIO的編程模型帽哑。

Server端代碼:

public class InfoServer {

    public static void main(String args[]){
        new Thread(new LoginCheckHandler(8080)).start();
    }
}

和之前類似谜酒,在構(gòu)造函數(shù)中完成Server端Socket的初始化,主要完成獲取AsynchronousServerSocketChannel對象祝拯,監(jiān)聽端口設(shè)置甚带。
在run方法中,我們通過AsynchronousServerSocketChannel的異步方法accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler)來接收client端的連接佳头,并指定了連接完成后的回調(diào)函數(shù)AcceptCompletionHandler對象(AcceptCompletionHandler是ComplettionHandler的實現(xiàn)類)

public class LoginCheckHandler implements Runnable {
    private int port;
    CountDownLatch latch;
    AsynchronousServerSocketChannel asynchronousServerSocketChannel;

    public LoginCheckHandler(int port){
        try{
            //打開異步通道
            asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
            //監(jiān)聽8080端口
            asynchronousServerSocketChannel.bind(new InetSocketAddress(8080));
            System.out.println("服務(wù)器正在監(jiān)聽8080端口中");
        }catch(Exception e){
            e.printStackTrace();
        }

    }
    public void run(){
        //latch的作用是在完成一組正在執(zhí)行的操作前之前鹰贵,允許當前的線程一直阻塞。在這里我們是為了防止服務(wù)器執(zhí)行完成退出康嘉。
        latch = new CountDownLatch(1);
        //異步ServerSocketchannel
        asynchronousServerSocketChannel.accept(this,new AcceptCompletionHandler());
        try{
            latch.await();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

AcceptCompletionHandler是Server端與Client端完成Socket通信過程建立后的回調(diào)函數(shù)碉输,當通信過程成功建立,則調(diào)用completed()方法亭珍,否則調(diào)用failed()方法敷钾。


需要特別注意枝哄,在completed方法中,還需要再次調(diào)用AsyncrhonousSocketChannel的accept方法阻荒,因為一個Server端可以接收多個client端的連接挠锥,所以需要繼續(xù)調(diào)用accept方法繼續(xù)接收其他client的連接,最終形成一個循環(huán)侨赡。每當一個client端連接進來后蓖租,再異步接收新的連接。
在completed方法中羊壹,通過asynchronousSocketChannel的read方法來異步讀取客戶端的請求內(nèi)容蓖宦,讀操作也是異步的。ReadCompletedHandler類是讀操作完成后的回調(diào)函數(shù)油猫。

public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel,LoginCheckHandler> {
    @Override
    public void completed(AsynchronousSocketChannel asynchronousSocketChannel, LoginCheckHandler attachment){
        System.out.println("服務(wù)器接收到連接");
        attachment.asynchronousServerSocketChannel.accept(attachment,this);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        asynchronousSocketChannel.read(buffer,buffer,new ReadCompletedHandler(asynchronousSocketChannel));
    }
    @Override
    public void failed(Throwable t,LoginCheckHandler attachment){
        t.printStackTrace();
        attachment.latch.countDown();
    }
}

ReadCompletedHandler也是CompletionHandler的實現(xiàn)類稠茂,如果是讀操作的回調(diào)函數(shù),java類庫已經(jīng)明確規(guī)定了,其泛型的參數(shù)類型為

CompletionHandler<Integer,ByteBuffer>

其中Integer主要是為了記錄讀取client端的請求數(shù)據(jù)的大小情妖。
在completed方法內(nèi)部睬关,通過讀取緩沖區(qū)ByteBuffer獲取請求數(shù)據(jù),并且完成業(yè)務(wù)處理鲫售,返回響應(yīng)內(nèi)容共螺。從doWrite方法內(nèi)部,可以看到寫操作也是異步的情竹。通過匿名內(nèi)部類的方式來指定了寫操作完成后的回調(diào)函數(shù)藐不。

public class ReadCompletedHandler implements CompletionHandler<Integer,ByteBuffer>{
    private AsynchronousSocketChannel asynchronousSocketChannel;
    public ReadCompletedHandler(AsynchronousSocketChannel channel){
        if( this.asynchronousSocketChannel == null){
            this.asynchronousSocketChannel = channel;
        }
    }
    public void completed(Integer result,ByteBuffer attachment){
        attachment.flip();
        byte[] reponse = new byte[attachment.remaining()];
        attachment.get(reponse);
        try{
            String req = new String(reponse,"UTF-8");
            System.out.println("the server received: "+req);
            //獲取訪問的域名,如果是登錄請求,則返回已經(jīng)登錄秦效,否則提示沒有登錄
            if(req.split(" ")[1].equals("/a/login")){
                doWrite("you are not login in this system!");
            }else{
                doWrite("you have login in this system!");
            }
        }catch(Exception e){
            e.printStackTrace();
        }

    }
    public void doWrite(String response){
        try{
            Thread.currentThread().sleep(5000);
            System.out.println("讀取到數(shù)據(jù)雏蛮,等待5s");
        }catch(Exception e){
            e.printStackTrace();
        }
        byte[] bytes = response.getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
        writeBuffer.put(bytes);
        writeBuffer.flip();
        asynchronousSocketChannel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer attachment) {
                //只要有剩余的內(nèi)容沒有發(fā)送完就繼續(xù)發(fā)送數(shù)據(jù)
                if(attachment.hasRemaining()){
                    asynchronousSocketChannel.write(attachment,attachment,this);
                }
            }

            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                try{
                    asynchronousSocketChannel.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        });
    }
    public void failed(Throwable t,ByteBuffer attachment){
        try{
            this.asynchronousSocketChannel.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

AIO client端代碼:

public class Client {
    public static void main(String args[]){
        new Thread(new ClientHandler("localhost",8080)).start();
    }
}

對于Client端的代碼,特別要注意AsynchronousSocketChannel的connect方法已經(jīng)指定了連接成功后的回調(diào)函數(shù)的泛型為

CompletionHandler<Void,ClientHandler>

在ClientHandler內(nèi)部阱州,完成了連接成功后的回調(diào)函數(shù)(ClientHandler)挑秉,寫操作完成后的回調(diào)函數(shù)(匿名內(nèi)部類),讀操作完成后的回調(diào)函數(shù)(匿名內(nèi)部類)苔货。

public class ClientHandler implements CompletionHandler<Void,ClientHandler>,Runnable {

    private AsynchronousSocketChannel asynchronousSocketChannel;
    private String host;
    private int port;
    private CountDownLatch countDownLatch;
    public ClientHandler(String host,int port){
        this.host = host;
        this.port = port;
        try{
            asynchronousSocketChannel = AsynchronousSocketChannel.open();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public void run(){
        countDownLatch = new CountDownLatch(1);
        asynchronousSocketChannel.connect(new InetSocketAddress(host,port),this,this);
        try{
            countDownLatch.await();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void completed(Void result, ClientHandler attachment) {
        byte[] req = "GET /a/index HTTP/1.1 ".getBytes();
        final ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
        writeBuffer.put(req);
        writeBuffer.flip();
        asynchronousSocketChannel.write(writeBuffer, writeBuffer,new CompletionHandler<Integer,ByteBuffer>() {
            @Override
            public void completed(Integer result,ByteBuffer attachment){
                if(attachment.hasRemaining()){
                    asynchronousSocketChannel.write(attachment,attachment,this);
                }else{
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                    asynchronousSocketChannel.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer attachment) {
                            attachment.flip();
                            byte[] bytes = new byte[attachment.remaining()];
                            attachment.get(bytes);
                            String body;
                            try{
                                body = new String(bytes,"UTF-8");
                                System.out.println(body);
                                countDownLatch.countDown();
                            }catch(Exception e){
                                e.printStackTrace();
                            }
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            try {
                                asynchronousSocketChannel.close();
                                countDownLatch.countDown();
                            }catch(Exception e){
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
            public void failed(Throwable t,ByteBuffer attachment){
                try{
                    asynchronousSocketChannel.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void failed(Throwable exc, ClientHandler attachment) {
        try{
            asynchronousSocketChannel.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

異步SocketChannel為被動執(zhí)行對象犀概,編程人員不需要像NIO一樣,編寫?yīng)毩⒌腎/O線程來處理讀寫操作夜惭。對于AsynchrousServerSocketChannel和AsynchrousSocketChannel姻灶,它們都由JDK底層的線程池負責(zé)回調(diào)并驅(qū)動讀寫操作。也正是因為如此AIO編程模型比NIO編程模型更加簡單诈茧。

4.BIO产喉、NIO、AIO對比
類型 同步阻塞BIO 同步非阻塞NIO 異步非阻塞AIO
客戶端個數(shù):(I/O 線程) 1:1 M:1(一個I/O線程處理多個客戶端連接) M:0(不需要額外的啟動線程,被動回調(diào))
I/O類型(阻塞) 阻塞I/O 非阻塞I/O 非阻塞I/O
I/O類型(同步) 同步I/O 同步I/O 異步I/O
API使用難度 簡單 非常復(fù)雜 復(fù)雜
調(diào)試難度 簡單 復(fù)雜 復(fù)雜
可靠性 非常差
吞吐量

上表對于三種類型的I/O模型進行了對比曾沈,具體應(yīng)該選用哪個編程模型这嚣,完全基于實際的業(yè)務(wù)場景進行技術(shù)選型。一般情況低負載塞俱、低并發(fā)的應(yīng)用程序可以選用阻塞IO姐帚,以降低編程的復(fù)雜度。而高負載敛腌、高并發(fā)的網(wǎng)絡(luò)應(yīng)用可以采用NIO的非阻塞模式卧土,提供應(yīng)用程序的性能。

參考:李林鋒《netty權(quán)威指南》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末像樊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子旅敷,更是在濱河造成了極大的恐慌生棍,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件媳谁,死亡現(xiàn)場離奇詭異涂滴,居然都是意外死亡,警方通過查閱死者的電腦和手機晴音,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門柔纵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锤躁,你說我怎么就攤上這事搁料。” “怎么了系羞?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵郭计,是天一觀的道長。 經(jīng)常有香客問我椒振,道長昭伸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任澎迎,我火速辦了婚禮庐杨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘夹供。我一直安慰自己灵份,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布罩引。 她就那樣靜靜地躺著各吨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上揭蜒,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天横浑,我揣著相機與錄音,去河邊找鬼屉更。 笑死徙融,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的瑰谜。 我是一名探鬼主播欺冀,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼萨脑!你這毒婦竟也來了隐轩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤渤早,失蹤者是張志新(化名)和其女友劉穎职车,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹊杖,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡悴灵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了骂蓖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片积瞒。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖登下,靈堂內(nèi)的尸體忽然破棺而出茫孔,到底是詐尸還是另有隱情,我是刑警寧澤庐船,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布银酬,位于F島的核電站,受9級特大地震影響筐钟,放射性物質(zhì)發(fā)生泄漏揩瞪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一篓冲、第九天 我趴在偏房一處隱蔽的房頂上張望李破。 院中可真熱鬧,春花似錦壹将、人聲如沸嗤攻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妇菱。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闯团,已是汗流浹背辛臊。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留房交,地道東北人彻舰。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像候味,于是被迫代替她去往敵國和親刃唤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容