Java--NIO&&AIO

BtWangZhi發表於2017-09-25

1 阻塞和非阻塞
阻塞和非阻塞是程式在訪問資料的時候,資料內是否準備就緒的一種處理方式。
1.1 阻塞:當資料沒有準備的時候,往往需要等待緩衝區中的資料準備好過後才處理其他的事情,否者一直等待在那裡。
1.2 非阻塞:當我們的程式訪問我們的資料緩衝區的時候,資料沒有準備好的時候,直接返回,不需要等待,資料有的時候也直接返回。

2 同步和非同步的方式:
同步:同步的方式在處理IO事件的時候,必須阻塞在某個方法上面等待我們的IO時間完成。
非同步:所有的IO讀寫交給作業系統去處理,這個時候可以去做其他事情。並不需要去完成真正的IO操作。
當操作完成IO後,我們的應用程式一個通知就可以了。

3 多路複用技術(select模式)
讀寫事件交給一個單獨的執行緒來處理,完成IO事件的註冊,還有就是不斷的去輪詢我們的讀寫緩衝區,
看是否有資料準備好,通知相應的讀寫執行緒,這樣的話,以前的讀寫執行緒就可以做其他的事情。
這個時候阻塞的不是所有的IO執行緒,阻塞的是Select這個執行緒。

4 NIO:Select+非阻塞,同步非執行緒,原來的I/O以流的方式處理資料,而NIO以塊的方式處理資料。
這裡寫圖片描述

4.1 通過selector(選擇器)就相當於管家,管理所有的IO事情。
執行open工廠方法建立一個新的ServerSocketChannel物件,將會返回一個未繫結的ServerSocket關聯的通道。一個通道可以被註冊到多個選擇器上,但對每個選擇器而言只能被註冊一次。選擇鍵封裝了通道與選擇器的註冊關係。通道在被註冊到一個選擇器上之前,必須先設定為非阻塞模式。通過
呼叫configureBlocking(false).

// 獲得一個socket通道
channel = SocketChannel.open();
// 獲取 一個通道管理器
selector = Selector.open();
// 設定為非阻塞
channel.configureBlocking(false);
channel.register(this.selector, SelectionKey.OP_CONNECT);

4.2 Selector管理事件
當IO事件註冊給我們的選擇器的時候,選擇器都會給他們分配一個key值,可以簡單的理解成一個時間標籤,當IO事件完成過通過key值來找到相應的管道,然後通過管道傳送資料和接受資料。
4.3 使用非阻塞I/O編寫伺服器處理程式,大致的步驟為
1 向Selector物件註冊感興趣的事件
2 從Selector中獲取感興趣的事件
3 根據不同的事件進行相應的處理
4.4相關操作:
判斷IO事件是否已經就緒:

key.isAccptable:是否可以接受客戶端的連線
key.isconnctionable:是否可以瞭解服務端
key.isreadable();緩衝區是否可讀
key.iswriteable();緩衝區是否可寫。

獲得事件的keys:

selecttionkey keys=Selector.selectedkeys();

註冊:

channel.regist(Selector,Selectionkey.OP_Write);
channel.regist(Selector,Selectionkey.OP_Read);
channel.regist(Selector,SelectionKey.OP_Connct);
channel.regist(Selector,Selectionkey,OP_Accept);

案例–客戶端與服務端直接的通訊:
服務端流程圖
這裡寫圖片描述
服務端:

public class Server {

    private final static Integer blockSize = 4096;

    private ByteBuffer sendBuffer = ByteBuffer.allocate(blockSize);

    private ByteBuffer receiveBuffer = ByteBuffer.allocate(blockSize);

    private final static String HOSTNAME = "127.0.0.1";

    private final static Integer PORT = 7080;

    private final static InetSocketAddress SOCKET_ADDRESS = new InetSocketAddress(
            HOSTNAME, PORT);

    private Selector selector;

