1. 程式人生 > >Java NIO之非阻塞

Java NIO之非阻塞

一、阻塞與非阻塞
1、傳統的IO流是阻塞式的。當一個執行緒呼叫read或write時,執行緒會被阻塞,直到有一些資料被讀取或寫入,該執行緒在此期間不能執行其他任務。因此,在網路通訊進行IO操作時,伺服器不得不為每個客戶端提供一個獨立的執行緒來進行處理。當伺服器需要處理大量的客戶端時,效能會急劇下降。
2、NIO流是非阻塞式的。當執行緒從某個通道進行讀寫操作時,若沒有資料可用,該執行緒可以執行其他任務。執行緒通常將非阻塞IO的空閒時間用於其他通道上執行IO操作。因此,NIO可以使服務端使用一個或者有限個來處理連線到服務端的所有客戶端。

二、選擇器
非阻塞IO的核心是選擇器。選擇器(Selector)可以同時監控多個通道。

1、通過Selector.open()來建立一個選擇器。並通過通道的register()方法將通道註冊到選擇器上。
2、選擇器可以監聽的事件:
讀:SelectionKey.OP_READ
寫: SelectionKey.OP_WRITE
連線:SelectionKey.OP_CONNECT
接收:SelectionKey.OP_ACCEPT

當不止監聽一個事件時,可以用位或 | 連線。

3、例子

客戶端:
//客戶端

public class Client {
    public static void main(String[]args) {
        Scanner sc=new Scanner(System.in);

            //1、獲取socketChannel通道
        try(SocketChannel socketChannel= SocketChannel.open(new InetSocketAddress("localhost",6666))) {

            //2、設定為非阻塞模式
            socketChannel.configureBlocking(false);

            //3、獲取緩衝區
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            while (sc.hasNext()) {
                buffer.put((sc.nextLine() + "\t" + new Date().toString()+"\n").getBytes());
                buffer.flip();  //轉為讀模式

                socketChannel.write(buffer);  //將緩衝區的內容寫進通道

                buffer.clear();  //清空緩衝區
            }

            socketChannel.shutdownOutput();

        }catch(Exception e){
            e.printStackTrace();
        }


    }
}

服務端:

//服務端
public class Server {
    public static void main(String[] args) throws IOException {
        Scanner sc=new Scanner(System.in);
        ByteBuffer buffer=null;

        //1、獲取ServerSocketChannel
        ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();

        //2、繫結IP、埠
        serverSocketChannel.bind(new InetSocketAddress("localhost",6666));

        //3、將serverSocketChannel轉為非阻塞式
        serverSocketChannel.configureBlocking(false);

        //4、獲取選擇器
        Selector selector=Selector.open();

        //5、將serverSocketChannel註冊到選擇器中,並新增選擇鍵
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //6、輪詢反覆的判斷已經準備就緒的事件是否大於0
        while(selector.select()>0){
            Iterator<SelectionKey> it=selector.selectedKeys().iterator();  //獲取迭代器

            while(it.hasNext()){
                SelectionKey sKey=it.next(); //獲取SelectionKey

                //7、判斷準備就緒的是否是“接受事件”
                if(sKey.isAcceptable()){
                    //8、獲取socketChannel
                    SocketChannel socketChannel=serverSocketChannel.accept();

                    //9、將socketChannel轉為非阻塞模式
                    socketChannel.configureBlocking(false);

                    //10、將socketChannel新增到選擇器中
                    socketChannel.register(selector,SelectionKey.OP_READ);
                }
                //判斷準備就緒的是否是“寫事件”
                else if(sKey.isReadable()){
                    buffer=ByteBuffer.allocate(1024);  //獲取緩衝區

                    SocketChannel socketChannel=(SocketChannel) sKey.channel();  //獲取socketChannel

                    int size=-1;
                    while((size=socketChannel.read(buffer))!=-1){
                        buffer.flip();

                        System.out.print(new String(buffer.array(),0,size));

                        buffer.clear();

                    }
                }

                it.remove();  //關閉
            }
        }
    }