    public Server() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        ServerSocket serverSocket = serverSocketChannel.socket();
        // 繫結IP和埠
        serverSocket.bind(SOCKET_ADDRESS);
        // 開啟選擇器
        this.selector = Selector.open();
        // 註冊
        serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);
        System.out.println("Server start->");
    }

    // 監聽
    public void listen() throws IOException {
        System.out.println("開始監聽。。。");
        while (true) {
            // 沒有業務時,將會出現阻塞
            Integer key = this.selector.select();// 就緒的通道個數
            if (key > 0) {
                Set<SelectionKey> selectedKeys = this.selector.selectedKeys();
                Iterator<SelectionKey> iter = selectedKeys.iterator();
                while (iter.hasNext()) {
                    SelectionKey selectionKey = iter.next();
                    iter.remove();
                    // 業務邏輯
                    if (selectionKey.isAcceptable()) {// 連線事件
                        acceptHandle(selectionKey);
                    } else if (selectionKey.isReadable()) {// 讀事件
                        readHandle(selectionKey);
                    } else if (selectionKey.isWritable()) {// 寫事件
                        writeHandle(selectionKey);
                    }
                }
            }
        }
    }

    /**
     * 接受事件處理
     * 
     * @param selectionKey
     * @throws IOException
     */
    private void acceptHandle(SelectionKey selectionKey) throws IOException {
        ServerSocketChannel server = (ServerSocketChannel) selectionKey
                .channel();
        // 獲得和客戶端連線的通道
        SocketChannel client = server.accept();
        client.configureBlocking(false);// 設定為非阻塞
        client.register(this.selector, SelectionKey.OP_READ);// 註冊讀事件
    }

    /**
     * 讀事件處理
     * 
     * @param selectionKey
     * @throws IOException
     */
    private void readHandle(SelectionKey selectionKey) throws IOException {
        SocketChannel client = (SocketChannel) selectionKey.channel();
        receiveBuffer.clear();// 將limit移動到capacity位置,將position移動到0的位置,新增資料做準備
        Integer number = client.read(receiveBuffer);// 讀取資料
        if (number > 0) {
            String receiveText = new String(receiveBuffer.array(), 0, number);
            System.out.println("伺服器----接收----》" + receiveText);
            client.register(this.selector, SelectionKey.OP_WRITE);// 註冊寫事件
        }
    }

    /**
     * 寫事件處理
     * 
     * @param selectionKey
     * @throws IOException
     */
    private void writeHandle(SelectionKey selectionKey) throws IOException {
        SocketChannel client = (SocketChannel) selectionKey.channel();
        String sendText = "Hello,Client";
        sendBuffer.clear();// 將limit移動到capacity位置,將position移動到0的位置,新增資料做準備
        sendBuffer.put(sendText.getBytes());
        sendBuffer.flip();// 將limit移動到position位置,將position位置,為出資料做準備
        client.write(sendBuffer);// 傳送資料
        System.out.println("伺服器----傳送----》" + sendText);
        client.register(this.selector, SelectionKey.OP_READ);// 註冊讀事件
    }

    public static void main(String[] args) throws IOException {
        new Server().listen();
    }
}

這裡寫圖片描述
客戶端:

public class Client {
    private final static Integer blockSize = 4096;

    private final static String HOSTNAME = "127.0.0.1";

    private final static Integer PORT = 7080;

    private final static InetSocketAddress SOCKET_ADDRESS = new InetSocketAddress(
            HOSTNAME, PORT);

    private static ByteBuffer sendBuffer = ByteBuffer.allocate(blockSize);

    private static ByteBuffer receiveBuffer = ByteBuffer.allocate(blockSize);

    private Selector selector;

    public Client() throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        // 是否阻塞
        socketChannel.configureBlocking(false);
        this.selector = Selector.open();
        socketChannel.register(this.selector, SelectionKey.OP_CONNECT);
        socketChannel.connect(SOCKET_ADDRESS);
    }

    public void listen() throws IOException {
        System.out.println("客戶端啟動。。。");
        while (true) {
            int key = this.selector.select();// 就緒的通道個數
            if (key > 0) {
                Set<SelectionKey> selectionKeys = this.selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    if (selectionKey.isConnectable()) {// 連線事件
                        connectHandle(selectionKey);
                    } else if (selectionKey.isReadable()) {// 讀事件
                        readHandle(selectionKey);
                    } else if (selectionKey.isWritable()) {// 寫事件
                        writeHandle(selectionKey);
                    }
                }
                selectionKeys.clear();
            }
        }
    }

    /**
     * 連線事件處理
     * 
     * @param selectionKey
     * @throws IOException
     */
    private void connectHandle(SelectionKey selectionKey) throws IOException {
        System.out.println("client connect");
        SocketChannel client = (SocketChannel) selectionKey.channel();
        if (client.isConnectionPending()) {
            client.finishConnect();
            System.out.println("客戶端完成連線操作");
            sendBuffer.clear();// 將limit移動到capacity位置,將position移動到0的位置,新增資料做準備
            sendBuffer.put("Hello,Server".getBytes());
            sendBuffer.flip();// 將limit移動到position位置,將position位置,為出資料做準備
            client.write(sendBuffer);
        }
        client.register(this.selector, SelectionKey.OP_READ);
    }

    /**
     * 讀事件處理
     * 
     * @param selectionKey
     * @throws IOException
     */
    private void readHandle(SelectionKey selectionKey) throws IOException {
        SocketChannel client = (SocketChannel) selectionKey.channel();
        receiveBuffer.clear();
        Integer number = client.read(receiveBuffer);
        if (number > 0) {
            String receiveText;
            receiveText = new String(receiveBuffer.array(), 0, number);
            System.out.println("客戶端----接收----》" + receiveText);
            client.register(this.selector, SelectionKey.OP_WRITE);// 註冊寫事件
        }
    }

    /**
     * 寫事件處理
     * 
     * @param selectionKey
     * @throws IOException
     */
    private void writeHandle(SelectionKey selectionKey) throws IOException {
        SocketChannel client = (SocketChannel) selectionKey.channel();
        sendBuffer.clear();// 將limit移動到capacity位置,將position移動到0的位置,新增資料做準備
        String sendText = "客戶端已收到訊息";
        sendBuffer.put(sendText.getBytes());
        sendBuffer.flip();// 將limit移動到position位置,將position位置,為出資料做準備
        client.write(sendBuffer);
        System.out.println("客戶端----傳送----》" + sendText);
        client.register(this.selector, SelectionKey.OP_READ);// 註冊讀事件
    }

    public static void main(String[] args) throws IOException {
        new Client().listen();
    }
}

6 基於檔案的NIO

public class FileChannelDemo {

    @SuppressWarnings({ "resource" })
    public static void fileChannelDemo() {
        try {
            ByteBuffer buff = ByteBuffer.allocate(1024);
            FileInputStream fileInputStream=new FileInputStream("d:/a.txt");
            // 通過檔案輸入流獲取通道物件(讀取操作)
            FileChannel inFc = fileInputStream.getChannel();

            // 追加寫入檔案
            FileChannel outFc = new FileOutputStream("d:/a.txt",true).getChannel();
            // 讀取資料
            buff.clear();
            int len = inFc.read(buff);
            System.out.println(new String(buff.array(), 0, len));

            // 寫資料
            ByteBuffer buf2 = ByteBuffer.wrap("789".getBytes());
            outFc.write(buf2);

            // 關閉資源
            outFc.close();
            inFc.close();
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
    public static void main(String[] args) {
        fileChannelDemo();
        System.out.println("run over");
    }
}

7 AIO 非同步非阻塞IO
這裡寫圖片描述
案例–服務端與客戶端之間的通訊:
服務端:

package com.text.aio01;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;

public class AioServer {

    private static final String HOST_NAME = "127.0.0.1";

    private static final Integer PORT = 7080;

    private static final InetSocketAddress inetSocketAddress = new InetSocketAddress(
            HOST_NAME, PORT);

    public AioServer() throws Exception {
        final AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel
                .open();
        listener.bind(inetSocketAddress);// 繫結網路地址
        listener.accept(null,
                new CompletionHandler<AsynchronousSocketChannel, Void>() {
                    @Override
                    public void completed(AsynchronousSocketChannel ch, Void vi) {
                        listener.accept(null, this);// 接收下一個連線
                        handler(ch);
                    }

                    @Override
                    public void failed(Throwable exc, Void vi) {
                        System.out.println("非同步IO失敗");
                    }
                });
    }

    public void handler(AsynchronousSocketChannel ch) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try {
            ch.read(byteBuffer).get();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        byteBuffer.flip();
        System.out.println("伺服器接收到的資料" + byteBuffer.get());
    }

    public static void main(String[] args) {
        try {
            AioServer aioServer = new AioServer();
            Thread.sleep(10000);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

客戶端:

package com.text.aio01;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.Future;

public class AioClient {

    private static final String HOST_NAME = "127.0.0.1";

    private static final Integer PORT = 7080;

    private static final InetSocketAddress SOCKET_ADDRESS = new InetSocketAddress(
            HOST_NAME, PORT);

    private AsynchronousSocketChannel client = null;

    public AioClient() throws Exception {
        client = AsynchronousSocketChannel.open();
        Future<?> future = client.connect(SOCKET_ADDRESS);
        System.out.println(future.get());
    }

    /**
     * 寫資料
     * @param b
     */
    public void write(byte b) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(32);
        byteBuffer.put(b);
        byteBuffer.flip();
        client.write(byteBuffer);
    }

    public static void main(String[] args) throws Exception {
        AioClient aioClient = new AioClient();
        aioClient.write((byte) 11);
    }
}

附IO和NIO之間的比較。通過餐廳中服務員和客人之間的互動來形象說明。
IO:
這裡寫圖片描述
NIO:
這裡寫圖片描述